Developer documentation

RegulaView Operator Reporting API

A workflow-first integration guide for licensed operators. You report individual transactions in real time; RegulaView validates, deduplicates, fraud-screens and aggregates them in memory, then reconciles your signed end-of-day report against the data it actually received.

Quickstart

Minimum viable production integration

Operators report continuously over HTTPS. RegulaView accepts signed source events, deduplicates by sequence, validates the stream, aggregates it, and reconciles your signed end-of-day report against the data it received.

  1. 1
    Get a token

    Exchange issued client credentials for a short-lived access token and cache it until close to expiry.

  2. 2
    Retrieve the daily signing key

    Use the key for the UTC day of the transaction timestamp.

  3. 3
    Sign and report transactions

    Send every wager, payout, void, refund, adjustment, deposit, and withdrawal, each with a per-operator sequence.

  4. 4
    Send operational evidence

    Heartbeat devices, submit responsible-gaming events, and send cashier or payment-provider totals.

  5. 5
    Declare daily tax details

    Submit aggregate tax details after the business day closes.

  6. 6
    Correct through append-only requests

    Use correction requests for after-the-fact changes instead of altering accepted events.

  7. 7
    Submit your signed end-of-day report

    After the day closes, declare your totals and sequence range. RegulaView reconciles them against its sealed aggregate.

How it works

You send transactions; RegulaView keeps aggregates

You send individual transactions, 1..N per request. RegulaView processes each one in memory — validate, deduplicate, fraud-screen, then fold it into time-bucketed aggregates — and keeps the aggregates as its system of record. Individual transactions are not stored long-term.

Sequence is required

Every transaction carries a per-operator monotonic sequence. It drives dedup and gap detection. A missing sequence is rejected.

Ingestion returns 202

RegulaView acknowledges receipt and folds the work. There is no 409 Conflict for a transaction; a repeated sequence is reported as duplicate.

You also report end-of-day

You submit signed daily totals, and RegulaView reconciles them against the data it received. The independent match is a primary fraud and completeness control.

Environments and versioning

Use the same v1 paths everywhere

EnvironmentBase URLPurpose
Sandboxhttps://api.sandbox.regulaview.ioIntegration testing and certification rehearsal.
Productionhttps://api.regulaview.ioLive regulatory reporting.

URL versioning is canonical. All endpoints in this guide use /v1. The optional X-RegulaView-Api-Version: 1.0 header is accepted for client tooling metadata, but the URL segment is the source of truth.

Authentication

Request an access token

Each operator receives a client ID, client secret, and scopes during onboarding. The bearer token identifies the licensed entity and scopes every subsequent request.

POST /v1/auth/token HTTP/1.1
Host: api.regulaview.io
Content-Type: application/json
X-Correlation-Id: op-token-20260430-0001

{
  "clientId": "<your-client-id>",
  "clientSecret": "<your-client-secret>",
  "grantType": "client_credentials"
}

Response 200 OK — a short-lived bearer token:

{
  "accessToken": "eyJhbGciOi...",
  "tokenType": "Bearer",
  "expiresIn": 3600,
  "scope": "ingest:write read:own"
}

Example — request and cache a token:

const BASE = "https://api.regulaview.io";

async function getToken(clientId, clientSecret) {
  const res = await fetch(`${BASE}/v1/auth/token`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ clientId, clientSecret, grantType: "client_credentials" })
  });
  if (!res.ok) throw new Error(`token ${res.status}`);
  return (await res.json()).accessToken; // cache until close to expiresIn
}
ScopeAllows
ingest:writeTransactions, tax declarations, cashier totals, heartbeats, corrections, responsible-gaming events, signing-key retrieval, and signed end-of-day reports.
read:ownOperator-scoped aggregate reports, reconciliation outcomes, devices, tax summaries, cashier rows, corrections, policies, and aggregate integrity manifests.
read:allRegulator or administrator read access. Normal operator clients are not issued this scope.

Making requests

A plain HTTPS + JSON API — no SDK required

RegulaView is a standard HTTPS + JSON API, so you call it directly with whatever HTTP client your language already ships with — fetch in JavaScript, HttpClient in .NET, java.net.http.HttpClient in Java, net/http in Go, or any equivalent.

Every example below is self-contained and uses only the standard library. They share two conventions to stay short:

  • The base URL is https://api.regulaview.io (use https://api.sandbox.regulaview.io for sandbox).
  • token is the accessToken string returned by the token endpoint. Cache it and reuse it until close to expiry.
  • Standard import/using lines for the HTTP client are omitted for brevity, as is response error handling beyond the happy path.

Wrapping these calls in your own helper is a good idea in real code, but each example shows the raw request so nothing is hidden behind a library you would have to write first.

Headers and data formats

Standard request shape

HeaderRequiredApplies toDescription
AuthorizationYesAll authenticated endpointsBearer <accessToken> from the token endpoint.
Content-TypeYes for bodiesPOST endpointsUse application/json.
Idempotency-KeyOptionalSingle transaction ingestClient retry key, echoed on the receipt. Deduplication is driven by sequence.
X-RegulaView-SignatureYes in production and certificationSingle transaction ingestLowercase hex HMAC-SHA256 over the canonical transaction string.
X-Correlation-IdNoAll endpointsClient trace identifier echoed in error payloads and logs.
TypeFormat
GUIDStandard UUID string.
Date/timeISO 8601 UTC, for example 2026-04-30T12:34:56Z.
Date onlyyyyy-MM-dd.
MoneyJSON number with currency in a separate ISO 4217 field.
EnumString enum name in JSON. Numeric enum values are used only inside the transaction signing string.

Transaction signing

Sign the exact transaction being submitted

Retrieve one daily key per UTC reporting day, then sign every transaction with the key for that transaction's occurredAtUtc day. Single submissions carry the signature in X-RegulaView-Signature; batch submissions carry signature on each item.

GET /v1/integrity/manifests/2026-04-30/signing-key HTTP/1.1
Authorization: Bearer <accessToken>
const day = "2026-04-30"; // UTC day of the transactions you are about to sign
const res = await fetch(`https://api.regulaview.io/v1/integrity/manifests/${day}/signing-key`, {
  headers: { Authorization: `Bearer ${token}` }
});
const key = await res.json(); // { keyId, secretBase64, algorithm, ... }

Response 200 OK — the day's signing key:

{
  "day": "2026-04-30",
  "keyId": "manifest-20260430",
  "secretBase64": "<daily-operator-hmac-secret>",
  "algorithm": "HMAC-SHA256",
  "validFromUtc": "2026-04-30T00:00:00Z",
  "validToUtc": "2026-05-01T00:00:00Z"
}

Canonical signing string — join these fields with |:

{keyId}|{deviceIdentity}|{externalId}|{type:int}|{product:int}|{amount:F4}|{currency}|{occurredAtUnixMs}|{chainReference}|{correlationId}
Canonical details

Use deviceExternalId as the device identity when present. Format amounts with four decimal places, use UTC Unix milliseconds for timestamps, and leave empty segments for missing optional values. sequence is not part of the signing string.

Transaction typeSigning valueCommon productSigning value
Wager0OnlineCasino0
Payout1PhysicalCasino1
Adjustment2RetailGambling2
Void3SportsBetting3
Refund4Lotto5
Deposit5GamingMachine9
Withdrawal6Other99

Example — compute the signature:

const crypto = require("crypto");

function signTransaction(key, tx) {
  const type = { Wager: 0, Payout: 1, Adjustment: 2, Void: 3, Refund: 4, Deposit: 5, Withdrawal: 6 }[tx.type];
  const product = { OnlineCasino: 0, PhysicalCasino: 1, RetailGambling: 2, SportsBetting: 3, VirtualGames: 4, Lotto: 5, InstantWin: 6, Keno: 7, Bingo: 8, GamingMachine: 9, Jackpot: 10, ParimutuelRacing: 11, MobileGambling: 12, Poker: 13, VirtualSports: 14, ScheduledGames: 15, Other: 99 }[tx.product ?? "Other"];
  const canonical = [
    key.keyId,
    tx.deviceExternalId ?? "",
    tx.externalId,
    type,
    product,
    Number(tx.amount).toFixed(4),
    tx.currency,
    new Date(tx.occurredAtUtc).getTime(),
    tx.chainReference ?? "",
    tx.correlationId ?? ""
  ].join("|");

  return crypto.createHmac("sha256", Buffer.from(key.secretBase64, "base64"))
    .update(canonical, "utf8")
    .digest("hex");
}

Ingestion

Submit a signed transaction

Send each wager, payout, void, refund, adjustment, deposit, and withdrawal as a distinct event. Use your own stable externalId for the transaction, a per-operator monotonic sequence, and deviceExternalId for the source device.

POST /v1/ingest/transactions HTTP/1.1
Host: api.regulaview.io
Authorization: Bearer <accessToken>
Content-Type: application/json
Idempotency-Key: 8c8a6a5b-6c37-4f1a-89c6-6264de4663c0
X-RegulaView-Signature: <lowercase-hex-hmac>
X-Correlation-Id: tx-OP-20260430-000001

{
    "externalId": "OP-20260430-000001",
    "sequence": 1839202,
    "deviceExternalId": "shop-014-terminal-02",
    "type": "Wager",
    "amount": 5.00,
    "currency": "EUR",
    "occurredAtUtc": "2026-04-30T10:30:00Z",
    "product": "Lotto",
    "chainReference": "TICKET-20260430-A1B2",
    "correlationId": "ticket-processor-7"
}
const day = tx.occurredAtUtc.slice(0, 10); // UTC day of the transaction
const keyRes = await fetch(`https://api.regulaview.io/v1/integrity/manifests/${day}/signing-key`, {
  headers: { Authorization: `Bearer ${token}` }
});
const key = await keyRes.json();

const res = await fetch("https://api.regulaview.io/v1/ingest/transactions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${token}`,
    "Content-Type": "application/json",
    "X-RegulaView-Signature": signTransaction(key, tx), // from Transaction signing
    "Idempotency-Key": tx.externalId
  },
  body: JSON.stringify(tx)
});
const receipt = await res.json();

Response 202 Accepted — an acknowledgement receipt (the transaction is folded, not stored):

{
  "receipt": {
    "transactionId": "f4c1a3d2-3b61-4a8b-a9cb-65258c03c503",
    "externalId": "OP-20260430-000001",
    "status": "accepted",
    "payloadHash": "6e8c...",
    "idempotencyKey": "8c8a6a5b-6c37-4f1a-89c6-6264de4663c0",
    "message": null
  }
}

transactionId is an acknowledgement id for the fold; the transaction itself is not persisted. status is accepted or duplicate (the sequence is already folded).

FieldRequiredRules
externalIdYesUnique per operator for one immutable transaction event. Maximum 64 characters.
sequenceYesPer-operator, strictly increasing, gap-free 64-bit number. Drives deduplication and gap detection. A missing sequence is rejected.
deviceExternalIdNoRecommended for events from a device, terminal, outlet server, or gaming system. Maximum 128 characters.
typeYesWager, Payout, Adjustment, Void, Refund, Deposit, or Withdrawal.
amountYesNon-negative decimal. Economic sign is derived from type.
currencyYesISO 4217 three-letter code.
occurredAtUtcYesUTC event time from the operator system.
productNoDefaults to Other if omitted.
chainReferenceNoRecommended link between lifecycle events, such as wager to payout or void.

Sequence & deduplication

One contiguous, monotonic stream per operator

sequence is a per-operator, strictly increasing, gap-free number that you assign in the order your systems produce transactions. RegulaView reconstructs your stream's head, tail and any gaps from these numbers, so the rules are simple:

Required

Every transaction must carry a sequence. A missing sequence returns 400 missing_sequence, because an absent sequence would let a gap go undetected.

Dedup by sequence

Re-sending a transaction with the same sequence returns 202 Accepted with status = "duplicate". Keep the sequence stable across retries.

Gaps are signals

Out-of-order delivery is fine, but the set you send for a day must be contiguous from first to last. Missing numbers are treated as missing data.

Batch and retry

Use batches for catch-up and store-and-forward

The batch endpoint accepts up to 5000 transactions. Preserve the original occurredAtUtc, give each item its own sequence, sign each item individually, and put per-item idempotencyKey values in the request body.

POST /v1/ingest/transactions/batch HTTP/1.1
Authorization: Bearer <accessToken>
Content-Type: application/json

{
    "transactions": [
        {
            "externalId": "OP-20260430-000002",
            "sequence": 1839203,
            "deviceExternalId": "shop-014-terminal-02",
            "type": "Wager",
            "amount": 10.00,
            "currency": "EUR",
            "occurredAtUtc": "2026-04-30T12:35:01Z",
            "product": "SportsBetting",
            "idempotencyKey": "OP-20260430-000002",
            "signature": "<lowercase-hex-hmac>"
        }
    ]
}
// key fetched as in "Retrieve the daily signing key"; sign each item individually
const items = transactions.map(tx => ({
  ...tx,
  idempotencyKey: tx.externalId,
  signature: signTransaction(key, tx) // from Transaction signing
}));

const res = await fetch("https://api.regulaview.io/v1/ingest/transactions/batch", {
  method: "POST",
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
  body: JSON.stringify({ transactions: items })
});
const result = await res.json();

Response 200 OK — per-item results in submission order:

{
  "batchId": "b1f0e3c4-2a77-4f0e-9c1a-7d3e9b2a51cc",
  "accepted": 1,
  "duplicates": 0,
  "rejected": 0,
  "errors": null,
  "results": [
    {
      "index": 0,
      "externalId": "OP-20260430-000002",
      "status": "accepted",
      "transactionId": "a2d9c1f7-58b0-4e2a-9f3d-1c7b8e6042aa",
      "idempotencyKey": "OP-20260430-000002",
      "payloadHash": "6e8c...",
      "errorCode": null,
      "message": null
    }
  ]
}

Duplicates are success

An item whose sequence was already folded returns a duplicate receipt.

Per-item results

Each item reports accepted, duplicate, or rejected (for example missing_sequence) in the results array.

Backoff is expected

Retry transient 5xx, 429, and timeouts with jitter, resending each item with its original sequence.

Daily tax declarations

Submit aggregate tax details after close

Transaction reporting is real time. Tax declarations are the operator's daily aggregate statement, submitted after the reporting day closes. A declaration for the same day replaces the previous declaration inside the accepted window, making late changes explicit and auditable.

POST /v1/tax/declarations HTTP/1.1
Authorization: Bearer <accessToken>
Content-Type: application/json

{
  "day": "2026-04-30",
  "grossWagers": 125000.00,
  "payouts": 91000.00,
  "declaredTax": 5100.00
}
const res = await fetch("https://api.regulaview.io/v1/tax/declarations", {
  method: "POST",
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
  body: JSON.stringify({ day: "2026-04-30", grossWagers: 125000.00, payouts: 91000.00, declaredTax: 5100.00 })
});
const summary = await res.json();

Response 200 OK — the computed summary and variance vs your declaration:

{
  "day": "2026-04-30",
  "grossWagers": 125000.00,
  "payouts": 91000.00,
  "netRevenue": 34000.00,
  "calculatedTax": 5100.00,
  "declaredTax": 5100.00,
  "variance": 0.00
}

Cashier reconciliation

Send independent payment or cashier totals

Reconciliation totals give the regulator an independent ground-truth feed to compare against transaction-derived cash-in and cash-out activity.

POST /v1/recon/cashier-totals HTTP/1.1
Authorization: Bearer <accessToken>
Content-Type: application/json

{
  "day": "2026-04-30",
  "cashIn": 48000.00,
  "cashOut": 36500.00,
  "outletReference": "SHOP-014",
  "source": "cashier-close-v2"
}
const res = await fetch("https://api.regulaview.io/v1/recon/cashier-totals", {
  method: "POST",
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
  body: JSON.stringify({ day: "2026-04-30", cashIn: 48000.00, cashOut: 36500.00, outletReference: "SHOP-014", source: "cashier-close-v2" })
});
const stored = await res.json();

Response 200 OK — the stored totals for the outlet day:

{
  "operatorId": "9f1c2b6e-7a40-4d8c-bb21-5e3f0a9d77c1",
  "day": "2026-04-30",
  "outletReference": "SHOP-014",
  "cashIn": 48000.00,
  "cashOut": 36500.00
}

Devices and responsible gaming

Report operational state and player-protection events

Heartbeats show whether the reporting estate is alive. Responsible-gaming events surface policy triggers and high-risk patterns for regulatory review.

POST /v1/devices/external/terminal-alpha-07/heartbeat HTTP/1.1
Authorization: Bearer <accessToken>
Content-Type: application/json

{
  "configurationVersion": "2026.04.30.1",
  "configurationHash": "b6e4f8b7266f4d2e998c7f2a0f1be75c",
  "gameEnabled": true,
  "health": "Healthy"
}
const state = await api("POST", `/v1/devices/external/${deviceExternalId}/heartbeat`,
  { configurationVersion: "2026.04.30.1", configurationHash: "b6e4f8b7266f4d2e998c7f2a0f1be75c", gameEnabled: true, health: "Healthy" });

Response 200 OK — echoes the current remote-disable state:

{
  "deviceId": "c7a1f0b3-9d24-4e15-8a6c-2f1b7e904db8",
  "acceptedAtUtc": "2026-04-30T10:30:05Z",
  "remotelyDisabled": false
}
POST /v1/rg/events HTTP/1.1
Authorization: Bearer <accessToken>
Content-Type: application/json

{
  "type": "LimitBreach",
  "severity": "High",
  "deviceExternalId": "terminal-alpha-07",
  "pseudonymousPlayerRef": "player-hash-91b2",
  "summary": "Daily loss threshold breached",
  "evidence": "loss=500;threshold=500;currency=EUR"
}
const event = await api("POST", "/v1/rg/events", {
  type: "LimitBreach", severity: "High", deviceExternalId: "terminal-alpha-07",
  pseudonymousPlayerRef: "player-hash-91b2", summary: "Daily loss threshold breached",
  evidence: "loss=500;threshold=500;currency=EUR"
});

Response 202 Accepted — the recorded event id:

{
  "id": "e3b8a1d6-4c70-4f92-9a51-8b2d6f0c34e7"
}

Corrections

Fix accepted events through append-only requests

Corrections create a reviewed delta record. They do not mutate the original accepted transaction or the aggregate that sealed it.

POST /v1/corrections HTTP/1.1
Authorization: Bearer <accessToken>
Content-Type: application/json

{
  "originalExternalId": "OP-20260430-000001",
  "reason": "Operator settlement system corrected payout amount after manual review.",
  "payloadDiff": [
    {
      "path": "/amount",
      "from": 5.00,
      "to": 4.50,
      "reason": "Manual settlement review corrected the amount."
    }
  ]
}
const correction = await api("POST", "/v1/corrections", {
  originalExternalId: "OP-20260430-000001",
  reason: "Operator settlement system corrected payout amount after manual review.",
  payloadDiff: [{ path: "/amount", from: 5.00, to: 4.50, reason: "Manual settlement review corrected the amount." }]
});

Response 201 Created — the new correction enters review (no original event is mutated):

{
  "id": "d41a9f02-6b3e-4c81-8d57-90a1c2e7b4f3",
  "status": "Pending",
  "version": 1
}

End-of-day reconciliation

Declare your totals; RegulaView verifies them

After your reporting day closes, submit your own signed end-of-day report: the declared count, sequence range and money totals for the day. Once the day's aggregate is sealed, RegulaView reconciles your declaration against the data it actually received — counts, wagered and paid-out amounts, and your declared sequence range vs. the contiguous set received. This independent match is a primary fraud and completeness control. Sign the report with the same daily key you use for transactions.

{keyId}|{day:yyyy-MM-dd}|{currency}|{declaredCount}|{declaredFirstSequence}|{declaredLastSequence}|{declaredWageredAmount:F4}|{declaredPaidOutAmount:F4}
POST /v1/ingest/end-of-day HTTP/1.1
Authorization: Bearer <accessToken>
Content-Type: application/json

{
  "day": "2026-04-30",
  "currency": "EUR",
  "declaredCount": 1839201,
  "declaredFirstSequence": 1,
  "declaredLastSequence": 1839201,
  "declaredWageredAmount": 4820551.0000,
  "declaredPaidOutAmount": 3611200.5000,
  "signature": "<lowercase-hex-hmac>"
}
const crypto = require("crypto");
// key fetched as in "Retrieve the daily signing key"
const canonical = [
  key.keyId, report.day, report.currency,
  report.declaredCount, report.declaredFirstSequence, report.declaredLastSequence,
  report.declaredWageredAmount.toFixed(4), report.declaredPaidOutAmount.toFixed(4)
].join("|");
report.signature = crypto.createHmac("sha256", Buffer.from(key.secretBase64, "base64"))
  .update(canonical, "utf8").digest("hex");

const res = await fetch("https://api.regulaview.io/v1/ingest/end-of-day", {
  method: "POST",
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
  body: JSON.stringify(report)
});
const receipt = await res.json();

Response 202 Accepted — receipt only; reconciliation runs after the day seals:

{
  "reportId": "7c2e1b8a-3d49-4f60-9a12-5e8c0b71d2f4",
  "day": "2026-04-30",
  "status": "accepted",
  "message": "Reconciliation runs once the day's aggregate is sealed."
}

Read the reconciliation outcome for a day or range:

GET /v1/reconciliation?from=2026-04-01&to=2026-04-30 HTTP/1.1
Authorization: Bearer <accessToken>
const outcomes = await api("GET", "/v1/reconciliation?from=2026-04-01&to=2026-04-30");

Response 200 OK — one outcome per reconciled day:

[
  {
    "operatorId": "9f1c2b6e-7a40-4d8c-bb21-5e3f0a9d77c1",
    "day": "2026-04-30",
    "jurisdiction": "MW",
    "ourCount": 1839201,
    "declaredCount": 1839201,
    "countVariance": 0,
    "amountVariance": 0.0000,
    "sequenceGapCount": 0,
    "matched": true,
    "outcome": "Matched",
    "evaluatedAtUtc": "2026-05-01T00:15:00Z"
  }
]
Why it matters

If your declared range is 1..1,839,201 but the contiguous set RegulaView aggregated is short by 20 sequences, reconciliation raises a missing-data finding for those 20 — even though no individual transaction is stored.

Aggregate integrity

Verify sealed aggregates

RegulaView seals each aggregation window into a hash-chained, server-signed bucket and publishes a daily Merkle manifest over those seals. You can retrieve the manifest, request an inclusion proof for a single sealed window, and re-walk the seal chain to verify continuity.

GET /v1/integrity/aggregates/manifest?day=2026-04-30 HTTP/1.1
Authorization: Bearer <accessToken>
const manifest = await api("GET", "/v1/integrity/aggregates/manifest?day=2026-04-30");

Response 200 OK — the signed Merkle manifest over the day's sealed windows:

{
  "operatorId": "9f1c2b6e-7a40-4d8c-bb21-5e3f0a9d77c1",
  "resolution": "Day",
  "fromUtc": "2026-04-30T00:00:00Z",
  "toUtc": "2026-05-01T00:00:00Z",
  "entryCount": 1,
  "merkleRoot": "a3f9c1...",
  "signature": "5c7d2e...",
  "signingKeyId": "manifest-20260430",
  "entries": [
    {
      "windowStartUtc": "2026-04-30T00:00:00Z",
      "chainSequence": 412,
      "sealHash": "9b2e7a...",
      "sealState": "Sealed"
    }
  ]
}
GET /v1/integrity/aggregates/proof?bucketId=<sealed-window-id> HTTP/1.1
Authorization: Bearer <accessToken>
const proof = await api("GET", `/v1/integrity/aggregates/proof?bucketId=${bucketId}`);

Response 200 OK — an inclusion proof you can verify against the signed root:

{
  "operatorId": "9f1c2b6e-7a40-4d8c-bb21-5e3f0a9d77c1",
  "resolution": "Day",
  "windowStartUtc": "2026-04-30T00:00:00Z",
  "chainSequence": 412,
  "leafHash": "9b2e7a...",
  "leafIndex": 0,
  "merkleRoot": "a3f9c1...",
  "verified": true,
  "steps": [
    { "siblingHex": "7d10b4...", "siblingIsRight": true }
  ]
}
GET /v1/integrity/aggregates/verify-chain?day=2026-04-30 HTTP/1.1
Authorization: Bearer <accessToken>
const result = await api("GET", "/v1/integrity/aggregates/verify-chain?day=2026-04-30");

Response 200 OK — a clean re-walk; firstBadSequence is set if tampering is found:

{
  "operatorId": "9f1c2b6e-7a40-4d8c-bb21-5e3f0a9d77c1",
  "resolution": "Day",
  "ok": true,
  "verified": 412,
  "firstBadSequence": null,
  "reason": null
}

verify-chain re-walks the seal chain (continuity, previous-seal links, recomputed hashes, and the server signature) and reports the first bad sequence if any.

Endpoint reference

Core v1 endpoints

POST/v1/auth/token

Issue an access token.

GET/v1/operator-onboarding/me

Read authenticated operator profile details.

GET/v1/integrity/manifests/{day}/signing-key

Retrieve the daily HMAC key.

POST/v1/ingest/transactions

Submit one signed transaction.

POST/v1/ingest/transactions/batch

Submit up to 5000 signed transactions.

POST/v1/ingest/end-of-day

Submit your signed end-of-day report.

GET/v1/reconciliation

Read end-of-day reconciliation outcomes.

GET/v1/reports/aggregates/summary

Read aggregate totals for a window.

GET/v1/reports/aggregates/series

Read resolution-aware aggregate windows.

GET/v1/reports/aggregates/gaps

Read detected sequence gaps.

GET/v1/devices

Read mapped operator devices.

POST/v1/devices/external/{deviceExternalId}/heartbeat

Report device liveness.

POST/v1/tax/declarations

Submit daily aggregate tax details.

GET/v1/tax

Read tax summaries and variance.

POST/v1/recon/cashier-totals

Submit independent cashier totals.

POST/v1/corrections

Append an after-the-fact correction request.

POST/v1/rg/events

Submit responsible-gaming events.

GET/v1/integrity/aggregates/manifest

Retrieve the daily aggregate Merkle manifest.

GET/v1/integrity/aggregates/proof

Inclusion proof for one sealed window.

GET/v1/integrity/aggregates/verify-chain

Re-walk and verify the seal chain.

Errors

Problem responses are stable and supportable

Errors use a standard problem envelope with errorCode, message, status, correlationId, requestId, and timestampUtc. Quote the correlation and request IDs in support cases.

{
  "type": "missing_sequence",
  "title": "Bad request",
  "status": 400,
  "detail": "A per-operator monotonic sequence is required for every transaction.",
  "correlationId": "op-2026-04-30-000001",
  "requestId": "0HMT...",
  "timestampUtc": "2026-04-30T12:34:56Z",
  "errors": null
}
HTTPCodeMeaning
400missing_sequenceThe transaction has no per-operator sequence.
400signature_requiredTransaction signing is required in this environment.
400signature_source_mismatchHeader and body signatures are both present but differ.
400signature_invalidThe calculated transaction or end-of-day HMAC did not validate.
400no_signing_keyNo daily key has been issued for the transaction day.
422timestamp_out_of_rangeThe transaction timestamp is outside the acceptance window.
429rate_limitedThe caller exceeded the configured rate limit.

Transaction ingestion does not return 409 Conflict: deduplication is by sequence, and a repeated sequence is acknowledged as 202 Accepted with status = "duplicate".

Rate limits

Sandbox testing capacity policies

The defaults below describe the sandbox and certification environment used during integration testing. Production limits are agreed during operator onboarding and may differ based on certified throughput and regulatory requirements.

PolicySandbox defaultPartitionApplies to
token60 requests/minuteSource IPAccess-token issuance.
ingest6000 requests/minuteAuthenticated operator identityTransactions, batches, heartbeats, corrections, recon, signing-key retrieval, and end-of-day reports.
read600 requests/minuteAuthenticated operator identityOperator-scoped queries and integrity reads.
anonymous120 requests/minuteSource IPUnauthenticated status and public policy reads.

Certification checklist

What operators should prove before go-live

  • Token caching and refresh before expiry.
  • Daily signing-key retrieval for each UTC transaction day.
  • Valid signatures for all seven transaction types.
  • Every transaction carries a gap-free, monotonic sequence.
  • Duplicate replay (same sequence) returns duplicate receipts and is treated as success.
  • A transaction without sequence returns 400 missing_sequence.
  • Batch replay works after an offline store-and-forward period.
  • Device heartbeats arrive on the agreed cadence.
  • Tax declarations and cashier totals are submitted after close.
  • A signed end-of-day report produces a Matched reconciliation outcome.
  • Aggregate integrity manifest, inclusion proof, and a clean verify-chain result.
  • Support tickets include correlation ID and request ID.

OpenAPI

Machine-readable contract

Use this guide for integration behavior and the OpenAPI document for schema validation, client generation, Postman imports, and automated contract checks. Swagger is reference tooling; this page is the operator integration guide.