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://api.getomnified.com/api/public/v1Sandbox: https://sandbox.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  · Org Owner
admin.sg@asiawealth.demo  · Compliance Admin (SG + GIFT)
reviewer.in@asiawealth.demo  · Reviewer (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://sandbox.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"
  }'
node
import { randomUUID } from "node:crypto";

const res = await fetch("https://api.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://api.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.

http
Authorization: Bearer omni_sk_live_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

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

  • Sandboxomni_sk_test_…, base URL https://sandbox.getomnified.com
  • Liveomni_sk_live_…, base URL https://api.getomnified.com

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

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
400validation_failedRequest body failed schema validation
401unauthorizedMissing or invalid Bearer key
403forbidden_scopeKey lacks the required scope
403live_mode_not_activatedLive key used before org activation
404not_foundResource does not exist or is not visible to the key
409idempotency_key_mismatchSame key reused with a different body
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)

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
}

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 document. Bytes are hashed (SHA-256) and routed to jurisdictional storage; raw content is never echoed back.

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/health

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

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);
}

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.

EventFires when
verification.completedA verification finishes with any verdict
verification.failedA verification errors before producing a verdict
case.openedA case is created from a review/escalate verdict
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 (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+tag supports 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_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-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.