Public REST API
Run KYC/KYB verifications, attach documents, read case status, and receive signed webhook events — server-to-server, over HTTPS.
Overview
The Omnified API is a small set of JSON-over-HTTPS endpoints. Every request is authenticated with a Bearer key, every write is idempotent, every response is either the resource or an RFC 7807 application/problem+json error. CORS is intentionally disabled — the API is server-to-server; browser clients cannot read responses directly.
- PII fields (name, DOB, ID number, address) are encrypted at rest with AES-256-GCM before insert.
- Sandbox and live use separate keys and separate data — sandbox data never mixes with live.
- All timestamps are ISO 8601 UTC. All IDs are opaque strings; do not parse them.
Quickstart
Create a sandbox key from Settings → API, then run:
No account yet? Use shared demo credentials for the sandbox tenant
Sign in at /auth with any of the roles below (password is the same for all), then mint a sandbox key from Settings → API.
Shared sandbox tenant — do not upload real customer data.
curl -X POST https://getomnified.com/api/public/v1/verifications \
-H "Authorization: Bearer omni_sk_test_XXXXXXXX" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"subject": {
"customer_type": "individual",
"full_name": "Wei Ling Tan",
"nationality": "SG",
"id_type": "nric",
"id_number": "S9412345A"
},
"jurisdiction": "SG",
"checks": "auto",
"client_reference": "cust_123"
}'import { randomUUID } from "node:crypto";
const res = await fetch("https://getomnified.com/api/public/v1/verifications", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.OMNI_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": randomUUID(),
},
body: JSON.stringify({
subject: { customer_type: "individual", full_name: "Wei Ling Tan", nationality: "SG" },
jurisdiction: "SG",
checks: "auto",
}),
});
if (!res.ok) throw new Error(await res.text());
const verification = await res.json();import os, uuid, requests
r = requests.post(
"https://getomnified.com/api/public/v1/verifications",
headers={
"Authorization": f"Bearer {os.environ['OMNI_API_KEY']}",
"Content-Type": "application/json",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"subject": {"customer_type": "individual", "full_name": "Wei Ling Tan", "nationality": "SG"},
"jurisdiction": "SG",
"checks": "auto",
},
timeout=30,
)
r.raise_for_status()
verification = r.json()Authentication
Send an Authorization: Bearer <key> header on every request. Keys are prefixed omni_sk_live_… (production) or omni_sk_test_… (sandbox). Keys are shown once at creation — we store only the SHA-256 hash plus a 16-character display prefix, and verify with a constant-time comparison. Rotate a key at any time from Settings → API; the previous key stays valid for a 24-hour grace window.
Authorization: Bearer omni_sk_live_9c81b12e...
Scopes
Each key has one or more scopes. Requests without the required scope return 403 forbidden_scope.
| Scope | Grants |
|---|---|
| verifications:write | Create and re-run verifications |
| verifications:read | List and fetch verifications |
| documents:write | Attach documents to a verification |
| cases:read | Read case status and events |
| webhooks:test | Fire test webhook deliveries |
| reporting:read | Read reporting summaries and detail feeds |
| rulebook:read | Search the jurisdictional rulebook corpus |
| pilcrow:write | Run a Pilcrow agent turn |
Environments
Sandbox is fully isolated: sandbox keys can only create sandbox verifications, vendor calls are mocked, and no live data is ever touched. Live keys require the org to have live mode activated (see the in-app activation checklist).
- Sandbox —
omni_sk_test_… - Live —
omni_sk_live_…
Both use the same base URL. The key prefix selects the environment; a key used against the wrong environment returns 401.
Metered products (Rulebook, Pilcrow) behave differently per environment: sandbox keys read only the curated demo corpus slice for each jurisdiction. When that slice is empty the call abstains and returns no results — it never widens to the full corpus. Production keys read the full ingested corpus for the jurisdictions the org is entitled to.
Idempotency
All POST endpoints accept an Idempotency-Key header (8–200 characters, UUIDv4 recommended). We store the request hash plus response for 24 hours. A retry with the same key returns the original response with Idempotent-Replay: true. Reusing the same key with a different body returns 409 idempotency_key_mismatch.
POST /api/public/v1/verifications Idempotency-Key: 4c5b1e6a-9f2a-4e1e-8fbb-1b3f0f2c9d10 Content-Type: application/json
Rate limits
Sliding-window per key, per minute. Defaults: sandbox 300 req/min, live 1200 req/min. Every response includes the current limit state; when exceeded we return 429 rate_limited with a Retry-After value in seconds.
X-RateLimit-Limit: 1200 X-RateLimit-Remaining: 1187 X-RateLimit-Reset: 1751780400 Retry-After: 12
Metered endpoints (Rulebook search, Pilcrow) additionally report the remaining credit balance for that product after the call, so you can watch exhaustion approach rather than discovering it as a 402 insufficient_credits.
X-Credits-Product: rulebook X-Credits-Remaining: 87
Errors
Every error is application/problem+json (RFC 7807). We never return stack traces, framework names, or version strings.
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
{
"type": "https://docs.getomnified.com/api/errors#validation_failed",
"title": "Request validation failed",
"status": 400,
"code": "validation_failed",
"errors": [{ "path": "subject.full_name", "message": "Required" }]
}| Status | Code | When |
|---|---|---|
| 400 | validation_failed | Request body failed schema validation |
| 401 | unauthorized | Missing or invalid Bearer key |
| 403 | forbidden_scope | Key lacks the required scope |
| 403 | live_mode_not_activated | Live key used before org activation |
| 403 | not_entitled | Org is not entitled to the requested product |
| 402 | insufficient_credits | No unexpired credits for the metered product |
| 404 | not_found | Resource does not exist or is not visible to the key |
| 409 | idempotency_key_mismatch | Same key reused with a different body |
| 413 | payload_too_large | Body or document exceeds size limit |
| 415 | unsupported_media_type | Content-Type is not application/json |
| 429 | rate_limited | Per-key sliding-window limit exceeded |
| 500 | internal_error | Unhandled server error (safe to retry) |
Pagination
List endpoints are cursor-paginated. Pass limit (max 100) and, for subsequent pages, the next_cursor from the previous response.
{
"data": [ /* … */ ],
"next_cursor": "eyJvIjoiY3JlYXRlZF9hdCJ9",
"has_more": true
}Verification endpoints
/verificationsscope: verifications:writeCreate and run a verification. Returns the resource plus checks results.
{
"subject": {
"customer_type": "individual",
"full_name": "Wei Ling Tan",
"dob_or_incorporation": "1994-03-11",
"nationality": "SG",
"id_type": "nric",
"id_number": "S9412345A",
"address": "..."
},
"jurisdiction": "SG",
"checks": "auto",
"client_reference": "cust_123"
}/verificationsscope: verifications:readList verifications, filterable by client_reference and status, cursor-paginated.
/verifications/{id}scope: verifications:readFetch a single verification, including checks, verdict, and linked case ID.
/verifications/{id}/documentsscope: documents:writeAttach a base64-encoded document. Bytes are hashed (SHA-256) and routed to jurisdictional storage; raw content is never echoed back.
{
"filename": "passport.jpg",
"content_type": "image/jpeg",
"data_base64": "…",
"jurisdiction": "SG"
}/cases/{id}scope: cases:readRead-only case snapshot: status, risk, reason codes, timeline.
/webhooks/testscope: webhooks:testFire a signed test event to your active webhook endpoint.
Reporting endpoints
Machine-readable compliance reporting for external BI, GRC and audit tooling. Rows carry no PII — no name, DOB, ID number or address — only IDs, verdicts, risk, jurisdiction, vendor, timestamps and your client_reference. Results are scoped to the environment of the key used.
/reports/summaryscope: reporting:readAggregate counts for a time window: volumes, verdict mix, average risk, jurisdiction and vendor breakdown, SLA and cost totals.
GET /api/public/v1/reports/summary?from=2026-07-01&to=2026-07-31&jurisdiction=SG
/reports/verificationsscope: reporting:readRow-level feed, cursor-paginated (limit max 500). Filters: from, to, jurisdiction, verdict.
{
"data": [
{
"id": "ver_01HZ…",
"created_at": "2026-07-14T09:12:44Z",
"jurisdiction": "SG",
"verdict": "approved",
"risk_score": 12,
"reason_codes": [],
"vendor_used": "singpass_myinfo",
"status": "completed",
"case_id": null,
"client_reference": "cust_123",
"sandbox": false
}
],
"next_cursor": "MjAyNi0wNy0xNC…",
"has_more": true
}Rulebook endpoints
Hybrid retrieval and reranking over ingested regulator documents (MAS, IFSCA/GIFT City, Indian PMLA and more), returning verbatim provisions with document, clause and page provenance. Every call is recorded as a sealed agent run and consumes product credits. Requires the rulebook product entitlement.
/rulebook/searchscope: rulebook:readSearch the corpus for one question across up to six jurisdictions.
{
"question": "What EDD is required for a non-face-to-face onboarding?",
"jurisdictions": ["SG", "GIFT_IFSC"],
"top_k": 6
}{
"run_id": "run_01HZ…",
"question": "…",
"jurisdictions": ["SG", "GIFT_IFSC"],
"sandbox": true,
"result_count": 4,
"results": [
{
"chunk_id": "…",
"document_id": "…",
"document": "MAS Notice 626",
"jurisdiction": "SG",
"clause_ref": "6.14",
"section_path": "6 · Customer Due Diligence",
"version_label": "2024-07",
"instrument_type": "notice",
"is_binding": true,
"issuing_authority": "MAS",
"page_from": 12,
"page_to": 12,
"source_url": "https://…",
"content": "…",
"score": 0.71
}
]
}Sandbox keys are restricted to the demo slice; an empty slice returns result_count: 0 rather than widening.
Pilcrow endpoints
Pilcrow is the conversational compliance agent layered over the rulebook engine: it plans retrieval, quotes only verbatim provisions it can cite, and abstains structurally when the corpus does not support an answer. API turns are stateless — pass a thread_id belonging to your org to carry prior context read-only; nothing is written to it. Requires the pilcrow entitlement and available credits.
/pilcrowscope: pilcrow:writeRun one agent turn. Returns the answer, citations, determination and run provenance.
{
"message": "Does a GIFT IFSC entity need to re-KYC an existing client?",
"jurisdictions": ["GIFT_IFSC"],
"thread_id": "6f1c…"
}{
"run_id": "run_01HZ…",
"thread_id": null,
"sandbox": true,
"answer": "…",
"citations": [
{ "document": "IFSCA AML Guidelines", "clause_ref": "9.1", "quote": "…" }
],
"confidence": "high",
"determination": "answered",
"abstained": false,
"billable_units": 1
}Returns 403 not_entitled without the product, and 402 insufficient_credits when the credit balance is exhausted.
Platform endpoints
/healthUnauthenticated liveness probe. Returns 200 OK with no version info.
/buildUnauthenticated build provenance of the deployed bundle — the same stamps written into every sealed audit row, so "is version X live" is answered by fetching this endpoint.
{
"app_version": "app-0.46.0+ab12cd3",
"declared_version": "app-0.46.0",
"code_sha": "build-ab12cd3",
"corpus_version": "…",
"prompt_version": "pilcrow-2026-08-13"
}/openapi.jsonMachine-readable OpenAPI 3.1 document covering every endpoint on this page.
MCP endpoint
A Model Context Protocol endpoint exposes the same capabilities to AI agents as first-class tools, over JSON-RPC 2.0 at POST /api/public/v1/mcp, authenticated with the same Bearer key. Each tool enforces the same scope as its REST equivalent. Call the apex host directly — cross-host redirects strip the Authorization header.
| Tool | Scope |
|---|---|
| verify_customer | verifications:write |
| get_verification | verifications:read |
| list_verifications | verifications:read |
| attach_document | documents:write |
| get_case | cases:read |
| health | none |
curl -X POST https://getomnified.com/api/public/v1/mcp \
-H "Authorization: Bearer omni_sk_test_XXXXXXXX" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Webhooks
Configure a HTTPS endpoint at Settings → API → Webhooks. Every delivery is signed with HMAC-SHA256 over <timestamp>.<raw_body>. Verify the signature before trusting the payload.
Omni-Signature: t=1751780400,v1=8c7a… Omni-Event: verification.completed Omni-Delivery: dlv_01H…
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyOmniSignature(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(",").map((s) => s.trim().split("=")));
const t = Number(parts.t);
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const got = Buffer.from(parts.v1 ?? "", "hex");
const exp = Buffer.from(expected, "hex");
return got.length === exp.length && timingSafeEqual(got, exp);
}Deliveries retry with exponential backoff — up to 6 attempts over 24 hours. Endpoints resolving to private / RFC 1918 / loopback IP ranges are rejected at both save time and send time.
| Event | Fires when |
|---|---|
| verification.completed | A verification finishes with any verdict |
| verification.failed | A verification errors before producing a verdict |
| case.opened | A case is created from a review/escalate verdict |
| case.approved | A case reaches an approved terminal state |
| case.rejected | A case reaches a rejected terminal state |
| case.escalated | A case is escalated to compliance leadership |
| case.closed | A case is closed (with or without duplicate) |
Security model
- Transport: HTTPS-only; HSTS with preload; webhooks HTTPS-only; SSRF guard blocks private ranges.
- Field-level encryption: subject name, DOB, ID number, address, and raw vendor payloads are AES-256-GCM encrypted (WebCrypto) before insert. Envelope
v1:iv:ciphertext+tagsupports versioned key rotation. - Key hygiene: shown once, stored as SHA-256 + 16-char prefix, constant-time compare, per-key rate limit, rolling 24h grace on rotation, immediate revocation.
- Auth boundary: RLS on every table; API paths bypass session auth but verify Bearer + scopes inside the handler.
- Logging hygiene: structured logs record method / path / status / duration / request-id / IP and only the 16-char key prefix — never the key, never full PII, never signatures.
- Data residency: every verification records a
data_regionderived from the jurisdiction so future in-region storage can shard on it. - Maker-checker: sanctions-hit and high-risk case closures require a second, different-user approver enforced at the database level.
OpenAPI spec
Machine-readable OpenAPI 3.1 document — feed it to any generator to produce a typed client:
GET /api/public/v1/openapi.json
Changelog
- v1 · 2026-08-14 — Metered product endpoints:
POST /rulebook/search(rulebook:read) andPOST /pilcrow(pilcrow:write), with entitlement and credit gating, sandboxdemocorpus slices, sealed agent-run provenance, and new402 insufficient_credits/403 not_entitledproblem codes. - v1 · 2026-08-02 — Reporting API:
GET /reports/summaryandGET /reports/verificationsunderreporting:read. AddedGET /builddeployed-build provenance and the MCP endpoint atPOST /mcp. - v1 · 2026-07-05 — Initial public release: verifications, documents, cases, webhooks. Sandbox 300 req/min, live 1200 req/min.
Email support@getomnified.com or open a ticket from your dashboard. Include your key prefix (never the full key) and a request ID.