Async requests, polling & webhooks
Most Nyne.ai data endpoints are asynchronous. A POST validates your
request, checks account access and available credits when applicable, 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 returns a terminal response, or hand over a callback_url and let a webhook deliver it. This guide covers the request lifecycle,
correct polling, terminal errors, and callback webhooks.
- queued202
Accepted and enqueued. Nothing has run yet.
- processing200 or 202
Check
completedand keep polling. - completed200
A match.
completed: true, result attached.failed / error200 or 4xx/5xxEndpoint-specific terminal failure. Stop polling that request.
Submit, 202, poll
A POST to a data endpoint validates your input, checks account
access and available credits when applicable, and enqueues background work. It returns 202 Accepted with a request_id that identifies this one job. The result is not in that response; the acknowledgement carries a non-terminal status such as queued or processing.
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": "jane.doe@acme.com" }'
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 contains a non-terminal status such as queued or processing.
From then on, the status endpoint commonly returns: pending, processing, completed, or failed. A representative progression is queued -> processing -> completed.
- Most endpoints use
completedfor terminal success (never"done"). Some endpoint pages document additional successful terminal values. For example, People Search can returnexhaustedorstalewithcompleted: true. - A successful poll may use
200or202while work is still in flight. When a successful payload includescompleted, keep polling while it is false and stop when it is true; otherwise use the endpoint's status table. HTTP200alone does not prove completion. A terminal error envelope or non-retriable non-2xx processing/billing response also stops that request; transient transport/status-infrastructure responses may be retried using the endpoint's documented backoff policy.
Use only documented status values
Poll for the result
Poll by sending a GET to the same path you POSTed to, with ?request_id=... and
any additional page values required by that endpoint. You may get 200 or 202 while the job is in progress. When completed is present on a successful response, continue while it
is false and stop when it is true. Otherwise follow the endpoint's status table. Any terminal error envelope or
non-retriable non-2xx job response also stops that request; retry transient transport/status-infrastructure
responses only as documented by the endpoint.
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 successful terminal payload 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": "jane.doe@acme.com"
},
"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.
Treat authentication, authorization, unknown-request, insufficient-credit, and terminal processing errors as
settled. Retry a 429 poll only when the poll itself was throttled;
a 429 stored as the job's terminal status ends that request. Back off
and retry transient service-unavailable responses:
| error.code | HTTP | Meaning |
|---|---|---|
| invalid_credentials | 401 | Authentication failed. Fix credentials before retrying. |
| insufficient_credits | 402 | The job cannot continue until the account has enough credits. |
| access_denied / ip_not_allowed | 403 | The authenticated account or request IP is not allowed. |
| request_not_found | 404 | No request matches that request_id, including IDs owned by another API key. |
| rate_limit_exceeded | 429 | If the poll itself was throttled, honor Retry-After and retry it. If this is the job's stored terminal status, stop polling that request_id and submit a new request after the limit resets. |
| search_error / internal_error | 500 | Terminal processing failure unless the endpoint says otherwise. |
| 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, Nyne.ai 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": "jane.doe@acme.com", "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.Person Search has endpoint-specific pagination behavior. Its callback can report queued session work, but it is
not a per-page pagination mechanism. After a successful callback it is not re-emitted for every later page, and
an immediately cached page POST does not enqueue one. The exact GET /person/search request with that page's request_id, offset, and limit remains the authoritative retrieval path. See Person Search.
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 with
the documented status GET is safe and does not re-charge credits. A poll loop, a retried GET poll, or a redelivered webhook does not
create another charge. People Search is the pagination exception: multiple page POSTs reuse one request_id, and each successfully fulfilled page POST is billable;
use GET, not POST, for status checks and stored-page reads.
Status GETs are free
request_id after a restart, retry a dropped GET poll, or accept a duplicate callback. Do not replace
a status GET with another page POST on People Search.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.