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.
| Guarantee | What it means for you |
|---|---|
| Durable results | Every answer, report and charge is retrievable by run_id, forever. |
| Idempotent creation | Same Idempotency-Key → same run. Retries can never execute or bill twice. |
| Exact billing | credits_used + per-check breakdown reconciles with your balance, always. |
| Stable contract | Within /v1, changes are additive only. |
https://enrich.timble.ai/api/v1
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.
| Property | Detail |
|---|---|
| Scope | Keys work only on /v1/*. They are rejected everywhere else — a leaked key can never manage users, keys or billing. |
| Identity | Runs are attributed to your organisation’s service identity, not a person. Keys survive personnel changes. |
| Storage | Only a SHA-256 hash is stored; the plaintext is unrecoverable. |
| Failure mode | Unknown, revoked and expired keys all return the same opaque 401 invalid_api_key. |
| Limits | 10 active keys per organisation; named and revocable independently, instantly. |
Authorization: Bearer sk-tm-XXXXXXXXXXXXXXXXXXXXXXXXXXXX
{
"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
{
"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 }
]
}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.
| Status | Meaning |
|---|---|
| queued | Accepted, about to execute. |
| running | The agent is executing checks. |
| requires_approval | Paused on a proposed plan, awaiting your decision (24h timeout). |
| completed·terminal | Finished — answer, steps, report/report_url, credits_used populated. |
| failed·terminal | Internal error (see error). Work performed is billed. |
| cancelled·terminal | Cancelled by you, a declined plan, or approval timeout. |
| interrupted·terminal | Orphaned 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.
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.
[
{ "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).
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
| Field | Type | Description |
|---|---|---|
| message | string · required | The verification request in plain language, with every identifier you have (name, phone, PAN, registration numbers…). |
| workflow | string | Workflow slug to follow. Unknown slug → 404 workflow_not_found. |
| thread_id | string | Continue an existing conversation. |
| stream | boolean | true upgrades this response to Server-Sent Events. |
| metadata | object | ≤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.
{
"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.
id: 41
event: answer.delta
data: {"text": "PAN verified — name match 96%…"}
: ping ← heartbeat during silenceResolve 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.
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.
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.
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").
[
{ "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).
{
"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.
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 attemptsGuides
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.
| event | data | Notes |
|---|---|---|
| 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. |
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.
- The run parks at requires_approval with
planon the resource (and arun.requires_approvalevent). - You post
approvedordeclined. - Approved → resumes where it paused; open streams continue on the same connection. Declined → cancelled, hard-stopped, only planning billed.
- No decision within 24 hours → auto-cancelled.
For zero-touch pipelines, enable auto-approve plans on the key — runs never pause.
{
"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.
| event | when |
|---|---|
| run.completed | Run finished successfully. |
| run.failed | Internal error (partial work billed). |
| run.cancelled | Cancelled / declined plan / approval timeout. |
| run.interrupted | Orphaned by a restart — safe to retry. |
| run.requires_approval | Paused 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.
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.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 */ }
}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 · code | When / what to do |
|---|---|
| 401 invalid_api_key | Unknown, revoked or expired key. Rotate via Admin → API Keys. |
| 402 insufficient_credits | Balance ≤ 0 at creation. Top up; retry with the same Idempotency-Key. |
| 404 not_found | Run or thread does not exist in your organisation. |
| 404 workflow_not_found | Unknown or deactivated workflow slug. |
| 409 idempotency_key_reuse | Same key, different body — use a fresh key for new requests. |
| 409 not_awaiting_approval | Approval sent to a run not awaiting one. |
| 409 not_cancellable | Cancel sent to a run already terminal. |
| 422 invalid_request | Validation failure; the message names the field. |
| 429 rate_limited | Respect the Retry-After header. |
| 500 internal_error | Run marked failed; work performed is billed. Retry with a fresh key. |
{
"error": {
"code": "insufficient_credits",
"message": "Organisation has no credits remaining",
"run_id": "run_9f3a2c…" // when applicable
}
}Reference
Rate limits
| Limit | Default | On breach |
|---|---|---|
| Run creations per key | 60 / minute | 429 + Retry-After: 60 |
| Concurrent active runs per key | 10 (queued + running; parked approvals don’t count) | 429 + Retry-After: 10 |
| Reads (GET) | 600 / minute per key | 429 + 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