Enrich API · v1

Verification, as an API.

Run the same AI-driven identity, KYC and business verifications your team uses in Enrich — programmatically, from any backend. Durable runs, exact billing, real-time streams.

Getting started

Introduction

The Enrich API is run-based: you create a run (one verification request), Enrich executes it asynchronously through its agent and data providers, and you retrieve the result by ID — polling, long-polling or a live event stream.

Results are durable. Nothing exists only inside a socket: a dropped connection, client crash or retry never loses an answer or double-bills a verification.

GuaranteeWhat it means for you
Durable resultsEvery answer, report and charge is retrievable by run_id, forever.
Idempotent creationSame Idempotency-Key → same run. Retries can never execute or bill twice.
Exact billingcredits_used + per-check breakdown reconciles with your balance, always.
Stable contractWithin /v1, changes are additive only.
Base URL
https://enrich.timble.ai/api/v1
A run, end to end
POST /v1/runs            → 202 {id, status: "queued"}
GET  /v1/runs/{id}?wait=50  (repeat until terminal)
                         → {status: "completed",
                            answer, report,
                            credits_used, tool_calls}

Getting started

Authentication

Every request carries an organisation API key in the Authorization header. An org administrator creates keys under Admin → API Keys; the secret is shown exactly once — store it in a secret manager.

PropertyDetail
ScopeKeys work only on /v1/*. They are rejected everywhere else — a leaked key can never manage users, keys or billing.
IdentityRuns are attributed to your organisation’s service identity, not a person. Keys survive personnel changes.
StorageOnly a SHA-256 hash is stored; the plaintext is unrecoverable.
Failure modeUnknown, revoked and expired keys all return the same opaque 401 invalid_api_key.
Limits10 active keys per organisation; named and revocable independently, instantly.
Header
Authorization: Bearer sk-tm-XXXXXXXXXXXXXXXXXXXXXXXXXXXX
401 — invalid key
{
  "error": {
    "code": "invalid_api_key",
    "message": "Invalid, revoked, or expired API key"
  }
}

Getting started

Quickstart

A complete verification in three steps. Step 1 returns immediately with a run_id; the verification executes in the background.

1 · Start the run

curl -X POST https://enrich.timble.ai/api/v1/runs \
  -H "Authorization: Bearer $ENRICH_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-7841" \
  -d '{
    "message": "Verify identity: Rahul Sharma, +91 98xxxxxx10, PAN ABCDE1234F",
    "workflow": "kyc-verification",
    "metadata": {"order_id": "7841"}
  }'

2 · Wait for the result

curl "https://enrich.timble.ai/api/v1/runs/run_9f3a2c…?wait=50" \
  -H "Authorization: Bearer $ENRICH_KEY"

3 · Read the answer

Response — completed run
{
  "status": "completed",
  "answer": "PAN verified — name match 96%. Mobile active…",
  "report": { /* structured verification report */ },
  "report_url": "https://…/report.pdf?X-Amz-…",
  "credits_used": 11.8,
  "tool_calls": [
    { "id": "call_201", "name": "pan_verification",
      "status": "success", "billed": true, "credits": 2.0 }
  ]
}
Plans: for multi-step verifications the agent may pause with a proposed plan (requires_approval). Approve it with one call — see Approval flow — or enable auto-approval on the key for zero-touch automation.

Concepts

Runs & the status lifecycle

A run is one verification request — the unit of execution, retrieval and billing.

StatusMeaning
queuedAccepted, about to execute.
runningThe agent is executing checks.
requires_approvalPaused on a proposed plan, awaiting your decision (24h timeout).
completed·terminalFinished — answer, steps, report/report_url, credits_used populated.
failed·terminalInternal error (see error). Work performed is billed.
cancelled·terminalCancelled by you, a declined plan, or approval timeout.
interrupted·terminalOrphaned by a service restart — safe to retry with the same Idempotency-Key.

Concepts

Threads

Every run belongs to a thread — a conversation context. Omit thread_id to start a new one; pass a previous run's thread_id to ask follow-ups with full memory of earlier results. Each run in a thread is billed and retrievable independently.

Follow-up on the same thread
POST /v1/runs
{
  "message": "Also check his employment history",
  "thread_id": "th_77b2…"
}

Concepts

Workflows

A workflow is a named, reusable verification procedure the agent follows — which checks to run, in what order, what the report must emphasise. Pass its slug as workflow when creating a run.

Built-ins ship with Enrich: kyc-verification, kyb-us, kyc-us, employment-verification, mobile-verification, vehicle-rc-verification, uae-verification, business-due-diligence, social-profile-analysis.

Custom workflows are Markdown procedures your admin creates under Admin → Workflows — private to your organisation, taking precedence over a built-in with the same slug.

GET /v1/workflows
[
  { "slug": "kyc-verification",
    "name": "Kyc Verification",
    "scope": "built-in", "is_active": true },

  { "slug": "loan-pre-check",
    "name": "Loan applicant pre-check",
    "description": "PAN + phone + employment",
    "scope": "org", "is_active": true }
]

Concepts

Credits & billing

Verifications consume credits from your organisation's prepaid pool — the same pool the web app uses. Each data-provider check has a price; model usage is metered per token. credits_used is the exact debit, itemised per check in tool_calls[] (failed checks are typically not billed).

Overdraft: the balance is checked when a run is created. A run admitted with a positive balance executes to completion even past zero — the balance goes negative, and top-ups settle the debt first. Size your runs and monitor GET /v1/usage.
GET /v1/usage
{
  "credits_used": 188.2,
  "credits_remaining": 311.8,   // negative = overdraft owed
  "max_credits": 500.0          // null = unlimited
}

Create a run

POST/v1/runs

FieldTypeDescription
messagestring · requiredThe verification request in plain language, with every identifier you have (name, phone, PAN, registration numbers…).
workflowstringWorkflow slug to follow. Unknown slug → 404 workflow_not_found.
thread_idstringContinue an existing conversation.
streambooleantrue upgrades this response to Server-Sent Events.
metadataobject≤2 KB of your own data, echoed back on every read.

Send an Idempotency-Key header (≤255 chars) on every create. Replaying the same key + body returns the existing run (200) instead of executing again; the same key with a different body returns 409 idempotency_key_reuse.

Returns 202 with the full run resource; execution continues in the background.

curl -X POST https://enrich.timble.ai/api/v1/runs \
  -H "Authorization: Bearer $ENRICH_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-7841" \
  -d '{
    "message": "Verify identity: Rahul Sharma, +91 98xxxxxx10, PAN ABCDE1234F",
    "workflow": "kyc-verification",
    "metadata": {"order_id": "7841"}
  }'

Retrieve a run

GET/v1/runs/{run_id}?wait={seconds}

Fetch the current state and result. With wait (max 50), the call long-polls: it returns early the moment the status changes. Strongly preferred over tight polling loops.

Derived fields appear as they become available: answer (the final synthesised reply — not the running narration), report and a presigned report_url PDF when completed, plan only while awaiting approval, error when failed.

steps[] is the ordered timeline (text, tool calls, report) — the same sequence a streaming consumer sees, for reconstructing the run. tool_calls[] carries a stable call_… ID, billing status and price per check — your reconciliation record. report_url is short-lived and re-minted on every read, so fetch it fresh.

The run resource
{
  "id":           "run_9f3a2c…",
  "thread_id":    "th_77b2…",
  "status":       "completed",
  "input":        { "message": "…",
                    "workflow": "kyc-verification" },
  "answer":       "PAN verified — name match 96%…",
  "steps": [      /* ordered timeline */
    { "type": "tool_call", "name": "pan_verification",
      "status": "success", "id": "call_201", "credits": 2.0 },
    { "type": "report" },
    { "type": "text", "content": "PAN verified…" }
  ],
  "report":       { /* structured report */ },
  "report_url":   "https://…/report.pdf?X-Amz-…",
  "plan":         null,
  "error":        null,
  "credits_used": 11.8,
  "tool_calls": [
    { "id": "call_201",
      "name": "pan_verification",
      "status": "success",
      "billed": true,
      "credits": 2.0,
      "latency_ms": 840 }
  ],
  "metadata":   { "order_id": "7841" },
  "created_at": "2026-06-13T09:30:12Z",
  "started_at": "2026-06-13T09:30:13Z",
  "completed_at": "2026-06-13T09:31:04Z"
}

Stream run events

GET/v1/runs/{run_id}/events

Server-Sent-Events stream of the run's full history and live progress. Honours Last-Event-ID (sent automatically by EventSource) so reconnects resume gaplessly. Already-finished runs replay all events, then close.

See the streaming guide for the full event vocabulary.

Wire format
id: 41
event: answer.delta
data: {"text": "PAN verified — name match 96%…"}

: ping        ← heartbeat during silence

Resolve plan approval

POST/v1/runs/{run_id}/approval

Valid only while the run is requires_approval — anything else returns 409 not_awaiting_approval (making concurrent double-approvals safe).

approved resumes execution exactly where it paused. declined cancels the run: the agent performs no further checks, and only planning work already done is billed.

Request
POST /v1/runs/run_9f3a2c…/approval
{ "decision": "approved" }     // or "declined"

Cancel a run

POST/v1/runs/{run_id}/cancel

Stops a queued, running or requires_approval run. Work already performed is billed (the same policy as the web app's Stop); partial answers are preserved on the run. Terminal runs return 409 not_cancellable.

Request
POST /v1/runs/run_9f3a2c…/cancel

List runs in a thread

GET/v1/threads/{thread_id}/runs

The thread's runs, newest first. limit (≤100) and offset paginate.

Request
GET /v1/threads/th_77b2…/runs?limit=20&offset=0

List workflows

GET/v1/workflows

Everything your key can pass as workflow: built-ins plus your organisation's custom procedures (scope: "org").

Response
[
  { "slug": "kyc-verification", "scope": "built-in", … },
  { "slug": "loan-pre-check",   "scope": "org", … }
]

Get credit balance

GET/v1/usage

credits_remaining can be negative (overdraft owed) or null (unlimited plan).

Response
{
  "credits_used": 188.2,
  "credits_remaining": 311.8,
  "max_credits": 500.0
}

Manage webhooks

POST/v1/webhooks

Register endpoints to be notified when runs finish — see the Webhooks guide for the payload, signature scheme and delivery semantics. Endpoints are per-org; also managed in Admin → Webhooks.

Creating one returns a whsec_… signing secret once.

Endpoints
POST   /v1/webhooks            create { url, event_types? }
GET    /v1/webhooks            list
GET    /v1/webhooks/{id}       fetch
PATCH  /v1/webhooks/{id}       update (is_active=true re-enables)
DELETE /v1/webhooks/{id}       remove
GET    /v1/webhooks/{id}/deliveries   recent attempts

Guides

Streaming (Server-Sent Events)

Two ways to stream — "stream": true on create (the same response becomes the stream), or attach to /events at any time. Both emit the identical, persisted sequence.

Every event has a monotonically increasing id. Store the last one you processed; reconnect with Last-Event-ID and you resume without loss or duplication — even across our restarts.

eventdataNotes
run.created{run_id, thread_id}Always first — capture the IDs immediately.
run.requires_approval{plan}Run paused; the stream stays open (heartbeats) while parked.
tool_call{id, name, status}started · success · failed · not_found
answer.delta{text}Incremental answer text (~0.7 s batches).
run.report{report}Full structured report, when generated.
run.completed{status, credits_used, report_url}Always last; carries the terminal status + presigned report PDF.
Forward compatibility: new event types may be added at any time. Ignore events you don't recognise; never depend on an event type being absent.
curl -N -X POST https://enrich.timble.ai/api/v1/runs \
  -H "Authorization: Bearer $ENRICH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message": "Verify …", "stream": true}'

Guides

Plan approval (human-in-the-loop)

For multi-step verifications the agent may propose a plan before executing checks — a control point for cost and compliance.

  1. The run parks at requires_approval with plan on the resource (and a run.requires_approval event).
  2. You post approved or declined.
  3. Approved → resumes where it paused; open streams continue on the same connection. Declined → cancelled, hard-stopped, only planning billed.
  4. No decision within 24 hours → auto-cancelled.

For zero-touch pipelines, enable auto-approve plans on the key — runs never pause.

A parked run
{
  "status": "requires_approval",
  "plan": {
    "summary": "3-phase KYC: identity, mobile, employment",
    "tasks": [
      { "goal": "Verify PAN against provided name" },
      { "goal": "Mobile intelligence + carrier check" },
      { "goal": "Employment history via UAN" }
    ]
  },
  …
}

Guides

Webhooks

Runs are long — instead of polling, register an endpoint and we POST a signed event when a run changes lifecycle state. Endpoints are per-org (create them above or in Admin → Webhooks).

The payload data is the full run resource — identical to GET /v1/runs/{id}, wrapped in an event envelope. report_url is minted fresh per delivery, so it's always valid.

eventwhen
run.completedRun finished successfully.
run.failedInternal error (partial work billed).
run.cancelledCancelled / declined plan / approval timeout.
run.interruptedOrphaned by a restart — safe to retry.
run.requires_approvalPaused on a plan — act via /approval.

Delivery is at-least-once. Ack with 2xx within 10s; failures retry with backoff (30s → 6h, 8 tries) then dead-letter. Dedupe on Webhook-Id. Endpoints that fail persistently are auto-disabled (we email the org admin). HTTPS only; URLs resolving to private addresses are rejected.

Verify every delivery: reject if Webhook-Timestamp is more than 5 minutes old, then constant-time compare the HMAC. New event types may be added — ignore ones you don't recognise.
Delivery
POST <your endpoint>
Webhook-Id: evt_3f9a…
Webhook-Timestamp: 1718280000
Webhook-Signature: v1,<base64 hmac>

{
  "id": "evt_3f9a…",
  "type": "run.completed",
  "created_at": "…",
  "data": { /* full run resource */ }
}
Verify (Python)
import hmac, hashlib, base64

signed = f"{wid}.{ts}.".encode() + raw_body
expected = "v1," + base64.b64encode(
    hmac.new(secret.encode(), signed,
             hashlib.sha256).digest()).decode()
assert hmac.compare_digest(expected, sig_header)

Reference

Errors

HTTP · codeWhen / what to do
401 invalid_api_keyUnknown, revoked or expired key. Rotate via Admin → API Keys.
402 insufficient_creditsBalance ≤ 0 at creation. Top up; retry with the same Idempotency-Key.
404 not_foundRun or thread does not exist in your organisation.
404 workflow_not_foundUnknown or deactivated workflow slug.
409 idempotency_key_reuseSame key, different body — use a fresh key for new requests.
409 not_awaiting_approvalApproval sent to a run not awaiting one.
409 not_cancellableCancel sent to a run already terminal.
422 invalid_requestValidation failure; the message names the field.
429 rate_limitedRespect the Retry-After header.
500 internal_errorRun marked failed; work performed is billed. Retry with a fresh key.
Error envelope — every error, same shape
{
  "error": {
    "code": "insufficient_credits",
    "message": "Organisation has no credits remaining",
    "run_id": "run_9f3a2c…"   // when applicable
  }
}

Reference

Rate limits

LimitDefaultOn breach
Run creations per key60 / minute429 + Retry-After: 60
Concurrent active runs per key10 (queued + running; parked approvals don’t count)429 + Retry-After: 10
Reads (GET)600 / minute per key429 + Retry-After

Limits are configurable per organisation — contact your account manager.

Reference

Versioning & support

The API is path-versioned (/v1). Within a version, changes are additive only — new endpoints, optional fields and event types may appear; removals and renames only happen in a new version path. Build clients that ignore unknown fields and events.

For integration support, contact your Enrich account team with the run_id — every event, check and charge is auditable by that ID.

Enrich API Documentation · v1 · Timble Technologies