Async requests, polling & webhooks
Most Nyne.ai data endpoints are asynchronous. A POST validates your
request, reserves credits, and enqueues the work, then returns 202 with a request_id. The result is not in that response - you poll the same path
until it turns 200, or hand over a callback_url and let a webhook deliver it. This guide covers the request lifecycle,
correct polling, and callback webhooks.
- queued202
Accepted and enqueued. Nothing has run yet.
- processing202
The worker is enriching. Keep polling.
- completed200
A match.
completed: true, result attached.failed200No match or an error.
completed: true, no result.
Submit, 202, poll
A POST to a data endpoint validates your input, reserves credits,
and enqueues background work. It returns 202 Accepted with a request_id - the enrichment request hash that identifies this one
job. The result is not in that response; the queue reply carries status: "queued".
curl -X POST https://api.nyne.ai/person/enrichment \ -H "X-API-Key: YOUR_API_KEY" \ -H "X-API-Secret: YOUR_API_SECRET" \ -H "Content-Type: application/json" \ -d '{ "email": "[email protected]" }'
The 202 body hands you the request_id to poll with next:
{
"success": true,
"data": {
"request_id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4_1717000000_4271",
"status": "queued",
"message": "Enrichment request queued. Use GET /person/enrichment?request_id=... to check status."
},
"timestamp": "2026-05-28T18:04:11"
}The lifecycle statuses
The submit/queue reply always says queued. From then on, the status
endpoint returns one of four caller-visible strings: pending, processing, completed, or failed. A representative progression is queued -> processing -> completed.
- The terminal string is
completed(never"done"). A request that finds no match finishes asfailed, notcompleted. - Once a request is terminal (
completedorfailed),completed: trueappears in the body and the response is200. While still in flight you keep getting202.
You only ever see the normalized statuses
provider_pending becomes processing and provider_failed becomes failed), so you never have to reason about a provider's own
vocabulary. See Interpreting responses for the full status model.Poll for the result
Poll by sending a GET to the same path you POSTed to, with ?request_id=.... You
get 202 while the job is in progress and 200 once it is terminal.
curl "https://api.nyne.ai/person/enrichment?request_id=a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4_1717000000_4271" \ -H "X-API-Key: YOUR_API_KEY" \ -H "X-API-Secret: YOUR_API_SECRET"
Poll once a second, and back off
A terminal 200 carries the merged, sanitized result inside the same
envelope, with status: "completed" and completed: true:
{
"success": true,
"data": {
"request_id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4_1717000000_4271",
"status": "completed",
"completed": true,
"result": {
"displayname": "Jane Doe",
"headline": "VP of Product at Acme",
"location": "San Francisco, CA",
"best_business_email": "[email protected]"
},
"error": null,
"created_on": "2026-05-28T18:04:11",
"completed_on": "2026-05-28T18:04:39"
},
"timestamp": "2026-05-28T18:04:45"
}A status lookup itself can fail. The request must belong to the authenticating key, the request_id must be known, and the lookup store must be reachable:
| error.code | HTTP | Meaning |
|---|---|---|
| access_denied | 403 | The request_id belongs to a different API key. |
| request_not_found | 404 | No request matches that request_id. |
| service_unavailable | 503 | A transient lookup issue. Back off and retry. |
Webhooks with callback_url
Instead of polling, supply a callback_url on the submit. When the
job completes, the worker POSTs the same result envelope to your URL:
curl -X POST https://api.nyne.ai/person/enrichment \ -H "X-API-Key: YOUR_API_KEY" \ -H "X-API-Secret: YOUR_API_SECRET" \ -H "Content-Type: application/json" \ -d '{ "email": "[email protected]", "callback_url": "https://hooks.example.com/nyne" }'
The callback URL is validated before the request is accepted. It must be http(s)://, at most 1024 characters, and carry no userinfo (no user:pass@ in the URL). It is also checked against an allowed-hosts
policy that rejects localhost, private, and reserved addresses, along with .local, .internal, .lan, .home, .corp, .test, .invalid, and .example hosts. An invalid target is rejected at submit time with 400 invalid_callback_url.
Handle deliveries idempotently
success, data (with the terminal status and result), and timestamp. Verify and process it idempotently, keyed on the request_id, so a retried delivery is safe.Which calls are synchronous
Not everything is async. A few calls return 200 directly, with no request_id and nothing to poll:
POST /person/enrichment/feedbackis a single insert that returns200immediately.- The status-lookup
GETs themselves are synchronous, and they are not credit-gated - polling never costs credits.
Idempotency and durability
The request_id is the durable handle to one job. Re-polling it is
safe and cheap and does not re-charge credits - credits are deducted once, on completion, after
a meaningful result. So a poll loop, a retried poll, or a redelivered webhook never bills you twice for the same
request.
One job, one charge
request_id after a restart, retry a dropped poll, or accept a duplicate callback without worrying
about double-charging.What's next
- Rate limits - how throttles work so your poll loop stays under them.
- Interpreting responses - the envelope, the status model, and how normalized statuses are derived.
- Credits - when a request is charged and what counts as a meaningful result.
- Your first request - the end-to-end walkthrough this guide builds on.