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

# Limits and errors

> Rate limits, the monthly search allowance, and every error the Periphery API returns, with its code.

## Rate limit

Your account may make one API request per second, across both endpoints; console searches and [MCP server](https://docs.periphery.exposed/mcp/overview.md) calls count against the same limit. A refused request uses no search. Over the limit, the API answers `429` with `Retry-After: 1`:

```json theme={null}
{"error": "rate limit: 1 request per second", "code": "rate_limited"}
```

Space requests one second apart, or wait for `Retry-After` and retry on this error. There is no burst allowance.

Requests are also capped at ten per second per IP address, before the key is checked, so several keys used from one machine share that ceiling. Over it, the API answers `429` with the code `ip_rate_limited` and `Retry-After: 1`.

## Monthly searches

Every search request (`GET /v1/apps?q=…`) that runs a search counts against your account's monthly allowance: 3,000 searches on Plus, shared with your console and MCP searches. Each page is a separate search, so reading pages 1 to 3 of a query uses three. The allowance resets at 00:00 UTC on the 1st of each month. A request with a missing or blank `q` answers `400 query_required` and uses no search:

```json theme={null}
{"error": "q is required: give a search query", "code": "query_required"}
```

A `q` that reads as nothing, such as `status:2oo` alone, returns an empty page whose `query` is `""` and uses no search either.

Once the allowance is used up, search answers `429` until the next reset:

```json theme={null}
{"error": "monthly search limit reached", "code": "monthly_limit_reached"}
```

This `429` has no `Retry-After`: retrying will not help before the 1st. Stop searching until then; `GET /v1/apps/{hostname}` keeps working. See [Plans and limits](https://docs.periphery.exposed/plans.md#monthly-searches) for what counts.

The `has:creds` filter always applies to API queries: API access and the filter are both part of the Plus plan.

## Which requests use a search

A search is used only when a `GET /v1/apps?q=…` request has passed every check, the monthly limit included, and the search has started.

* **Never use one:** `GET /v1/apps/{hostname}`, a `q` that reads as nothing, and every `400` (a missing or blank `q` included), `401`, `403`, `404`, `405` and `429`.
* **Use one:** a `502`, `503 busy` or `504` from a search once it has started. Count it as used. A `504` is remembered for a minute: retrying the same query and page within that minute returns the same `504` and uses another search.
* `503 maintenance` uses none: the request never reached Periphery.

## Error format

Errors are JSON objects with two strings, a human-readable `error` and a stable `code`:

```json theme={null}
{"error": "app not found", "code": "app_not_found"}
```

Switch on `code`, not on the message: messages may change, and new codes may be added. Error responses carry `Content-Type: application/json` and `Cache-Control: no-store`, like every other response.

> [!WARNING]
>
> Any response other than `200` means the result is unknown. Never read an error as "no results": an empty result is a `200` with `"hits": []`.

## Error codes

| Status | Code | Message | Headers | Endpoint | Meaning |
| - | - | - | - | - | - |
| `400` | `query_required` | `q is required: give a search query` | | `/v1/apps` | `q` is missing or blank. Send a query. |
| `400` | `query_too_long` | `query too long: up to 256 characters` | | `/v1/apps` | Over 256 characters, counted as Unicode characters rather than bytes. Shorten the query. |
| `400` | `too_many_terms` | `query too long: up to 10 terms (this one has 12)` | | `/v1/apps` | Over 10 terms; the message gives your query's count. Shorten the query. |
| `400` | `page_out_of_range` | `page is past the last page (20000)` | | `/v1/apps` | `page` is above 20,000, the last page. |
| `401` | `api_key_missing` | `missing API key` | `WWW-Authenticate: Bearer realm="api.periphery.exposed"` | both | No key header. |
| `401` | `api_key_invalid` | `invalid API key` | `WWW-Authenticate: Bearer realm="api.periphery.exposed", error="invalid_token"` | both | Unknown or rotated key. |
| `403` | `plan_required` | `API access requires the Plus plan` | | both | The account is on Free. |
| `403` | `https_required` | `HTTPS required: use https://api.periphery.exposed. A key sent over plain HTTP travelled in clear text: rotate it.` | | both | The request used `http://`. Use `https://` and [rotate the key](https://docs.periphery.exposed/api/authentication.md#rotate-a-key). |
| `404` | `app_not_found` | `app not found` | | `/v1/apps/{hostname}` | No app under that hostname that search can return: never visited, marked dead, unknown, or not a valid hostname. |
| `404` | `not_found` | `not found` | | any other path | The path is not part of the API. |
| `405` | `method_not_allowed` | `method not allowed` | `Allow: GET` on `/v1/apps`, `Allow: GET, HEAD` on `/v1/apps/{hostname}` | both | Checked before the key, and never uses a search. |
| `429` | `ip_rate_limited` | `rate limit: too many requests from this address` | `Retry-After: 1` | both | More than ten requests per second from one IP. Slow down. |
| `429` | `rate_limited` | `rate limit: 1 request per second` | `Retry-After: 1` | both | More than one request per second from the account. Slow down. |
| `429` | `monthly_limit_reached` | `monthly search limit reached` | | `/v1/apps` | The month's searches are used up. Stop until 00:00 UTC on the 1st; do not retry. |
| `502` | `unavailable` | `Periphery is temporarily unavailable, try again in a moment` | `Retry-After: 5` | both | Periphery or its database failed, or is down. Retry after the delay. |
| `503` | `busy` | `Periphery is busy, try again in a moment` | `Retry-After: 2` | `/v1/apps` | Too many searches running across Periphery. Retry after the delay. |
| `503` | `maintenance` | `Periphery is temporarily unavailable for maintenance, try again later` | `Retry-After: 3600` | both | Planned maintenance. Retry later. |
| `504` | `timeout` | `Search took too long, narrow it with more words or a filter` | | `/v1/apps` | The query ran past its time limit. Narrow it rather than retrying it as is: a retry within a minute returns the same `504` and uses another search. |

Search can also answer `403 plan_required` with `the has:creds filter requires the Plus plan`; no plan with API access triggers it today.

Successful responses are `200` with the JSON body described on each endpoint page.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.