iGaming Atlas data licensing sample - field guide Sample schema: 2 Prepared: 2026-10-15 Source Atlas dataset: 2026-10-15 Source dataset path: https://igaming-atlas.com/datasets/global-igaming-regulatory-evidence-2026.json Source dataset SHA-256: 1288befa8527d4f32d91a3f1bea69307e4956a7859a8e1419f231f44ca06392a Individual verification range: 2026-07-28 to 2026-10-02 PURPOSE Evaluation of data structure and evidence limits. Selected coverage, not a complete market census, current licence-status check or legal opinion. Six selected operator-market records projected from the dated Atlas export. No regulator source was reverified by preparing this sample. The preparation date is not a source verification date. Each record retains its own evidence and access-attempt dates. Products supported by a positive conclusion are separate from products searched for a negative or unresolved finding. Authorisation does not establish consumer availability or commercial eligibility. Holds and notes remain attached. Blank legal_entity and consumer_domain fields mean no structured value is encoded in the source export. Do not infer an entity or domain from a brand. Published or historical expiry dates do not resolve an evidence hold or prove renewal. Sample data is for evaluation, not redistribution. Commercial rights and scope are agreed separately. FIELDS record_type: The legal or administrative unit represented by the row. jurisdiction: Human-readable market name. jurisdiction_code: Atlas market code. This is not always an ISO country code. market_model: Atlas classification of the jurisdiction regulatory structure. operator_or_brand: The brand or licence-holder label recorded in the source. operator_slug: Atlas identifier for the operator profile. legal_entity: Structured legal entity when encoded. Blank is not proof of no holder; consult note and source. consumer_domain: Structured domain when encoded. Blank does not authorise guessing a domain from the brand. conclusion: Jurisdiction-specific evidence result, bounded by product, entity, domain, source date and note. products: Product categories supported by a positive conclusion. JSON array; semicolon-separated CSV. Empty means none asserted in this record. searched_products: Product categories assessed for negative or unresolved findings; this field does not assert authorisation or illegality. JSON array; semicolon-separated CSV. regulator: Authority or official body associated with the evidence. evidence_type: Direct record citation or documented batch review. source_title: Title and scope of the cited source as retained in the export. source_url: Official source or register used for the conclusion. last_verified: Successful evidence-verification date; not the sample preparation date. last_access_attempt: Separate access-attempt date. A later attempt does not renew last_verified. access_attempt_outcome: Access result only; timeout or failure proves neither approval nor illegality. access_attempt_http_status: HTTP status of that attempt when recorded; blank is unknown. commercial_hold_reason: Reason commercial activation remains blocked despite the regulatory finding. valid_until: Earliest applicable published or transparently derived expiry. Null in JSON or blank in CSV does not mean perpetual validity. validity_basis: Provenance of valid_until; blank means no basis encoded. validity_by_product: Product-specific dates where the canonical record encodes them. Blank does not merge distinct licences. legal_basis: Separate legal-basis layer when present in the canonical export. note: Material entity, domain, scope, expiry and interpretation context that must travel with the row. STATUS RULES licensed and authorized are bounded findings, not proof of an available consumer product. not-listed records a register-search result and does not by itself mean illegal. under-review keeps a gap or conflict open; a future historical expiry cannot resolve that gap. licence-revoked records a documented revocation in the named jurisdiction. WORKED EXAMPLE: RECONCILE THE CSV AND JSON SAMPLES Download these two evaluation sample files into the same folder: https://igaming-atlas.com/samples/atlas-data-licensing-sample.csv https://igaming-atlas.com/samples/atlas-data-licensing-sample.json Copy only the code between BEGIN PYTHON 3 and END PYTHON 3 into a local file named check_atlas_sample.py in that folder. Run: python3 check_atlas_sample.py For other local filenames: python3 check_atlas_sample.py sample.csv sample.json Python 3 standard library only. The recipe reads those two local files and prints a report; it makes no network requests and writes no files. A mismatch or invalid input exits with a non-zero status. The CSV uses comma-separated fields with standard CSV quoting. Products and searched_products are semicolon-separated within a cell. The JSON contains sample metadata plus records; only the 25 record fields are compared with the CSV. Records are matched by record_type + jurisdiction_code + operator_slug, not by row position. Reordered rows are accepted; duplicate keys, missing records and different field values fail. JSON product arrays map to semicolon-separated CSV cells, and a null valid_until maps to a blank CSV cell. BEGIN PYTHON 3 import csv import json import sys from collections import Counter from pathlib import Path FIELDS = ["record_type","jurisdiction","jurisdiction_code","market_model","operator_or_brand","operator_slug","legal_entity","consumer_domain","conclusion","products","searched_products","regulator","evidence_type","source_title","source_url","last_verified","last_access_attempt","access_attempt_outcome","access_attempt_http_status","commercial_hold_reason","valid_until","validity_basis","validity_by_product","legal_basis","note"] LIST_FIELDS = {"products", "searched_products"} KEY_FIELDS = ("record_type", "jurisdiction_code", "operator_slug") def index_records(rows, label): indexed = {} for row in rows: key = tuple(row[field] for field in KEY_FIELDS) if not all(isinstance(value, str) and value.strip() for value in key): raise ValueError(label + ": a record has a blank or invalid composite key") if key in indexed: raise ValueError(label + ": duplicate composite key " + repr(key)) indexed[key] = row return indexed def main(): if len(sys.argv) not in (1, 3): raise ValueError("Usage: python3 check_atlas_sample.py [sample.csv sample.json]") csv_path, json_path = sys.argv[1:] or [ "atlas-data-licensing-sample.csv", "atlas-data-licensing-sample.json" ] with Path(csv_path).open(encoding="utf-8-sig", newline="") as handle: reader = csv.DictReader(handle, strict=True) headers = reader.fieldnames or [] if len(headers) != len(FIELDS) or set(headers) != set(FIELDS): raise ValueError("CSV: expected the 25 distinct sample-schema-2 fields") csv_rows = list(reader) if any(None in row or any(value is None for value in row.values()) for row in csv_rows): raise ValueError("CSV: a record has missing or extra cells") sample = json.loads(Path(json_path).read_text(encoding="utf-8-sig")) if not isinstance(sample, dict) or sample.get("sample") is not True or sample.get("sample_schema_version") != 2: raise ValueError("JSON: expected an evaluation sample with schema version 2") for field in ("sample_generated_at", "source_dataset_updated_at"): if not isinstance(sample.get(field), str) or not sample[field].strip(): raise ValueError("JSON: expected sample metadata " + field) json_rows = sample.get("records") if not isinstance(json_rows, list) or not json_rows or not csv_rows: raise ValueError("Both files must contain records") normalized_json = [] for row in json_rows: if not isinstance(row, dict) or set(row) != set(FIELDS): raise ValueError("JSON: a record does not contain the 25 sample fields") normalized = {} for field in FIELDS: value = row[field] if field in LIST_FIELDS: if not isinstance(value, list) or not all(isinstance(item, str) for item in value): raise ValueError("JSON: expected an array of strings for " + field) normalized[field] = "; ".join(value) elif field == "valid_until" and value is None: normalized[field] = "" elif isinstance(value, str): normalized[field] = value else: raise ValueError("JSON: unexpected value type for " + field) normalized_json.append(normalized) csv_index = index_records(csv_rows, "CSV") json_index = index_records(normalized_json, "JSON") if set(csv_index) != set(json_index): raise ValueError("CSV and JSON contain different record keys or counts") for key, row in json_index.items(): for field in FIELDS: if csv_index[key][field] != row[field]: raise ValueError("CSV/JSON mismatch at " + repr(key) + ", field " + field) dates = sorted(row["last_verified"] for row in json_rows) counts = Counter(row["conclusion"] for row in json_rows) print("PASS: " + str(len(json_rows)) + " records, " + str(len(FIELDS)) + " fields; CSV and JSON are equivalent.") print("Conclusions: " + "; ".join(key + "=" + str(counts[key]) for key in sorted(counts)) + ".") print("Commercial holds: " + str(sum(bool(row["commercial_hold_reason"]) for row in json_rows)) + ".") print("Blank legal_entity: " + str(sum(not row["legal_entity"] for row in json_rows)) + "; blank consumer_domain: " + str(sum(not row["consumer_domain"] for row in json_rows)) + ".") print("Prepared: " + sample["sample_generated_at"] + "; source dataset: " + sample["source_dataset_updated_at"] + ".") print("Individual last_verified range: " + dates[0] + " to " + dates[-1] + ".") print("This checks local sample format and parity only, not truth, freshness, current licence status or commercial eligibility.") if __name__ == "__main__": try: main() except (OSError, UnicodeError, csv.Error, TypeError, ValueError, KeyError) as error: print("ERROR: " + str(error), file=sys.stderr) sys.exit(1) END PYTHON 3 EXPECTED OUTPUT FOR THE PAIRED FILES PREPARED 2026-10-15 PASS: 6 records, 25 fields; CSV and JSON are equivalent. Conclusions: authorized=1; licence-revoked=1; licensed=2; not-listed=1; under-review=1. Commercial holds: 2. Blank legal_entity: 6; blank consumer_domain: 6. Prepared: 2026-10-15; source dataset: 2026-10-15. Individual last_verified range: 2026-07-28 to 2026-10-02. This checks local sample format and parity only, not truth, freshness, current licence status or commercial eligibility. READ THE RESULT PASS means the two local representations agree on their fields and values. It does not check the cited regulator sources, authenticate the source-dataset hash, confirm current permission or approve commercial use. Conclusions are counted exactly as stored, without treating licensed and authorized as a universal approval category. A commercial hold stays attached even when a record has a positive conclusion. Blank entity or domain counts describe missing structured values; they do not prove that no entity or domain exists. Keep last_verified, last_access_attempt and sample preparation dates separate. A later failed access attempt does not renew evidence; a historical or future valid_until cannot resolve an under-review finding. Dates, notes and holds are compared without being changed. This recipe targets sample schema 2. A differently scoped extract or schema needs its own field and key agreement; this example does not normalise the full Atlas dataset. COMMERCIAL DELIVERY A commercial extract can be scoped by market, operator, product, field and refresh requirement. Permitted use, attribution, redistribution and update terms are agreed in writing before delivery. NEXT STEP Review the sample and request coverage for your use case: https://igaming-atlas.com/for-business/data-licensing Describe markets, fields, intended use and refresh needs. The Research Desk confirms fit before discussing a commercial licence.