> ## Documentation Index
> Fetch the complete documentation index at: https://docs.enrichloops.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Tasks, polling and errors

> Every enrichment is a task. Here is what one looks like, how to poll it, and what each error code means.

Every enrichment endpoint returns a task. The task is your handle on the work: poll it for the result, match it to a webhook delivery, or list it later.

## The task object

```json theme={null}
{
  "task": {
    "id": "8bf3d449-e013-4faa-8994-a6d25031e001",
    "object_type": "company",
    "operation": "enrich",
    "status": "succeeded",
    "target": "stripe.com",
    "credits_charged": 1,
    "loop_id": null,
    "idempotency_key": null,
    "created_at": "2026-08-07T23:21:32.481Z",
    "finished_at": "2026-08-07T23:21:32.520Z",
    "expires_at": "2026-08-07T23:31:32.481Z"
  }
}
```

| Field             | Meaning                                                                         |
| ----------------- | ------------------------------------------------------------------------------- |
| `object_type`     | `company` or `person`.                                                          |
| `status`          | `queued`, `running`, `succeeded`, `failed` or `canceled`.                       |
| `target`          | The normalized input we resolved: the domain, LinkedIn URL or email you sent.   |
| `credits_charged` | What this task cost. Refunded if the task fails.                                |
| `loop_id`         | The Loop this task delivers to, if any.                                         |
| `expires_at`      | If the task is still running past this time, we mark it `failed` and refund it. |

## 200 vs 202

An enrichment endpoint responds `200` when the task settled inside the request, and `202` when it was queued. Both bodies have the same shape. Neither carries the enrichment result. Branch on `task.status`, not on the HTTP status.

The only time an enqueue response carries more than the task is a synchronous failure. Then `error` is present, with the same error codes listed below.

## Polling

```bash theme={null}
curl "https://api.enrichloops.com/v1/tasks/8bf3d449-e013-4faa-8994-a6d25031e001" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```json theme={null}
{
  "task": { "...": "...", "status": "succeeded" },
  "data": { "name": "Stripe", "domain": "stripe.com", "...": "..." }
}
```

* `data` is present once `status` is `succeeded`.
* `error` is present once `status` is `failed`.
* While the task is `queued` or `running`, the response carries `Retry-After: 2`. Wait 2 seconds between polls.

A task that belongs to another organization returns `404`, the same as a task that does not exist.

If you would rather not poll, send the request through a [Loop](/loops-and-webhooks) and we push the result to your webhook.

## Listing tasks

```bash theme={null}
curl "https://api.enrichloops.com/v1/tasks?status=failed&page=0&pageSize=25" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Filter by `status`. Pages are zero-indexed, 25 per page by default.

## Error codes

A failed task carries a stable `error.code`. Branch on it:

| Code                   | Meaning                                                                                                       |
| ---------------------- | ------------------------------------------------------------------------------------------------------------- |
| `not_found`            | We could not find or resolve the company or person. For emails, this also covers refused addresses.           |
| `ambiguous`            | Email lookups only. The address fits more than one person. See [reverse email lookup](/reverse-email-lookup). |
| `provider_unavailable` | A temporary issue on our side stopped us. Safe to retry.                                                      |
| `timed_out`            | The enrichment did not finish in time. Safe to retry.                                                         |
| `invalid_input`        | The request was malformed. For example, neither `domain` nor `linkedinUrl` was provided.                      |
| `internal_error`       | Something went wrong on our side.                                                                             |

The same codes appear whether you poll the task or receive it on a webhook.

## Idempotency keys

Send an `Idempotency-Key` header (up to 255 characters) on any enrichment request. Reusing the key returns the original task instead of creating and charging a new one. Use it to retry a request whose response you never received.

A key is bound to the body it was first sent with. Reusing it with a different body gets `422` with `code: "idempotency_key_reused"`. Nothing is charged and no task is created. Retry with a new key.

## Credits

| Request                           | Cost      |
| --------------------------------- | --------- |
| Company enrichment                | 1 credit  |
| Person enrichment by LinkedIn URL | 1 credit  |
| Person enrichment by email        | 3 credits |

A few rules that never change:

* A failed task is refunded. You only pay for results.
* A cache hit costs the same as a fresh fetch. So does a refresh of a stale record.
* Sending through a Loop costs the same as calling the endpoint directly.

If your balance runs out, requests get `402` with `code: "insufficient_credits"` or `code: "credits_expired"`.

## Rate limits

Two limits apply: one per client address and one per API key. When you hit either, you get `429` with a `Retry-After` header saying how many seconds to wait.

A `429` with `code: "queue_full"` is different. Neither limit was exceeded. Your organization's backlog of queued tasks is full. Let in-flight tasks drain rather than slowing down your request rate. The body carries `queuedTasks` so you can see how deep the backlog is.

There is no hard cap on requests in flight. Very large bursts are processed in the order you sent them. Another customer's bulk upload does not block your requests.
