API Documentation
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.
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.
Key format
API keys follow the format tnl_ followed by 64 hex characters. Example:
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 -H "X-Operator-ID: operator-1" https://lite.tracenode.co/api/stats
Quick Start Guide
The full dispute-to-report workflow in three steps.
/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)./api/compliance-review to see disputes requiring human review. Each entry shows elapsed SLA time (green → amber → red over 24 hours)./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.
Headers
| Header | Value | Note |
|---|---|---|
| Authorization | Bearer <api_key> | Required. Scopes to your operator. |
| Content-Type | application/json | Required. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| dispute_id | string | required | Your system's unique dispute identifier. Duplicates are rejected with 409. |
| event_type | string | required | Dispute category. Options: self_exclusion_breach, bonus_abuse, payment_dispute, account_closure, general_complaint, responsible_gambling, unfair_terms, other |
| claim_amount | number | optional | Player's claimed amount in major units (e.g. 250.00 for £250). Non-negative. Default: null. |
| currency | string | optional | 3-letter ISO 4217 code. Default: GBP. |
| player_jurisdiction | string | optional | 2-letter country code (e.g. GB, MT). |
| player_id | string | optional | Your internal player identifier. |
| description | string | optional | Free-text description of the dispute. |
| evidence_url | string | optional | URL pointing to evidence (screenshot, logs, etc.). |
Example request
{
"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
{
"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"
}
}
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.
Path parameters
| Parameter | Type | Description |
|---|---|---|
| disputeId | string | The dispute identifier as submitted in dispute_id. |
Response body
{
"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+).
Response body
{
"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.
Path parameters
| Parameter | Type | Description |
|---|---|---|
| id | string | The dispute's dispute_id (your system's ID, not the internal DB ID). |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| reviewer_id | string | required | Identifier for the reviewer making the decision. Can also be sent as arbiter_id or X-Reviewer-ID header. |
| decision | string | required | One of: APPROVE, REJECT, ESCALATE. |
| reasoning | string | required | Written explanation for the decision. Minimum 20 characters. Stored in audit trail. |
Example request
{
"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
{
"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.
Path parameters
| Parameter | Type | Description |
|---|---|---|
| year | integer | 4-digit year (e.g. 2026). |
| month | integer | Month number 1–12 (e.g. 4 for April). |
Query parameters
| Parameter | Type | Description |
|---|---|---|
| format | string | Pass pdf to receive a UKGC-formatted PDF report. Content-Disposition: attachment. |
Example request
curl -H "Authorization: Bearer tnl_..." \ https://lite.tracenode.co/api/reports/compliance/2026/4
Response structure
{
"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.
Response structure
{
"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).
Response body
{
"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"
}
]
}
eq, neq, gt, gte, lt, lte, contains, inPOST /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.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| rule_name | string | required | Unique name for this rule. Duplicate names within the same operator are rejected. |
| priority | integer | required | Evaluation order. Lower numbers run first. Common values: 1–100 for operator rules. |
| conditions | array | required | Array of condition objects. Each requires field, operator, and value. All conditions must match for the rule to fire (AND logic). |
| outcome | string | required | One of: HARD_ACCEPT (auto-approve), HARD_REJECT (auto-reject), REQUIRES_REVIEW (human decision needed). |
Example request
{
"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
{
"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 -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"
}'
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 https://lite.tracenode.co/api/compliance-review \ -H "Authorization: Bearer tnl_your_api_key_here"
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 -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."
}'
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 -o april-2026-compliance.pdf \ https://lite.tracenode.co/api/reports/compliance/2026/4?format=pdf \ -H "Authorization: Bearer tnl_your_api_key_here"
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 https://lite.tracenode.co/api/b1-check
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 -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:
// HTTP 4xx
{ "success": false, "message": "Human-readable error description" }
// HTTP 400 with validation errors
{ "success": false, "errors": ["field is required", "invalid format"] }
| HTTP Status | Error / Message | Cause & Fix |
|---|---|---|
| 400 | Validation errorserrors: [...] | Request body missing or has invalid fields. Check required fields and format (e.g. currency must be 3-letter ISO code like GBP). |
| 401 | Invalid or revoked API key | The API key passed in the Authorization header is not valid or has been revoked. Generate a new key via the operator management API. |
| 401 | Authorization header required | No Authorization: Bearer header was provided. All endpoints require it. |
| 404 | Dispute not found | The dispute ID does not exist, or belongs to a different operator. Verify you are using the correct dispute_id value. |
| 404 | Operator not found | The operator referenced when generating a key does not exist. |
| 409 | Dispute ID already exists | A dispute with this dispute_id was already submitted. Use a unique ID or check your deduplication logic. |
| 409 | Decision already recorded | A reviewer decision has already been submitted for this dispute. Each dispute can only be decided once. |
| 409 | Rule with this name already exists | A rule with that name already exists for this operator. Use a unique rule_name. |
| 400 | Dispute is not in review | The decision endpoint was called on a dispute that is not in IN_REVIEW status. Only in-review disputes can receive decisions. |
| 500 | Internal server error | Unexpected server-side error. Check system logs. If persistent, contact support. |
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.