> ## Agent Instructions
>
> Base URL: https://api.anysite.io
> Authentication: send the `access-token` header. Do NOT use `Authorization: Bearer`.
> Full endpoint catalog: https://app.anysite.io/docs
# Errors and limits

Every response the Anysite API can return, what each status means, what is charged, timeouts, paging and rate limits.

Goal: handle every response the API can give and know which calls cost credits.

## A successful response

The body of a successful data call is **always a JSON array**, even on an endpoint that returns a
single entity — `linkedin/user` returns a list with one item. A search that matched nothing is a
normal `200` with `[]`.

The `x-result-count` header counts the rows in the body you received, so it never exceeds the array.
To learn how much more exists beyond what you asked for, read `X-Total-Available-Results` on the
endpoints whose source reports a usable total.

## How a call is processed

Checks run in this order; the first one that fails returns its error and nothing is charged:

1. Auth header present, exactly one → else `422`.
2. Key valid, plan not ended, key allowed on the REST API → else `401` / `403`.
3. Endpoint available for your plan and parameters within your plan's maximum → else `404` / `529` / `403` / `400`.
4. Enough credits on the balance → else `401`.
5. Rate limits and usage windows → else `429`.
6. The request runs on the endpoint; its response is returned to you, and is charged by the rules in
   [What is charged](#what-is-charged).

## Statuses returned before the call runs

These come from Anysite's billing layer, before the endpoint is reached. None of them is charged, and
their body is `{"detail": ...}`:

```json
{"detail": "Token expired"}
```

`422` returns a list of field errors instead, and `429` returns an object plus an `X-Retry-After`
header with the same number of seconds:

```json
{"detail": {"message": "Rate limit exceeded: ...", "retry_after": 12}}
```

| Status | `detail` | What to do |
|---|---|---|
| `422` | a required header is missing | Send the `access-token` header. |
| `422` | `Body should be a valid dict object` | Send a JSON object as the body, not an array or a scalar. |
| `401` | `Invalid token` | Copy the key again from the Billing page. See [Authentication](/docs/authentication). |
| `401` | `Token does not exist` | The key was deleted; use another key. |
| `401` | `Token expired` | The plan has ended; renew it on the Billing page. |
| `401` | `Points limit exhausted, required at least N points` | Add credits or upgrade the plan. See [Plans and credits](/docs/plans-and-credits). |
| `403` | `This token is restricted to MCP usage only and cannot be used for direct API access` | Use the [MCP server](/docs/mcp/overview), or a plan that includes the REST API. |
| `403` | `This endpoint is disabled for your plan` | Use a plan that includes this endpoint. |
| `400` | `Parameter 'count' exceeds the maximum allowed for your plan: N` | Lower `count` to N or less, or page through results. |
| `404` | `Endpoint was removed` | The endpoint no longer exists; find its replacement in the [API reference](/docs/api). |
| `529` | `This endpoint is temporarily unavailable while we work on a fix. ...` | We switched this endpoint off. Retrying does not help; open a ticket from the Support page if you depend on it. |
| `429` | object, `Rate limit exceeded: scope=..., limit=N requests <window>. ...` | Wait `retry_after` seconds. See [Rate limits](#rate-limits). |
| `429` | object, `You've reached your <window> usage limit. ...` | Wait for the window to free up or upgrade. See [Usage windows](#usage-windows). |

A `429` always comes from Anysite's rate limiter — the sources behind an endpoint never produce one.

## Statuses from the endpoint

Once the checks pass, the endpoint's own status and body are returned to you unchanged. **An endpoint
error usually answers with an empty JSON array `[]`, but a call that had already collected rows
returns them alongside the error status; the reason is in the `x-error` header**, which is set only
when the status is `400` or higher:

```
HTTP/1.1 412
x-error: Failed to fetch URL. Website is not accessible
x-result-count: 0

[]
```

The exception is validation: a malformed body is rejected with `422` and a `detail` object naming the
field.

| Status | Meaning |
|---|---|
| `200` | Success. The body is an array, possibly empty. |
| `209` | Looked and found nothing — a free miss. Only on the endpoints listed under [What is charged](#what-is-charged). |
| `400` | Your client disconnected before the answer was ready. |
| `408` | The call ran out of time. See [Timeouts](#timeouts). The body may still carry the rows collected before the deadline. |
| `412` | The API's "not found": the entity does not exist, the profile is private or gated, or (on `webparser/*`) the target host is unreachable. |
| `415` | `webparser/*` was pointed at a PDF, Office document, image, archive or other non-page file. |
| `422` | Schema validation failed (`detail` names the field), a LinkedIn URN was malformed (`detail` has `"type": "linkedin_wrong_urn"`), or the response was too large to return. |
| `500` | The source is out of reach, or retries against it were exhausted. |
| `529` | All scraping capacity for that source is busy right now. |

Each endpoint in the [API reference](/docs/api) publishes only its **non-obvious** statuses — for
example that `linkedin/user` answers `412` where you might expect `404`. A status missing from that
list is not impossible; the meanings above always apply.

A gated LinkedIn profile answers `412` for as long as it stays gated — retrying the same alias will
not change the answer.

## What is charged

In words:

- A `200` is charged, **including a search that returned an empty array**. You paid for the lookup,
  not for the rows.
- A `412` — the "not found" answer — **is charged**. The work of looking was done.
- A `408` timeout is charged, and may still return partial rows.
- A `209` is **free**. It exists only on these endpoints: `linkedin/search/sql/users`,
  `linkedin/search/sql/companies`, `crunchbase/db/search`, `linkedin/user/find_email_by_url`,
  `person/by_email`, `person/by_phone`, `phone/find`, `email/find`, `company/resolve`. On those same
  endpoints some misses answer `412` instead and are charged — you cannot predict which of the two
  you get, so read `x-credits-charged`.
- Any other error — `400`, `415`, `422`, `500`, `529` — is charged **only if the response still
  carried results**. A failure that returned rows is a paid call; the same failure with an empty body
  is free. `x-result-count` on the response tells you which one you got.
- Nothing is charged when the request never reached the endpoint: the checks above, and the
  `{"error": "Internal server error"}` we return when the endpoint could not be reached at all.

| Endpoint response | Charged |
|---|---|
| Any status below `400` except `209` | Yes |
| `209` | No |
| `408`, `412` | Yes |
| Any other `4xx` / `5xx` | Only if the response reports at least one result |
| Any error from the checks above, or `500` "Internal server error" when the endpoint was unreachable | No |

The price of a call depends on the endpoint and your plan, and on many endpoints it grows with the
number of results: the base price is charged for every started block of results, so an endpoint priced
per 10 results charges twice for 11 rows and three times for 21. Where they are published, both
numbers are in the [API reference](/docs/api) as `x-price` (the base price) and `x-increase-every`
(the block size; absent means one flat price per call) — some endpoints publish neither, and their
price lives only in our billing records. Ask for the `count` you actually need: a larger `count` can
cost more. Asking for less of a profile can cost less too — on `linkedin/user`, every `with_*`
section you turn off lowers the price of the call, and `fast_mode` raises it.

Whatever the reference says, the amount actually taken for a call is the `x-credits-charged` header —
`0` means the call was free — and it is in the request logs on the Billing page.

Credits are taken from the plan's credits first, then from purchased credits. A call is allowed when
the balance covers its base price; if the final price is higher (for example because more results
came back), the balance can go below zero, and the negative amount is subtracted from credits you add
later.

## Headers worth reading

| Header | What it tells you |
|---|---|
| `x-credits-charged` | Credits this call cost. `0` means it was free. |
| `x-token-points` | Credits left on the balance. |
| `x-result-count` | Rows in this response — the length of the array you received. |
| `x-error` | Why the call failed. Present only on `400` and above. |
| `x-warning` | Something was off with the request although it succeeded — see [Ignored parameters](#ignored-parameters). |
| `x-execution-time` | How long the endpoint worked, in seconds. |
| `x-request-id` | The id of this call. Quote it in a support ticket. You may send it yourself, but it must be a valid UUID or the request is rejected with `422`. |
| `X-Total-Available-Results` | How many results are reachable in total, on endpoints whose source reports a usable total. |

## Timeouts

The `timeout` field in the body defaults to **300 seconds** and accepts 20-1500.

It is a **stall budget, not a total duration**. A call that has not produced its first result is cut
with `408` once `timeout` elapses. After results start arriving, each new one pushes the deadline out
to about a minute after it — less, if your `timeout` is shorter than that — up to a hard ceiling of
1500 seconds. So a call that keeps producing rows may legitimately run far longer than its `timeout`,
while a call that stops producing them is cut shortly after it stalls.

Because of that ceiling, **set your HTTP client's own timeout to about 1600 seconds** on endpoints
that can run long. A client that gives up at 30 or 60 seconds abandons calls that were still working.

```bash
curl --max-time 1600 -X POST "https://api.anysite.io/api/linkedin/search/users" \
  -H "access-token: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"keywords": "cto", "count": 100, "timeout": 600}'
```

> ACTION FOR THE HUMAN: copy your API key at https://app.anysite.io/billing — an agent cannot obtain it.

## Ignored parameters

**Unknown keys in the body are ignored, not rejected.** A misspelled filter therefore behaves exactly
like no filter: you get `200` with unfiltered results, and you pay for them. Every ignored key is
listed in the `x-warning` header (and in `warnings` in an MCP result), so check that header whenever a
filter appears to do nothing.

`count` is required on every endpoint that can return more than one result, and it is a maximum, not a
promise: asking for more than exists returns `200` with fewer rows.

## Paging

Most endpoints have no paging parameter at all: they take only `count` and page through the source
for you, so ask for the number of results you need in one call. A minority do take `offset` or
`page` — the [API reference](/docs/api) shows which — and there you advance `offset` by `count`, or
increment `page`, on each call. Repeating such a call without advancing returns the same rows, which
is the usual cause of "the API keeps giving me duplicates".

## Retrying

- `429`: wait `retry_after` seconds (also in `X-Retry-After`) before retrying.
- `529` from an endpoint: capacity for that source is busy. Retry later and spread your calls out
  rather than firing them in parallel.
- `500`: the source was unreachable or gave up. A source being briefly unavailable is normal; retry
  after a wait, and open a ticket from the Support page if one source stays broken while others work.
- Both are free when they come back empty, and charged when they still carried results — check
  `x-credits-charged`.
- `408`: the call was charged. Retrying charges again — narrow the request instead.
- `412` on a guessed identifier: retrying returns the same answer and is charged again. Find the
  entity with a search endpoint first.
- `401`, `403`, `400`, `404`, `422` from the checks: retrying the same request returns the same error.

Transport failures between Anysite and the source are already retried for you, with backoff, before
you ever see an error. There is no idempotency key: a call you retry yourself is charged again if it
is a charged status.

## An endpoint can be switched off

An endpoint may appear in the [API reference](/docs/api) while it is switched off. You recognise it
by the answer arriving before the endpoint runs: a `404` `Endpoint was removed`, or a `529` whose
`detail` says the endpoint is temporarily unavailable while we work on a fix. Retrying does not help;
open a ticket from the Support page if you depend on it.

A `529` that carries no such `detail` is a different thing — the endpoint ran and the scraping
capacity for that source is busy. Retry it later.

## Rate limits

Plans, and some endpoints, limit the number of requests over rolling windows (10 seconds, minute,
hour, day or 30 days). Every request that passes the checks before it counts toward these limits,
whether or not it ends up charged — a free `209` included. When a limit is hit, the API returns `429`
with `retry_after` equal to the seconds until the oldest request in the window expires.

## Usage windows

Some plans, including MCP plans, also cap the credits spent over two rolling windows: **5 hours** and
**weekly**. Some plans add separate LinkedIn 5-hour and weekly windows, which count only `linkedin/*`
endpoints and skip the database ones whose path contains `/sql/`. Usage is the sum of credits charged
within the window, so it frees up gradually as older
calls leave the window.

When a window is full, the API returns `429` with `retry_after` equal to the full window length — the
longest possible wait. Usage may free up sooner.

For an MCP plan, the current usage of each window is shown as a percentage on the MCP page.

> ACTION FOR THE HUMAN: open https://app.anysite.io/mcp (Open MCP usage).
