Read your surveillance data, push your market data, and mirror alerts into your SIEM or ticketing system. All endpoints are tenant-scoped by API key; dispositions and approvals always happen inside DataTap.
Create keys at Portal → Admin → API keys (shown once, revocable). Send as a bearer token:
curl -H "Authorization: Bearer dtk_..." https://datatap.org/api/v1/cases
Rate limit: 120 requests/minute per key. Unauthorized → 401, throttled → 429.
| Endpoint | Returns |
|---|---|
GET /api/v1/cases | Cases with dispositions, SLA state, WORM timelines, linked accounts/clusters |
GET /api/v1/alerts | Alerts with detection facts, narratives, triage state. Filters: ?since=YYYY-MM-DD, ?pattern=, ?status=; pagination ?limit= (≤500) and ?cursor= from the previous page's next_cursor. Cases take ?type= instead of ?pattern=. |
POST /api/v1/pretrade/check/batch | Up to 200 checks in one call ({"checks": [ ... ]}, each with an optional ref); tenant state loads once, every result carries elapsed_us and over_budget against the 50ms per-check budget. |
POST /api/v1/events · quality gate | A file whose first 2,000-row sample rejects more than ingest_config.max_reject_pct (default 25%) is quarantined: 422 {"quarantined": true, ...}, nothing written, the Idempotency-Key not consumed — fix the mapping and resend with the same key. ingest_config.px_divisor scales prices exported in cents. |
GET /api/v1/packages | Audit packages with hashed manifests, chain hashes, release records |
POST /api/v1/events — JSON-lines body (one object per line, same fields as the
drop-copy schema).
Send an Idempotency-Key header: retries with the same key return the original
validation report without re-ingesting.
curl -X POST https://datatap.org/api/v1/events \
-H "Authorization: Bearer dtk_..." \
-H "Idempotency-Key: batch-2026-09-08-001" \
--data-binary @- <<'EOF'
{"kind":"trade","ts_ms":1200,"market":"CHIEFS-WIN-SB","account":"ACCT-1","side":"buy","px":0.58,"qty":4000}
{"kind":"settlement","ts_ms":86400000,"market":"CHIEFS-WIN-SB","account":"ACCT-1","outcome":"win"}
EOF
Response: {"accepted": n, "rejected": [[line, reason], ...], "markets": [...], "replayed": bool}
— rows are accepted whole or rejected whole; accepted events are append-only (WORM posture).
POST /api/v1/pretrade/check (needs the ingest scope) — JSON body
{"account":"ACCT-1","market":"CHIEFS-WIN-SB","side":"buy","px":0.58,"qty":4000,"yes_px":0.55,"no_px":0.48,"participant":"MM-1"}.
Returns decision (allow/block) and every check with its
result, detail, and regulatory citation: restricted person (account or resolved
cluster), $0.01–$0.99 price bounds, YES+NO parity, cluster position headroom
against the tenant's det_cluster_limit, and participant kill-switch
state. Wire it into your gateway and the controls the portal shows become the
controls your matching engine enforces.
Every route the API serves, from the same table that generates
/api/v1/openapi.json (OpenAPI 3.0, public,
import it into Postman or your gateway). Scope: read for GETs, ingest for POSTs.
| Endpoint | Scope | What it does |
|---|---|---|
GET /api/v1/cases |
read |
Cases with dispositions, SLA state, WORM timelines, linked accounts and clusters |
GET /api/v1/cases/{id} |
read |
One case by id (CASE-/INV- code) with its full timeline, evidence refs and review state |
GET /api/v1/alerts |
read |
Alerts with detection facts, narratives, threshold distance and triage state |
GET /api/v1/alerts/{id} |
read |
One alert by id (ALT- code) including its narrative, facts, recurrence and dismissal |
GET /api/v1/packages |
read |
Audit packages with hashed manifests, chain hashes and release records |
GET /api/v1/audit |
read |
Admin actions and regulator/demo access views since a cursor (SIEM pull) |
GET /api/v1/audit/verify |
read |
Walk the hash-chained admin and access ledgers; reports the first broken row, if any |
POST /api/v1/events |
ingest |
JSON-lines market events (trade/book/settlement/catalyst); Idempotency-Key required; ?dry_run=true validates only |
POST /api/v1/accounts |
ingest |
JSON-lines account/KYC rows: account, identity, funding, device, subnet, address, restricted, state, country |
GET /api/v1/wallets |
read |
Wallet↔account map on file with screening flags |
POST /api/v1/wallets |
ingest |
JSON-lines wallet rows: address, account, chain, flag (clear|mixer-exposure|sanctioned|watch) |
GET /api/v1/participants |
read |
Market-maker participants (desks, scopes, obligations) |
POST /api/v1/participants |
ingest |
Create a participant: code, name, scope, trigger_rule |
GET /api/v1/rlists |
read |
Restricted-list sources with counts and versions |
POST /api/v1/rlists |
ingest |
Upload a restricted list (CSV body) under a named source |
GET /api/v1/markets |
read |
Market schedule (open/close/settle) and correlated groups |
POST /api/v1/markets/schedule |
ingest |
Upsert market schedule rows: {rows: [{market, settle_at, open_at?, close_at?}]} |
POST /api/v1/markets/groups |
ingest |
Replace correlated-market groups: {groups: {name: [markets]}} |
POST /api/v1/uploads/presign |
ingest |
Presigned direct-to-bucket upload URL for large backfills |
POST /api/v1/ingest/from-bucket |
ingest |
Ingest an object already in the drop bucket by key |
POST /api/v1/pretrade/check |
ingest |
One pre-trade check: allow/block with every rule cited |
POST /api/v1/pretrade/check/batch |
ingest |
Up to 200 pre-trade checks in one call; per-check elapsed time and budget flag |
GET /api/v1/pretrade/decisions |
read |
Recorded pre-trade decisions, newest first: rules hit, checks, restricted-list version, latency |
GET /api/v1/openapi.json |
public |
This document |
Register endpoints at Admin → outbound webhooks (https only; signing secret shown once).
Each endpoint subscribes to the events it wants — none selected means every event. Every payload is
{"event", "at", "data"}; Slack-format endpoints get a Block Kit card instead.
Deliveries retry 3× with backoff and are visible (with manual retry) on the Admin screen.
| Event | Fires when · data |
|---|---|
alert.created |
a scan or intraday sweep raised alerts (ids, patterns, subjects, severities) |
case.opened |
a case or investigation was opened (case_id, type, sev, subject, origin) |
case.escalated |
a case was escalated to supervisors (case_id, reason) |
case.disposition |
a disposition was approved (case_id, disposition, approved_by) |
sla.breached |
a 38.156 review clock ran out before disposition (case_id, sla_deadline) |
killswitch.triggered |
a participant kill switch was triggered or re-armed (participant, state, by) |
guardrail.changed |
a control threshold change was approved under four-eyes (rule, old, new, by, approved_by) |
package.released |
an audit package was released (package_id, channel, recipient, approved_by) |
ingest.completed |
an upload or API batch was accepted (source, accepted, rejected, markets, warnings) |
ingest.quarantined |
an upload failed the quality gate and nothing was written (source, rejected, warnings) |
Wire ingest.quarantined to the pager that owns your export job,
sla.breached to the compliance on-call, killswitch.triggered to the risk desk.
Every audit-package ZIP carries manifest.json (per-file SHA-256 and the
package chain hash). datatap-verify.py
recomputes both from the ZIP with the Python standard library and prints PASS/FAIL per file — regulators and
counsel can verify a production without an account. GET /api/v1/audit/verify walks the
hash-chained admin and access ledgers the same way.
Verify the signature — x-datatap-signature: sha256=HMAC_SHA256(secret, raw_body):
expected = "sha256=" + hex(hmac_sha256(secret, raw_body)) constant_time_compare(expected, request.headers["x-datatap-signature"])
Questions or a format we should support? Get in touch · Data & Usage Guide