Docs · Public REST API
Reference · v1

Public REST API

Run KYC/KYB verifications, attach documents, read case status, and receive signed webhook events — server-to-server, over HTTPS.

Base URL: https://getomnified.com/api/public/v1

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.

owner@asiawealth.demo  · Administrator
admin.sg@asiawealth.demo  · Compliance admin (SG + GIFT)
reviewer.in@asiawealth.demo  · Member (IN)
dev@asiawealth.demo  · Developer
auditor@asiawealth.demo  · Auditor
password: Omnified!Demo2026

Shared sandbox tenant — do not upload real customer data.

curl
curl -X POST https://getomnified.com/api/public/v1/verifications \
  -H "Authorization: Bearer omn_sk_test_sg_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"
  }'
node
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();
python
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 omn_sk_live_sg_… (production) or omn_sk_test_sg_… (sandbox); the segment after the environment is the issuing region. 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. To rotate a key, create a new one in Settings → API, move your systems to it, then revoke the old one. Revocation is immediate: the revoked key is refused with 401 key_revoked from then on.

http
Authorization: Bearer omn_sk_live_sg_9c81b12e...

Scopes

Each key has one or more scopes. Requests without the required scope return 403 forbidden_scope.

ScopeGrants
verifications:writeCreate and re-run verifications
verifications:readList and fetch verifications
documents:writeAttach documents to a verification
cases:readRead case status and events
webhooks:testFire test webhook deliveries
reporting:readRead reporting summaries and detail feeds
rulebook:readSearch the jurisdictional rulebook corpus
pilcrow:writeRun a Pilcrow agent turn
journeys:runStart journey runs, read their status and next step, answer their steps
journeys:evidenceDownload journey run evidence bundles
journeys:readAgent tokens only: explain a journey run, check a rulebook change's impact on a journey
journeys:writeAgent tokens only: draft a journey from a policy (a draft; never approve or publish)

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 — omn_sk_test_sg_…
  • Live — omn_sk_live_sg_…

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.

http
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.

http
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.

http
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
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" }]
}
StatusCodeWhen
400invalid_jsonRequest body is not valid JSON
400validation_failedRequest body or query parameters failed validation (including an invalid cursor)
401unauthorizedMissing or invalid Bearer key
401key_revokedThe key or agent token has been revoked (immediate)
401wrong_regionThe key was issued for another region; call that region's API host
401wrong_environmentThe credential is bound to the other environment (Test or Production)
403forbidden_scopeKey lacks the required scope
403forbidden_jurisdictionThe credential is not allowed to use that jurisdiction
403not_entitledOrg is not entitled to the requested product
403trial_expiredThe organisation's trial has ended
402insufficient_creditsNo unexpired credits for the metered product
402contract_exhaustedThe prepaid KYC contract has no credits left (Production only)
404not_foundResource does not exist or is not visible to the key
409idempotency_key_mismatchSame key reused with a different body
409idempotency_key_in_progressA request with the same Idempotency-Key is still running; retry shortly
413payload_too_largeBody or document exceeds size limit
415unsupported_media_typeContent-Type is not application/json
429rate_limitedPer-key sliding-window limit exceeded
500internal_errorUnhandled server error (safe to retry)
503idempotency_unavailableThe Idempotency-Key could not be recorded; nothing ran, safe to retry

Pagination

List endpoints are cursor-paginated. Pass limit (max 100) and, for subsequent pages, the next_cursor from the previous response.

json
{
  "data": [ /* … */ ],
  "next_cursor": "eyJvIjoiY3JlYXRlZF9hdCJ9",
  "has_more": true
}

Verification endpoints

POST/verificationsscope: verifications:write

Create and run a verification. Returns the resource plus checks results.

json
{
  "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"
}
GET/verificationsscope: verifications:read

List verifications, filterable by client_reference and status, cursor-paginated.

GET/verifications/{id}scope: verifications:read

Fetch a single verification, including checks, verdict, and linked case ID.

POST/verifications/{id}/documentsscope: documents:write

Attach a base64-encoded PDF, JPEG, PNG, WebP, HEIC or HEIF document (10 MB decoded). The file is stored in your region's document store and appears in the verification's documents; the response returns its document_id and the SHA-256 of the raw bytes. Documents attached over the API are not OCR'd yet (ocr: "not_performed"); raw content is never echoed back. jurisdiction is optional; when sent it must equal the verification's jurisdiction, otherwise the request is refused with 422.

json
{
  "filename": "passport.jpg",
  "content_type": "image/jpeg",
  "data_base64": "…",
  "jurisdiction": "SG"
}
GET/cases/{id}scope: cases:read

Read-only case snapshot: status, risk, reason codes, timeline.

POST/webhooks/testscope: webhooks:test

Fire a signed test event to your active webhook endpoint.

GET/journeys/evidence/{run_id}scope: journeys:evidence

A Journey Builder run's signed evidence bundle: the pinned journey version, rulebook bindings and clause references, every check with its vendor, verdict and failover history, consent evidence, review decisions, the outcome and the hash-chained timeline. Personal data is not included. The default returns the signed JSON; ?format=pdf returns the PDF as base64. The Ed25519 signature covers the RFC 8785 canonical form of evidence and verifies offline against /.well-known/omnified-evidence-keys.json. Every download is audited.

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.

GET/reports/summaryscope: reporting:read

Aggregate counts for a time window: volumes, verdict mix, average risk, jurisdiction and vendor breakdown, SLA and cost totals.

http
GET /api/public/v1/reports/summary?from=2026-07-01&to=2026-07-31&jurisdiction=SG
GET/reports/verificationsscope: reporting:read

Row-level feed, cursor-paginated (limit max 500). Filters: from, to, jurisdiction, verdict.

json
{
  "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.

POST/rulebook/searchscope: rulebook:read

Search the corpus for one question across up to six jurisdictions.

json
{
  "question": "What EDD is required for a non-face-to-face onboarding?",
  "jurisdictions": ["SG", "GIFT_IFSC"],
  "top_k": 6
}
json
{
  "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.

POST/pilcrowscope: pilcrow:write

Run one agent turn. Returns the answer, citations, determination and run provenance.

json
{
  "message": "Does a GIFT IFSC entity need to re-KYC an existing client?",
  "jurisdictions": ["GIFT_IFSC"],
  "thread_id": "6f1c…"
}
json
{
  "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.

Journey endpoints

Run a published Journey Builder journey for an applicant. Every run pins the journey’s live release in the key’s environment (sandbox keys: Test). Production opens when Journey Builder is available in Production. Personal data in applicant and inputs is sealed in the run’s log; responses and webhooks carry ids, statuses and verdicts. Requires a Journey Builder install.

New to Journey Builder? The Journey Builder guide covers journeys, versions, review, evidence and delivery options.

POST/journeys/runsscope: journeys:run

Start a run. Send Idempotency-Key: a retry returns the same run. Add hosted_link to get a single-use URL to send the applicant to (they finish on the hosted page; you follow along with the API or webhooks).

json
{
  "journey_id": "6f1c…",
  "subject_ref": "cust-0001",
  "applicant": { "type": "individual", "residency": "SG" },
  "inputs": { "full_name": "…" },
  "jurisdiction": "SG",
  "hosted_link": { "ttl_seconds": 86400 }
}
json
{
  "id": "0a3e…",
  "object": "journey_run",
  "status": "waiting",
  "sandbox": true,
  "next_step": {
    "kind": "form",
    "node_id": "profile",
    "entry": 1,
    "form": { "id": "f_profile", "title": "About you", "fields": [
      { "id": "full_name", "type": "text", "label": "Full name", "required": true,
        "value": "…", "locked": true, "show_if": null }
    ] }
  },
  "outcome": null,
  "hosted_link": { "url": "https://…/j/s/jh_…", "expires_at": "…", "single_use": true }
}
GET/journeys/runs/{id}scope: journeys:run

The run’s status and its next_step: form (answer it), capture (your configured vendor’s own capture: a hosted link, or an access token for the vendor’s web SDK), processing, review or wait; null once finished. ?locale=en|hi|ar picks the language of labels.

POST/journeys/runs/{id}/answersscope: journeys:run

Answer the form the run waits on, naming its node_id and entry. The same checks as the hosted form apply (required, formats, show-if, locked prefill; a national identity number is kept as its last 4 digits). 422 invalid_answers lists each field; 409 step_mismatch means the run has moved on.

json
{ "node_id": "profile", "entry": 1, "answers": { "date_of_birth": "1980-01-01" } }
GET/journeys/runs/{id}/outcomescope: journeys:run

The verdict (pass, refer, fail), each check’s verdict, reason codes and vendor, and the path taken.

POST/journeys/runs/{id}/hosted-linkscope: journeys:run

A new single-use hosted URL for a run started through the API.

Hosted link and Web SDK. A hosted link (/j/jl_…) is created in Journey Builder → Settings and opens the journey for anyone who has it. Embed it with the dependency-free Web SDK (/sdk/journeys-v1.js, or the ES module /sdk/journeys-v1.mjs; types at /sdk/journeys-v1.d.ts) after adding your site to the allowed origins: the page is framed only by those origins and posts its events (ready, started, step, completed, failed, expired, error, resize, close) only to them. Confirm an outcome server-side before acting on it.

html
<script src="https://getomnified.com/sdk/journeys-v1.js"></script>
<script>
  OmnifiedJourneys.open({ link: "jl_…", mode: "modal" })
    .on("completed", (e) => fetch("/my-backend/check-run/" + e.runId));
</script>

Platform endpoints

GET/health

Unauthenticated liveness probe. Returns 200 OK with no version info.

GET/build

Unauthenticated 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.

json
{
  "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"
}
GET/openapi.json

Machine-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.

ToolScope
verify_customerverifications:write
get_verificationverifications:read
list_verificationsverifications:read
attach_documentdocuments:write
get_casecases:read
draft_journey_from_policyjourneys:write (agent tokens only)
explain_runjourneys:read (agent tokens only)
diff_rulebook_impactjourneys:read (agent tokens only)
healthnone
curl
curl -X POST https://getomnified.com/api/public/v1/mcp \
  -H "Authorization: Bearer omn_sk_test_sg_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.

http
Omni-Signature: t=1751780400,v1=8c7a…
Omni-Event: verification.completed
Omni-Delivery: dlv_01H…
node
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);
}

Journey events (journey.run.*) carry the run, journey and version ids, node ids, verdicts and reason codes. Runs in Test are marked "sandbox": true and are sent only to endpoints with Test runs switched on in Settings → Webhooks.

Deliveries retry with exponential backoff — up to 7 attempts: the first try, then retries after 1 min, 5 min, 15 min, 1 h, 6 h and 24 h. Endpoints resolving to private / RFC 1918 / loopback IP ranges are rejected at both save time and send time.

EventFires when
verification.completedA verification finishes with a verdict
verification.failedA verification could not get a verdict from any vendor (the row is kept for review)
verification.updatedA vendor decision changes an existing verification
case.openedA case is created from a review, escalate or rejected verdict
case.reopenedA closed case is reopened by a later, harsher decision
case.updatedA vendor update refreshes an open case
case.pending_second_approvalA case decision is waiting for a second approver
case.approvedA case reaches an approved terminal state
case.rejectedA case reaches a rejected terminal state
case.escalatedA case is escalated to compliance leadership
case.closedA case is closed
case.closed_duplicateA case is closed as a duplicate of another case
journey.run.startedA journey run starts (hosted link, API or console)
journey.run.step_completedA journey step (form, check, review, wait or score) completes
journey.run.awaiting_reviewA journey run reaches a review step and waits for a reviewer
journey.run.completedA journey run reaches an outcome (pass, refer or fail)
journey.run.failedA journey run stops with an error before reaching an outcome
journey.run.failed_overA journey check fails over from one vendor to the next

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+tag supports versioned key rotation.
  • Key hygiene: shown once, stored as SHA-256 + 16-char prefix, constant-time compare, per-key rate limit, 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_region derived 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:

http
GET /api/public/v1/openapi.json

Download openapi.json

Changelog

  • v1 · 2026-11-01 — Journey endpoints under journeys:run: start a run, read its next step, answer it, read its outcome, and mint single-use hosted links. Journey webhooks journey.run.*, with Test-run delivery opt-in per endpoint.
  • v1 · 2026-08-14 — Metered product endpoints: POST /rulebook/search (rulebook:read) and POST /pilcrow (pilcrow:write), with entitlement and credit gating, sandbox demo corpus slices, sealed agent-run provenance, and new 402 insufficient_credits / 403 not_entitled problem codes.
  • v1 · 2026-08-02 — Reporting API: GET /reports/summary and GET /reports/verifications under reporting:read. Added GET /build deployed-build provenance and the MCP endpoint at POST /mcp.
  • v1 · 2026-07-05 — Initial public release: verifications, documents, cases, webhooks. Sandbox 300 req/min, live 1200 req/min.
Need a hand?

Email support@getomnified.com or open a ticket from your dashboard. Include your key prefix (never the full key) and a request ID.