API Documentation

v1.0 · UKGC 6.1.1

TraceNode Lite API

RESTful API for automated player dispute compliance management under UKGC Social Responsibility Code 6.1.1. Integrates dispute intake, automated rules routing, compliance review queue, and monthly regulatory reporting.

Base URL https://lite.tracenode.co

Authentication

Every API request must include an API key via the Authorization header. Keys are scoped to a single operator and grant access to that operator's disputes only.

HEADER Authorization: Bearer <your_api_key>
Getting your API key: API keys are generated via the operator management endpoints. Store the raw key securely on first creation — it cannot be retrieved again. Only the SHA-256 hash is stored server-side.

Key format

API keys follow the format tnl_ followed by 64 hex characters. Example:

key format
tnl_a1b2c3d4e5f6... (total 68 characters: tnl_ + 64 hex)

Operator isolation

Every API key is bound to a specific operator. Requests using an operator's key will only ever access that operator's disputes. Cross-operator access is architecturally impossible — all queries include an operator_id filter at the SQL layer.

Dev / local testing

When NODE_ENV is not production, you may use the X-Operator-ID header as a fallback to bypass key auth for local testing:

curl (dev only)
curl -H "X-Operator-ID: operator-1" https://lite.tracenode.co/api/stats

Quick Start Guide

The full dispute-to-report workflow in three steps.

1
Submit a Dispute
POST to /disputes/submit with player dispute data. The system hashes it SHA-3-256, runs it through your rules, and returns a status (auto-resolved or queued for review).
2
Check the Review Queue
GET /api/compliance-review to see disputes requiring human review. Each entry shows elapsed SLA time (green → amber → red over 24 hours).
3
Download Compliance Report
GET /api/reports/compliance/{year}/{month} (JSON or ?format=pdf) for the monthly UKGC 6.1.1 report. Includes SLA rates, hash integrity checks, and rules activity.

Endpoints Reference

POST /disputes/submit

Submit a new player dispute. Validates required fields, generates a SHA-3-256 integrity hash, runs it through the rules engine, and returns the resolution status.

POST /disputes/submit

Headers

HeaderValueNote
AuthorizationBearer <api_key>Required. Scopes to your operator.
Content-Typeapplication/jsonRequired.

Request body

FieldTypeRequiredDescription
dispute_idstringrequiredYour system's unique dispute identifier. Duplicates are rejected with 409.
event_typestringrequiredDispute category. Options: self_exclusion_breach, bonus_abuse, payment_dispute, account_closure, general_complaint, responsible_gambling, unfair_terms, other
claim_amountnumberoptionalPlayer's claimed amount in major units (e.g. 250.00 for £250). Non-negative. Default: null.
currencystringoptional3-letter ISO 4217 code. Default: GBP.
player_jurisdictionstringoptional2-letter country code (e.g. GB, MT).
player_idstringoptionalYour internal player identifier.
descriptionstringoptionalFree-text description of the dispute.
evidence_urlstringoptionalURL pointing to evidence (screenshot, logs, etc.).

Example request

JSON body
{
  "dispute_id": "DSP-2026-0416-001",
  "event_type": "self_exclusion_breach",
  "claim_amount": 1500,
  "currency": "GBP",
  "player_jurisdiction": "GB",
  "player_id": "PLR-88234",
  "description": "Player claims account was active for 3 weeks after self-exclusion request was submitted.",
  "evidence_url": "https://operator.internal/logs/plr-88234-2026"
}

Example response

JSON response
{
  "success": true,
  "entry_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "status": "RESOLVED",
  "tracking_url": "/disputes/DSP-2026-0416-001",
  "dispute": {
    "id": 42,
    "dispute_id": "DSP-2026-0416-001",
    "status": "RESOLVED",
    "resolution": "APPROVED",
    "rule_applied": "self_exclusion_auto_approve",
    "created_at": "2026-04-16T13:56:08.000Z"
  }
}
Possible status values:   PENDING   IN_REVIEW   RESOLVED   ESCALATED

GET /disputes/:disputeId

Retrieve a single dispute by its ID, including the full audit trail and reviewer decision. Results are scoped to the authenticated operator.

GET /disputes/:disputeId

Path parameters

ParameterTypeDescription
disputeIdstringThe dispute identifier as submitted in dispute_id.

Response body

JSON response
{
  "success": true,
  "dispute": {
    "id": 42,
    "dispute_id": "DSP-2026-0416-001",
    "operator_id": "operator-1",
    "event_type": "self_exclusion_breach",
    "claim_amount": 1500,
    "currency": "GBP",
    "player_jurisdiction": "GB",
    "player_id": "PLR-88234",
    "description": "Player claims account was active...",
    "entry_hash": "e3b0c44298fc1c149afbf...",
    "status": "RESOLVED",
    "resolution": "APPROVED",
    "resolved_at": "2026-04-16T13:56:10.000Z",
    "created_at": "2026-04-16T13:56:08.000Z",
    "audit_trail": [
      { "action": "DISPUTE_RECEIVED", "actor": "system", "created_at": "..." },
      { "action": "RULES_EVALUATED",  "actor": "rules_engine", "created_at": "..." },
      { "action": "AUTO_RESOLVED",    "actor": "rules_engine", "created_at": "..." }
    ],
    "decision": null
  }
}

GET /api/compliance-review

Returns all disputes in the compliance review queue (IN_REVIEW status). Ordered oldest-first so the most urgent SLA cases appear first. SLA status is colour-coded: green (0–12h), amber (12–20h), red (20h+).

GET /api/compliance-review

Response body

JSON response
{
  "success": true,
  "queue": [
    {
      "id": 42,
      "dispute_id": "DSP-2026-0416-001",
      "event_type": "general_complaint",
      "claim_amount": 250,
      "currency": "GBP",
      "player_jurisdiction": "GB",
      "status": "IN_REVIEW",
      "created_at": "2026-04-16T13:00:00.000Z",
      "elapsed_hours": 14.5,
      "sla_status": "amber",
      "sla_deadline_at": "2026-04-17T13:00:00.000Z"
    }
  ],
  "count": 1
}

POST /api/compliance-review/:id/decision

Submit a human reviewer's compliance decision for an in-review dispute. The dispute must be in IN_REVIEW status. A written reasoning of at least 20 characters is mandatory.

POST /api/compliance-review/:id/decision

Path parameters

ParameterTypeDescription
idstringThe dispute's dispute_id (your system's ID, not the internal DB ID).

Request body

FieldTypeRequiredDescription
reviewer_idstringrequiredIdentifier for the reviewer making the decision. Can also be sent as arbiter_id or X-Reviewer-ID header.
decisionstringrequiredOne of: APPROVE, REJECT, ESCALATE.
reasoningstringrequiredWritten explanation for the decision. Minimum 20 characters. Stored in audit trail.

Example request

JSON body
{
  "reviewer_id": "compliance-officer-01",
  "decision": "APPROVE",
  "reasoning": "Player provided valid evidence of account activity after self-exclusion was processed. UKGC guidance supports full refund in this case."
}

Example response

JSON response
{
  "success": true,
  "dispute_id": "DSP-2026-0416-001",
  "decision": "APPROVE",
  "status": "RESOLVED",
  "resolution": "APPROVED"
}

GET /api/reports/compliance/:year/:month

Returns the monthly UKGC Code 6.1.1 compliance report for the authenticated operator. Includes dispute statistics, SLA compliance rates, hash verification, and rules engine activity. Append ?format=pdf to receive a downloadable PDF report.

GET /api/reports/compliance/:year/:month

Path parameters

ParameterTypeDescription
yearinteger4-digit year (e.g. 2026).
monthintegerMonth number 1–12 (e.g. 4 for April).

Query parameters

ParameterTypeDescription
formatstringPass pdf to receive a UKGC-formatted PDF report. Content-Disposition: attachment.

Example request

curl
curl -H "Authorization: Bearer tnl_..." \
  https://lite.tracenode.co/api/reports/compliance/2026/4

Response structure

JSON response
{
  "success": true,
  "report": {
    "title": "UKGC Code 6.1.1 Compliance Report \u2014 April 2026",
    "operator_id": "acme-casino",
    "period": { "year": 2026, "month": 4, "month_name": "April", ... },
    "generated_at": "2026-04-16T13:56:08.000Z",
    "dispute_overview": {
      "total_submitted": 142,
      "by_status": { "resolved": 128, "in_review": 8, "escalated": 2, "pending": 4 },
      "by_resolution": { "approved": 98, "rejected": 30 },
      "avg_resolution_hours": 18.4
    },
    "sla_compliance": {
      "target_hours": 24,
      "resolved_within_target": 119,
      "total_resolved": 128,
      "compliance_rate_pct": 92.97,
      "breached_sla": 9,
      "open_past_sla": 3,
      "longest_duration_hours": 28.1
    },
    "hash_verification": {
      "algorithm": "SHA-3-256",
      "total_disputes_hashed": 142,
      "missing_or_invalid_hashes": 0,
      "chain_integrity_issues": 0,
      "all_chains_valid": true
    },
    "rules_engine": {
      "rules_triggered": [
        { "rule_name": "self_exclusion_auto_approve", "outcome": "HARD_ACCEPT", "trigger_count": 31 },
        { "rule_name": "bonus_abuse_auto_reject",     "outcome": "HARD_REJECT", "trigger_count": 22 }
      ],
      "auto_resolved": 87,
      "human_reviewed": 41,
      "total_decisions": 128,
      "auto_resolve_rate_pct": 67.97
    }
  }
}

GET /api/b1-check

Run all 5 B1 integrity checks against the system. Returns a full report of passed/failed assertions covering hash determinism, append-only enforcement, rules engine routing, corpus record writes, and audit log entries. No authentication required.

GET /api/b1-check
Use this endpoint to programmatically verify system integrity before regulatory audits. All 32 checks must pass for a full B1 pass.

Response structure

JSON response
{
  "success": true,
  "all_passed": true,
  "summary": { "passed": 32, "failed": 0, "total": 32 },
  "sections": [
    { "code": "B1.1", "status": "PASS", "passed": 5, "total": 5,
      "checks": [{ "section": "B1.1", "label": "Hash is deterministic...", "ok": true }] },
    { "code": "B1.2", "status": "PASS", "passed": 4, "total": 4,
      "checks": [{ "section": "B1.2", "label": "UPDATE on audit_log blocked...", "ok": true }] },
    ...
  ],
  "run_id": 1744811768000,
  "verified_at": "2026-04-16T13:56:08.000Z"
}

GET /api/rules

List all active rules for the authenticated operator, including operator-specific rules and system-wide default rules. Rules are returned in priority order (lowest number = evaluated first).

GET /api/rules

Response body

JSON response
{
  "success": true,
  "rules": [
    {
      "id": 1,
      "operator_id": "acme-casino",
      "rule_name": "self_exclusion_auto_approve",
      "priority": 1,
      "conditions": [{ "field": "event_type", "operator": "eq", "value": "self_exclusion_breach" }],
      "outcome": "HARD_ACCEPT",
      "active": true,
      "created_at": "2026-04-10T09:00:00.000Z"
    },
    {
      "id": 99,
      "operator_id": "DEFAULT",
      "rule_name": "default_fallback",
      "priority": 9999,
      "conditions": [],
      "outcome": "REQUIRES_REVIEW",
      "active": true,
      "created_at": "2026-01-01T00:00:00.000Z"
    }
  ]
}
Condition operators: eq, neq, gt, gte, lt, lte, contains, in

POST /api/rules

Create a custom dispute resolution rule scoped to the authenticated operator. Rules are evaluated in priority order; the first matching rule determines the outcome.

POST /api/rules

Request body

FieldTypeRequiredDescription
rule_namestringrequiredUnique name for this rule. Duplicate names within the same operator are rejected.
priorityintegerrequiredEvaluation order. Lower numbers run first. Common values: 1–100 for operator rules.
conditionsarrayrequiredArray of condition objects. Each requires field, operator, and value. All conditions must match for the rule to fire (AND logic).
outcomestringrequiredOne of: HARD_ACCEPT (auto-approve), HARD_REJECT (auto-reject), REQUIRES_REVIEW (human decision needed).

Example request

JSON body
{
  "rule_name": "low_value_bonus_abuse_reject",
  "priority": 10,
  "conditions": [
    { "field": "event_type",  "operator": "eq",   "value": "bonus_abuse" },
    { "field": "claim_amount", "operator": "lte",  "value": 100 }
  ],
  "outcome": "HARD_REJECT"
}

Example response

JSON response
{
  "success": true,
  "rule": {
    "id": 42,
    "operator_id": "acme-casino",
    "rule_name": "low_value_bonus_abuse_reject",
    "priority": 10,
    "conditions": [{ "field": "event_type", "operator": "eq", "value": "bonus_abuse" }, { "field": "claim_amount", "operator": "lte", "value": 100 }],
    "outcome": "HARD_REJECT",
    "active": true,
    "created_at": "2026-04-16T14:00:00.000Z"
  }
}

Code Examples

Full curl and Node.js fetch examples for each primary endpoint.

Submit a dispute

curl
curl -X POST https://lite.tracenode.co/disputes/submit \
  -H "Authorization: Bearer tnl_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "dispute_id": "DSP-2026-0416-001",
    "event_type": "self_exclusion_breach",
    "claim_amount": 1500,
    "currency": "GBP",
    "player_jurisdiction": "GB",
    "player_id": "PLR-88234"
  }'
Node.js
const response = await fetch('https://lite.tracenode.co/disputes/submit', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer tnl_your_api_key_here',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    dispute_id: 'DSP-2026-0416-001',
    event_type: 'self_exclusion_breach',
    claim_amount: 1500,
    currency: 'GBP',
    player_jurisdiction: 'GB',
    player_id: 'PLR-88234',
  }),
});

const data = await response.json();
console.log(data.status); // 'RESOLVED' or 'IN_REVIEW'

Check the review queue

curl
curl https://lite.tracenode.co/api/compliance-review \
  -H "Authorization: Bearer tnl_your_api_key_here"
Node.js
const response = await fetch('https://lite.tracenode.co/api/compliance-review', {
  headers: { 'Authorization': 'Bearer tnl_your_api_key_here' }
});

const { queue, count } = await response.json();

// Find SLA-critical cases
const critical = queue.filter(d => d.sla_status === 'red');
const warning  = queue.filter(d => d.sla_status === 'amber');
console.log(`${count} cases pending. ${critical.length} critical, ${warning.length} at risk.`);

Submit a reviewer decision

curl
curl -X POST https://lite.tracenode.co/api/compliance-review/DSP-2026-0416-001/decision \
  -H "Authorization: Bearer tnl_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "reviewer_id": "compliance-officer-01",
    "decision": "APPROVE",
    "reasoning": "Player provided valid evidence. UKGC guidance supports full refund."
  }'
Node.js
const response = await fetch(
  'https://lite.tracenode.co/api/compliance-review/DSP-2026-0416-001/decision',
  {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer tnl_your_api_key_here',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      reviewer_id: 'compliance-officer-01',
      decision: 'APPROVE',
      reasoning: 'Player provided valid evidence of account activity after self-exclusion was processed.',
    }),
  }
);

const data = await response.json();
// data.resolution: 'APPROVED', data.status: 'RESOLVED'

Download compliance report (PDF)

curl
curl -o april-2026-compliance.pdf \
  https://lite.tracenode.co/api/reports/compliance/2026/4?format=pdf \
  -H "Authorization: Bearer tnl_your_api_key_here"
Node.js
const response = await fetch(
  'https://lite.tracenode.co/api/reports/compliance/2026/4?format=pdf',
  { headers: { 'Authorization': 'Bearer tnl_your_api_key_here' } }
);

const blob = await response.blob();
const url  = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'compliance-report-2026-04.pdf';
link.click();
URL.revokeObjectURL(url);

Verify system integrity

curl
curl https://lite.tracenode.co/api/b1-check
Node.js
const response = await fetch('https://lite.tracenode.co/api/b1-check');
const { all_passed, summary } = await response.json();

if (all_passed) {
  console.log(`B1 integrity check PASSED. All ${summary.total} checks passed.`);
} else {
  console.error(`B1 integrity check FAILED. ${summary.failed}/${summary.total} checks failed.`);
}

Create a custom rule

curl
curl -X POST https://lite.tracenode.co/api/rules \
  -H "Authorization: Bearer tnl_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "rule_name": "high_value_requires_review",
    "priority": 5,
    "conditions": [
      { "field": "claim_amount", "operator": "gte", "value": 5000 }
    ],
    "outcome": "REQUIRES_REVIEW"
  }'

Error Codes

All error responses follow the format:

error response
// HTTP 4xx
{ "success": false, "message": "Human-readable error description" }

// HTTP 400 with validation errors
{ "success": false, "errors": ["field is required", "invalid format"] }
HTTP StatusError / MessageCause & Fix
400Validation errors
errors: [...]
Request body missing or has invalid fields. Check required fields and format (e.g. currency must be 3-letter ISO code like GBP).
401Invalid or revoked API keyThe API key passed in the Authorization header is not valid or has been revoked. Generate a new key via the operator management API.
401Authorization header requiredNo Authorization: Bearer header was provided. All endpoints require it.
404Dispute not foundThe dispute ID does not exist, or belongs to a different operator. Verify you are using the correct dispute_id value.
404Operator not foundThe operator referenced when generating a key does not exist.
409Dispute ID already existsA dispute with this dispute_id was already submitted. Use a unique ID or check your deduplication logic.
409Decision already recordedA reviewer decision has already been submitted for this dispute. Each dispute can only be decided once.
409Rule with this name already existsA rule with that name already exists for this operator. Use a unique rule_name.
400Dispute is not in reviewThe decision endpoint was called on a dispute that is not in IN_REVIEW status. Only in-review disputes can receive decisions.
500Internal server errorUnexpected server-side error. Check system logs. If persistent, contact support.
UKGC SLA note: Disputes in IN_REVIEW status have a 24-hour resolution target under UKGC Social Responsibility Code 6.1.1. The compliance report endpoint exposes your SLA compliance rate per month — track it to demonstrate regulatory compliance.