Sequence is required
Every transaction carries a per-operator monotonic sequence. It drives dedup and gap detection. A missing sequence is rejected.
Developer documentation
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
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.
Exchange issued client credentials for a short-lived access token and cache it until close to expiry.
Use the key for the UTC day of the transaction timestamp.
Send every wager, payout, void, refund, adjustment, deposit, and withdrawal, each with a per-operator sequence.
Heartbeat devices, submit responsible-gaming events, and send cashier or payment-provider totals.
Submit aggregate tax details after the business day closes.
Use correction requests for after-the-fact changes instead of altering accepted events.
After the day closes, declare your totals and sequence range. RegulaView reconciles them against its sealed aggregate.
How it works
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.
Every transaction carries a per-operator monotonic sequence. It drives dedup and gap detection. A missing sequence is rejected.
RegulaView acknowledges receipt and folds the work. There is no 409 Conflict for a transaction; a repeated sequence is reported as duplicate.
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
| Environment | Base URL | Purpose |
|---|---|---|
| Sandbox | https://api.sandbox.regulaview.io | Integration testing and certification rehearsal. |
| Production | https://api.regulaview.io | Live 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
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
}
using System;
using System.Net.Http;
using System.Net.Http.Json;
var http = new HttpClient { BaseAddress = new Uri("https://api.regulaview.io") };
async Task<string> GetTokenAsync(string clientId, string clientSecret)
{
var res = await http.PostAsJsonAsync("/v1/auth/token",
new { clientId, clientSecret, grantType = "client_credentials" });
res.EnsureSuccessStatusCode();
var body = await res.Content.ReadFromJsonAsync<TokenResponse>();
return body!.AccessToken; // cache until close to ExpiresIn
}
record TokenResponse(string AccessToken, string TokenType, int ExpiresIn, string Scope);
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
static final HttpClient HTTP = HttpClient.newHttpClient();
static String getToken(String clientId, String clientSecret) throws Exception {
String body = "{\"clientId\":\"" + clientId + "\",\"clientSecret\":\"" + clientSecret
+ "\",\"grantType\":\"client_credentials\"}";
HttpResponse<String> res = HTTP.send(
HttpRequest.newBuilder(URI.create("https://api.regulaview.io/v1/auth/token"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body)).build(),
HttpResponse.BodyHandlers.ofString());
return readAccessToken(res.body()); // parse with your JSON library; cache until expiry
}
package client
import (
"bytes"
"encoding/json"
"net/http"
)
const base = "https://api.regulaview.io"
func getToken(clientID, clientSecret string) (string, error) {
body, _ := json.Marshal(map[string]string{
"clientId": clientID,
"clientSecret": clientSecret,
"grantType": "client_credentials",
})
res, err := http.Post(base+"/v1/auth/token", "application/json", bytes.NewReader(body))
if err != nil {
return "", err
}
defer res.Body.Close()
var out struct {
AccessToken string `json:"accessToken"`
}
if err := json.NewDecoder(res.Body).Decode(&out); err != nil {
return "", err
}
return out.AccessToken, nil // cache until expiresIn
}
| Scope | Allows |
|---|---|
ingest:write | Transactions, tax declarations, cashier totals, heartbeats, corrections, responsible-gaming events, signing-key retrieval, and signed end-of-day reports. |
read:own | Operator-scoped aggregate reports, reconciliation outcomes, devices, tax summaries, cashier rows, corrections, policies, and aggregate integrity manifests. |
read:all | Regulator or administrator read access. Normal operator clients are not issued this scope. |
Making requests
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:
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.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
| Header | Required | Applies to | Description |
|---|---|---|---|
Authorization | Yes | All authenticated endpoints | Bearer <accessToken> from the token endpoint. |
Content-Type | Yes for bodies | POST endpoints | Use application/json. |
Idempotency-Key | Optional | Single transaction ingest | Client retry key, echoed on the receipt. Deduplication is driven by sequence. |
X-RegulaView-Signature | Yes in production and certification | Single transaction ingest | Lowercase hex HMAC-SHA256 over the canonical transaction string. |
X-Correlation-Id | No | All endpoints | Client trace identifier echoed in error payloads and logs. |
| Type | Format |
|---|---|
| GUID | Standard UUID string. |
| Date/time | ISO 8601 UTC, for example 2026-04-30T12:34:56Z. |
| Date only | yyyy-MM-dd. |
| Money | JSON number with currency in a separate ISO 4217 field. |
| Enum | String enum name in JSON. Numeric enum values are used only inside the transaction signing string. |
Transaction signing
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, ... }
var day = "2026-04-30"; // UTC day of the transactions you are about to sign
using var http = new HttpClient();
using var req = new HttpRequestMessage(HttpMethod.Get,
$"https://api.regulaview.io/v1/integrity/manifests/{day}/signing-key");
req.Headers.Authorization = new("Bearer", token);
var res = await http.SendAsync(req);
res.EnsureSuccessStatusCode();
var key = await res.Content.ReadFromJsonAsync<SigningKey>();
String day = "2026-04-30"; // UTC day of the transactions you are about to sign
HttpResponse<String> res = HttpClient.newHttpClient().send(
HttpRequest.newBuilder(URI.create(
"https://api.regulaview.io/v1/integrity/manifests/" + day + "/signing-key"))
.header("Authorization", "Bearer " + token).GET().build(),
HttpResponse.BodyHandlers.ofString());
// res.body() holds the signing-key JSON: { "keyId": ..., "secretBase64": ..., ... }
day := "2026-04-30" // UTC day of the transactions you are about to sign
req, _ := http.NewRequest("GET",
"https://api.regulaview.io/v1/integrity/manifests/"+day+"/signing-key", nil)
req.Header.Set("Authorization", "Bearer "+token)
res, err := http.DefaultClient.Do(req)
// decode res.Body into your SigningKey struct
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}
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 type | Signing value | Common product | Signing value |
|---|---|---|---|
Wager | 0 | OnlineCasino | 0 |
Payout | 1 | PhysicalCasino | 1 |
Adjustment | 2 | RetailGambling | 2 |
Void | 3 | SportsBetting | 3 |
Refund | 4 | Lotto | 5 |
Deposit | 5 | GamingMachine | 9 |
Withdrawal | 6 | Other | 99 |
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");
}
using System;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
// type and product use the integer codes from the signing-value table above.
static string SignTransaction(
string keyId, string secretBase64, string deviceExternalId, string externalId,
int type, int product, decimal amount, string currency,
DateTimeOffset occurredAtUtc, string chainReference, string correlationId)
{
var canonical = string.Join("|",
keyId,
deviceExternalId ?? "",
externalId,
type,
product,
amount.ToString("F4", CultureInfo.InvariantCulture),
currency,
occurredAtUtc.ToUnixTimeMilliseconds(),
chainReference ?? "",
correlationId ?? "");
using var hmac = new HMACSHA256(Convert.FromBase64String(secretBase64));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(canonical));
return Convert.ToHexString(hash).ToLowerInvariant();
}
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
// type and product use the integer codes from the signing-value table above.
static String signTransaction(
String keyId, String secretBase64, String deviceExternalId, String externalId,
int type, int product, BigDecimal amount, String currency,
Instant occurredAtUtc, String chainReference, String correlationId) throws Exception {
String canonical = String.join("|",
keyId,
deviceExternalId == null ? "" : deviceExternalId,
externalId,
Integer.toString(type),
Integer.toString(product),
amount.setScale(4, RoundingMode.HALF_UP).toPlainString(),
currency,
Long.toString(occurredAtUtc.toEpochMilli()),
chainReference == null ? "" : chainReference,
correlationId == null ? "" : correlationId);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(Base64.getDecoder().decode(secretBase64), "HmacSHA256"));
byte[] hash = mac.doFinal(canonical.getBytes(StandardCharsets.UTF_8));
StringBuilder hex = new StringBuilder(hash.length * 2);
for (byte b : hash) hex.append(String.format("%02x", b));
return hex.toString();
}
package signing
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"strconv"
"strings"
"time"
)
// typeCode and productCode use the integer codes from the signing-value table above.
func SignTransaction(
keyID, secretBase64, deviceExternalID, externalID string,
typeCode, productCode int, amount float64, currency string,
occurredAtUtc time.Time, chainReference, correlationID string,
) (string, error) {
secret, err := base64.StdEncoding.DecodeString(secretBase64)
if err != nil {
return "", err
}
canonical := strings.Join([]string{
keyID,
deviceExternalID,
externalID,
strconv.Itoa(typeCode),
strconv.Itoa(productCode),
strconv.FormatFloat(amount, 'f', 4, 64),
currency,
strconv.FormatInt(occurredAtUtc.UnixMilli(), 10),
chainReference,
correlationID,
}, "|")
mac := hmac.New(sha256.New, secret)
mac.Write([]byte(canonical))
return hex.EncodeToString(mac.Sum(nil)), nil
}
Ingestion
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();
using var http = new HttpClient();
var day = tx.OccurredAtUtc.ToString("yyyy-MM-dd"); // UTC day of the transaction
using var keyReq = new HttpRequestMessage(HttpMethod.Get,
$"https://api.regulaview.io/v1/integrity/manifests/{day}/signing-key");
keyReq.Headers.Authorization = new("Bearer", token);
var key = await (await http.SendAsync(keyReq)).Content.ReadFromJsonAsync<SigningKey>();
using var req = new HttpRequestMessage(HttpMethod.Post, "https://api.regulaview.io/v1/ingest/transactions");
req.Headers.Authorization = new("Bearer", token);
req.Headers.Add("X-RegulaView-Signature", SignTransaction(key, tx)); // from Transaction signing
req.Headers.Add("Idempotency-Key", tx.ExternalId);
req.Content = JsonContent.Create(tx);
var res = await http.SendAsync(req);
res.EnsureSuccessStatusCode();
HttpClient http = HttpClient.newHttpClient();
ObjectMapper mapper = new ObjectMapper(); // Java has no built-in JSON; e.g. Jackson
String day = tx.occurredAtUtc.substring(0, 10); // UTC day of the transaction
HttpResponse<String> keyRes = http.send(
HttpRequest.newBuilder(URI.create(
"https://api.regulaview.io/v1/integrity/manifests/" + day + "/signing-key"))
.header("Authorization", "Bearer " + token).GET().build(),
HttpResponse.BodyHandlers.ofString());
SigningKey key = mapper.readValue(keyRes.body(), SigningKey.class);
HttpResponse<String> res = http.send(
HttpRequest.newBuilder(URI.create("https://api.regulaview.io/v1/ingest/transactions"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.header("X-RegulaView-Signature", signTransaction(key, tx)) // from Transaction signing
.header("Idempotency-Key", tx.externalId)
.POST(HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(tx))).build(),
HttpResponse.BodyHandlers.ofString());
day := tx.OccurredAtUtc.UTC().Format("2006-01-02") // UTC day of the transaction
keyReq, _ := http.NewRequest("GET",
"https://api.regulaview.io/v1/integrity/manifests/"+day+"/signing-key", nil)
keyReq.Header.Set("Authorization", "Bearer "+token)
keyRes, _ := http.DefaultClient.Do(keyReq)
var key SigningKey
json.NewDecoder(keyRes.Body).Decode(&key)
sig, _ := SignTransaction(key, tx) // from Transaction signing
txJSON, _ := json.Marshal(tx)
req, _ := http.NewRequest("POST", "https://api.regulaview.io/v1/ingest/transactions", bytes.NewReader(txJSON))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-RegulaView-Signature", sig)
req.Header.Set("Idempotency-Key", tx.ExternalID)
res, err := http.DefaultClient.Do(req)
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).
| Field | Required | Rules |
|---|---|---|
externalId | Yes | Unique per operator for one immutable transaction event. Maximum 64 characters. |
sequence | Yes | Per-operator, strictly increasing, gap-free 64-bit number. Drives deduplication and gap detection. A missing sequence is rejected. |
deviceExternalId | No | Recommended for events from a device, terminal, outlet server, or gaming system. Maximum 128 characters. |
type | Yes | Wager, Payout, Adjustment, Void, Refund, Deposit, or Withdrawal. |
amount | Yes | Non-negative decimal. Economic sign is derived from type. |
currency | Yes | ISO 4217 three-letter code. |
occurredAtUtc | Yes | UTC event time from the operator system. |
product | No | Defaults to Other if omitted. |
chainReference | No | Recommended link between lifecycle events, such as wager to payout or void. |
Sequence & deduplication
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:
Every transaction must carry a sequence. A missing sequence returns 400 missing_sequence, because an absent sequence would let a gap go undetected.
Re-sending a transaction with the same sequence returns 202 Accepted with status = "duplicate". Keep the sequence stable across retries.
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
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();
// key fetched as in "Retrieve the daily signing key"; sign each item individually
var items = transactions
.Select(tx => tx with { IdempotencyKey = tx.ExternalId, Signature = SignTransaction(key, tx) })
.ToArray();
using var http = new HttpClient();
using var req = new HttpRequestMessage(HttpMethod.Post, "https://api.regulaview.io/v1/ingest/transactions/batch");
req.Headers.Authorization = new("Bearer", token);
req.Content = JsonContent.Create(new { transactions = items });
var res = await http.SendAsync(req);
res.EnsureSuccessStatusCode();
var result = await res.Content.ReadFromJsonAsync<BatchResult>();
// key fetched as in "Retrieve the daily signing key"; sign each item individually
for (Tx tx : transactions) {
tx.idempotencyKey = tx.externalId;
tx.signature = signTransaction(key, tx); // from Transaction signing
}
String body = mapper.writeValueAsString(Map.of("transactions", transactions));
HttpResponse<String> res = HttpClient.newHttpClient().send(
HttpRequest.newBuilder(URI.create("https://api.regulaview.io/v1/ingest/transactions/batch"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body)).build(),
HttpResponse.BodyHandlers.ofString());
// key fetched as in "Retrieve the daily signing key"; sign each item individually
for i := range transactions {
transactions[i].IdempotencyKey = transactions[i].ExternalID
transactions[i].Signature, _ = SignTransaction(key, transactions[i])
}
body, _ := json.Marshal(map[string]any{"transactions": transactions})
req, _ := http.NewRequest("POST", "https://api.regulaview.io/v1/ingest/transactions/batch", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
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
}
]
}
An item whose sequence was already folded returns a duplicate receipt.
Each item reports accepted, duplicate, or rejected (for example missing_sequence) in the results array.
Retry transient 5xx, 429, and timeouts with jitter, resending each item with its original sequence.
Daily tax declarations
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();
using var http = new HttpClient();
using var req = new HttpRequestMessage(HttpMethod.Post, "https://api.regulaview.io/v1/tax/declarations");
req.Headers.Authorization = new("Bearer", token);
req.Content = JsonContent.Create(new { day = "2026-04-30", grossWagers = 125000.00m, payouts = 91000.00m, declaredTax = 5100.00m });
var res = await http.SendAsync(req);
res.EnsureSuccessStatusCode();
var summary = await res.Content.ReadFromJsonAsync<TaxSummary>();
String body = "{\"day\":\"2026-04-30\",\"grossWagers\":125000.00,\"payouts\":91000.00,\"declaredTax\":5100.00}";
HttpResponse<String> res = HttpClient.newHttpClient().send(
HttpRequest.newBuilder(URI.create("https://api.regulaview.io/v1/tax/declarations"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body)).build(),
HttpResponse.BodyHandlers.ofString());
body := []byte(`{"day":"2026-04-30","grossWagers":125000.00,"payouts":91000.00,"declaredTax":5100.00}`)
req, _ := http.NewRequest("POST", "https://api.regulaview.io/v1/tax/declarations", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
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
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();
using var http = new HttpClient();
using var req = new HttpRequestMessage(HttpMethod.Post, "https://api.regulaview.io/v1/recon/cashier-totals");
req.Headers.Authorization = new("Bearer", token);
req.Content = JsonContent.Create(new { day = "2026-04-30", cashIn = 48000.00m, cashOut = 36500.00m, outletReference = "SHOP-014", source = "cashier-close-v2" });
var res = await http.SendAsync(req);
res.EnsureSuccessStatusCode();
var stored = await res.Content.ReadFromJsonAsync<CashierTotals>();
String body = "{\"day\":\"2026-04-30\",\"cashIn\":48000.00,\"cashOut\":36500.00,\"outletReference\":\"SHOP-014\",\"source\":\"cashier-close-v2\"}";
HttpResponse<String> res = HttpClient.newHttpClient().send(
HttpRequest.newBuilder(URI.create("https://api.regulaview.io/v1/recon/cashier-totals"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body)).build(),
HttpResponse.BodyHandlers.ofString());
body := []byte(`{"day":"2026-04-30","cashIn":48000.00,"cashOut":36500.00,"outletReference":"SHOP-014","source":"cashier-close-v2"}`)
req, _ := http.NewRequest("POST", "https://api.regulaview.io/v1/recon/cashier-totals", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
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
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" });
var state = await Api<HeartbeatState>(HttpMethod.Post, $"/v1/devices/external/{deviceExternalId}/heartbeat",
new { configurationVersion = "2026.04.30.1", configurationHash = "b6e4f8b7266f4d2e998c7f2a0f1be75c", gameEnabled = true, health = "Healthy" });
HttpResponse<String> state = api("POST", "/v1/devices/external/" + deviceExternalId + "/heartbeat", toJson(heartbeat), null);
body, _ := json.Marshal(heartbeat)
state, err := api("POST", "/v1/devices/external/"+deviceExternalID+"/heartbeat", body, nil)
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"
});
var ev = await Api<RgEventAck>(HttpMethod.Post, "/v1/rg/events", new {
type = "LimitBreach", severity = "High", deviceExternalId = "terminal-alpha-07",
pseudonymousPlayerRef = "player-hash-91b2", summary = "Daily loss threshold breached",
evidence = "loss=500;threshold=500;currency=EUR"
});
HttpResponse<String> ev = api("POST", "/v1/rg/events", toJson(rgEvent), null);
body, _ := json.Marshal(rgEvent)
ev, err := api("POST", "/v1/rg/events", body, nil)
Response 202 Accepted — the recorded event id:
{
"id": "e3b8a1d6-4c70-4f92-9a51-8b2d6f0c34e7"
}
Corrections
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." }]
});
var correction = await Api<CorrectionAck>(HttpMethod.Post, "/v1/corrections", new {
originalExternalId = "OP-20260430-000001",
reason = "Operator settlement system corrected payout amount after manual review.",
payloadDiff = new[] { new { path = "/amount", from = 5.00m, to = 4.50m, reason = "Manual settlement review corrected the amount." } }
});
HttpResponse<String> correction = api("POST", "/v1/corrections", toJson(request), null);
body, _ := json.Marshal(request)
correction, err := api("POST", "/v1/corrections", body, nil)
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
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();
// key fetched as in "Retrieve the daily signing key"
var canonical = string.Join("|",
key.KeyId, report.Day, report.Currency,
report.DeclaredCount, report.DeclaredFirstSequence, report.DeclaredLastSequence,
report.DeclaredWageredAmount.ToString("F4", CultureInfo.InvariantCulture),
report.DeclaredPaidOutAmount.ToString("F4", CultureInfo.InvariantCulture));
using var hmac = new HMACSHA256(Convert.FromBase64String(key.SecretBase64));
report.Signature = Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes(canonical))).ToLowerInvariant();
using var http = new HttpClient();
using var req = new HttpRequestMessage(HttpMethod.Post, "https://api.regulaview.io/v1/ingest/end-of-day");
req.Headers.Authorization = new("Bearer", token);
req.Content = JsonContent.Create(report);
var res = await http.SendAsync(req);
res.EnsureSuccessStatusCode();
var receipt = await res.Content.ReadFromJsonAsync<EodReceipt>();
// key fetched as in "Retrieve the daily signing key"
String canonical = String.join("|",
key.keyId, report.day, report.currency,
Long.toString(report.declaredCount),
Long.toString(report.declaredFirstSequence),
Long.toString(report.declaredLastSequence),
report.declaredWageredAmount.setScale(4, RoundingMode.HALF_UP).toPlainString(),
report.declaredPaidOutAmount.setScale(4, RoundingMode.HALF_UP).toPlainString());
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(Base64.getDecoder().decode(key.secretBase64), "HmacSHA256"));
report.signature = HexFormat.of().formatHex(mac.doFinal(canonical.getBytes(StandardCharsets.UTF_8)));
HttpResponse<String> res = HttpClient.newHttpClient().send(
HttpRequest.newBuilder(URI.create("https://api.regulaview.io/v1/ingest/end-of-day"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(report))).build(),
HttpResponse.BodyHandlers.ofString());
// key fetched as in "Retrieve the daily signing key"
canonical := strings.Join([]string{
key.KeyID, report.Day, report.Currency,
strconv.FormatInt(report.DeclaredCount, 10),
strconv.FormatInt(report.DeclaredFirstSequence, 10),
strconv.FormatInt(report.DeclaredLastSequence, 10),
strconv.FormatFloat(report.DeclaredWageredAmount, 'f', 4, 64),
strconv.FormatFloat(report.DeclaredPaidOutAmount, 'f', 4, 64),
}, "|")
secret, _ := base64.StdEncoding.DecodeString(key.SecretBase64)
m := hmac.New(sha256.New, secret)
m.Write([]byte(canonical))
report.Signature = hex.EncodeToString(m.Sum(nil))
body, _ := json.Marshal(report)
req, _ := http.NewRequest("POST", "https://api.regulaview.io/v1/ingest/end-of-day", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
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");
var outcomes = await Api<ReconOutcome[]>(HttpMethod.Get, "/v1/reconciliation?from=2026-04-01&to=2026-04-30");
HttpResponse<String> outcomes = api("GET", "/v1/reconciliation?from=2026-04-01&to=2026-04-30", null, null);
outcomes, err := api("GET", "/v1/reconciliation?from=2026-04-01&to=2026-04-30", nil, nil)
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"
}
]
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
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");
var manifest = await Api<Manifest>(HttpMethod.Get, "/v1/integrity/aggregates/manifest?day=2026-04-30");
HttpResponse<String> manifest = api("GET", "/v1/integrity/aggregates/manifest?day=2026-04-30", null, null);
manifest, err := api("GET", "/v1/integrity/aggregates/manifest?day=2026-04-30", nil, nil)
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}`);
var proof = await Api<InclusionProof>(HttpMethod.Get, $"/v1/integrity/aggregates/proof?bucketId={bucketId}");
HttpResponse<String> proof = api("GET", "/v1/integrity/aggregates/proof?bucketId=" + bucketId, null, null);
proof, err := api("GET", "/v1/integrity/aggregates/proof?bucketId="+bucketID, nil, nil)
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");
var result = await Api<ChainVerification>(HttpMethod.Get, "/v1/integrity/aggregates/verify-chain?day=2026-04-30");
HttpResponse<String> result = api("GET", "/v1/integrity/aggregates/verify-chain?day=2026-04-30", null, null);
result, err := api("GET", "/v1/integrity/aggregates/verify-chain?day=2026-04-30", nil, nil)
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
/v1/auth/tokenIssue an access token.
/v1/operator-onboarding/meRead authenticated operator profile details.
/v1/integrity/manifests/{day}/signing-keyRetrieve the daily HMAC key.
/v1/ingest/transactionsSubmit one signed transaction.
/v1/ingest/transactions/batchSubmit up to 5000 signed transactions.
/v1/ingest/end-of-daySubmit your signed end-of-day report.
/v1/reconciliationRead end-of-day reconciliation outcomes.
/v1/reports/aggregates/summaryRead aggregate totals for a window.
/v1/reports/aggregates/seriesRead resolution-aware aggregate windows.
/v1/reports/aggregates/gapsRead detected sequence gaps.
/v1/devicesRead mapped operator devices.
/v1/devices/external/{deviceExternalId}/heartbeatReport device liveness.
/v1/tax/declarationsSubmit daily aggregate tax details.
/v1/taxRead tax summaries and variance.
/v1/recon/cashier-totalsSubmit independent cashier totals.
/v1/correctionsAppend an after-the-fact correction request.
/v1/rg/eventsSubmit responsible-gaming events.
/v1/integrity/aggregates/manifestRetrieve the daily aggregate Merkle manifest.
/v1/integrity/aggregates/proofInclusion proof for one sealed window.
/v1/integrity/aggregates/verify-chainRe-walk and verify the seal chain.
Errors
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
}
| HTTP | Code | Meaning |
|---|---|---|
| 400 | missing_sequence | The transaction has no per-operator sequence. |
| 400 | signature_required | Transaction signing is required in this environment. |
| 400 | signature_source_mismatch | Header and body signatures are both present but differ. |
| 400 | signature_invalid | The calculated transaction or end-of-day HMAC did not validate. |
| 400 | no_signing_key | No daily key has been issued for the transaction day. |
| 422 | timestamp_out_of_range | The transaction timestamp is outside the acceptance window. |
| 429 | rate_limited | The 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
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.
| Policy | Sandbox default | Partition | Applies to |
|---|---|---|---|
token | 60 requests/minute | Source IP | Access-token issuance. |
ingest | 6000 requests/minute | Authenticated operator identity | Transactions, batches, heartbeats, corrections, recon, signing-key retrieval, and end-of-day reports. |
read | 600 requests/minute | Authenticated operator identity | Operator-scoped queries and integrity reads. |
anonymous | 120 requests/minute | Source IP | Unauthenticated status and public policy reads. |
Certification checklist
sequence.sequence) returns duplicate receipts and is treated as success.sequence returns 400 missing_sequence.Matched reconciliation outcome.verify-chain result.OpenAPI
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.