# Async Polling

Webhook delivery is the primary path for async search/enrichment results. Polling is the fallback when your webhook receiver is unavailable, when you need to fetch results out-of-band, or for debugging.

**Outreach Emails are the exception**: they require no webhook secret and are designed to be polled — `GET /api/public/v1/emails/{id}/` is the live resource itself (not a job wrapper), so everything below about `dj_` jobs doesn't apply to `em_` emails. See [Outreach Emails](https://docs.clodo.ai/api-reference/endpoint/outreach-emails). While drafting, poll every 5–10 seconds; drafts typically complete in 10–60 seconds.

## Polling URLs

Use the job `id` returned by the original `POST` (e.g. `dj_a1b2c3d4...`):

| Endpoint | Polling URL |
|---|---|
| People Search | `GET /api/public/v1/search/{job_id}/` |
| Deep Search | `GET /api/public/v1/deep-search/{job_id}/` |
| Agentic Search | `GET /api/public/v1/agentic-search/{job_id}/` |
| Phone Enrichment | `GET /api/public/v1/enrich/phone/{job_id}/` |

Polling does not consume credits.

## Response shape

```json
{
  "id": "dj_a1b2c3d4...",
  "status": "completed",
  "tier": "light",
  "created_at": "2026-05-02T06:39:06.013878Z",
  "started_at": "2026-05-02T06:39:06.079964Z",
  "completed_at": "2026-05-02T06:39:42.009761Z",
  "leads_returned": 25,
  "result": { ... },
  "error": null
}
```

## Field reference

| Field | Type | Notes |
|---|---|---|
| `id` | string | `dj_<32-hex>` job identifier. |
| `status` | string | One of `pending`, `running`, `completed`, `failed`, `cancelled`. |
| `tier` | string | One of `light` (People Search), `deep_standard`, `deep_extended` (Deep Search), `agentic_search`, `phone_enrich`. |
| `created_at` | ISO 8601 | When the job was accepted. |
| `started_at` | ISO 8601 or `null` | When the worker picked it up. |
| `completed_at` | ISO 8601 or `null` | When the worker reached a terminal state. |
| `leads_returned` | int | Final lead count. `0` until terminal. |
| `result` | object or `null` | Populated only when `status == "completed"`. |
| `error` | object or `null` | Populated only when `status` is `failed` or `cancelled`. |

## `result` shape (when completed)

The `result` object carries the same body the webhook would have delivered — minus the `id` / `event_type` wrapper. Schemas:

- People Search: `{results: [...], total_returned: int}`
- Deep Search: `{results: [...], total_returned: int}`
- [Agentic Search](https://docs.clodo.ai/api-reference/endpoint/agentic-search): `{results: [...], total_returned: int, partial: bool, completion_reason: string}`
- Phone Enrichment: `{phone: string | null}`

Per-lead and per-field shapes are documented under [Webhook Events](https://docs.clodo.ai/guides/webhook-events).

## `error` shape (when failed or cancelled)

```json
{"code": "error", "message": "..."}
```

Codes and messages are the same as the webhook `*.failed` events — see [Webhook Events](https://docs.clodo.ai/guides/webhook-events) for the per-endpoint tables.

## Status lifecycle

```
pending → running → completed
                  → failed
                  → cancelled
```

`pending` is the initial state right after the `POST` returns 202. The worker transitions it to `running` when it picks up the job, then to one of the three terminal states.

Agentic Search can also move directly from `pending` to `cancelled`. Cancelling a running Agentic Search normally finishes as `completed` with `result.partial: true` and `result.completion_reason: "user_cancelled"`. Always inspect the result's partial flag when processing completed Agentic Search jobs.

## Polling cadence

Reasonable defaults:

- People Search: poll every 5–10 seconds. Typical run completes in 15–30 seconds.
- Phone Enrichment: poll every 10-15 seconds. Typical run completes in 1 minute.
- Deep Search: poll every 30–60 seconds. Typical run completes in 10–15 minutes.
- Agentic Search: poll every 30–60 seconds until terminal; duration depends on the research brief and budget.

Polling endpoints have a high rate cap (3600/min) so brief tight-loop polling will not throttle.

## Wrong job ID

A `GET` against an unknown or malformed `job_id` returns `404 not_found` in the canonical envelope.

## See also

- [Webhook Events](https://docs.clodo.ai/guides/webhook-events) for the inner result shape and per-endpoint payloads.
- [Webhook Signing Secrets](https://docs.clodo.ai/guides/webhook-secrets) and [Webhook Signature Verification](https://docs.clodo.ai/guides/webhook-signing) for the primary delivery path.
- [Idempotency](https://docs.clodo.ai/guides/idempotency) for replaying without double-dispatch.
