# Async requests, polling & webhooks

> Most Nyne.ai data endpoints are asynchronous: a POST returns 202 with a request_id, then you poll the same path until it turns 200 - or hand over a callback_url and let a webhook deliver the result. This guide covers the request lifecycle, correct polling, and callback webhooks.

HTML version: https://api.nyne.ai/documentation/async

The lifecycle every async data endpoint follows: `POST /person/enrichment` -> `202` with a `request_id` and `status: "queued"` -> `GET ?request_id=` (poll) -> `200` with a terminal status (`completed` or `failed`).

## 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 (for example `a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4_1717000000_4271`). The result is NOT in that response; the queue reply carries `status: "queued"`.

```bash
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:

```json
{
  "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 as `failed`, not `completed`.
- Once a request is terminal (`completed` or `failed`), `completed: true` appears in the body and the response is `200`. While still in flight you keep getting `202`.

Internally, provider statuses are normalized onto these values (for example `provider_pending` becomes `processing` and `provider_failed` becomes `failed`), so you only ever see the normalized statuses. See [Interpreting responses](https://api.nyne.ai/documentation/responses.md) 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.

```bash
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"
```

Enrichment usually finishes in well under a second, so the first poll often already returns the result. Poll roughly once per second with a sensible max-wait; do not hammer the endpoint. Polls count toward your throttles, so see the [Rate limits](https://api.nyne.ai/documentation/rate-limits.md) guide before tightening the loop.

A terminal `200` carries the merged, sanitized result inside the same envelope, with `status: "completed"` and `completed: true`:

```json
{
  "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:

| 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:

```bash
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`.

The delivered webhook payload is the same envelope your poll would have returned - `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/feedback` is a single insert that returns `200` immediately.
- The status-lookup `GET`s 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.

## What's next

- [Rate limits](https://api.nyne.ai/documentation/rate-limits.md) - how throttles work so your poll loop stays under them.
- [Interpreting responses](https://api.nyne.ai/documentation/responses.md) - the envelope, the status model, and how normalized statuses are derived.
- [Credits](https://api.nyne.ai/documentation/credits.md) - when a request is charged and what counts as a meaningful result.
- [Your first request](https://api.nyne.ai/documentation/first-request.md) - the end-to-end walkthrough this guide builds on.
