---
title: "Authenticating with the API"
description: "How to add credentials to API requests and understand the error responses the API returns."
canonical: "https://everypage.co/docs/developers/authentication"
plan: "free"
last-updated: "2026-09-09"
---

# Authenticating with the API

The EveryPage API uses Bearer token authentication: include your API key or OAuth token in the `Authorization` header of every request. The `/api/v1/` prefix is not just a version marker—it is what selects the permissive, credential-less CORS policy (so the API is callable from any origin) and the exemption from the same-origin CSRF check. Nothing under `/api/v1/` authenticates by session cookie.

## Bearer tokens and token lanes

The API accepts two kinds of bearer tokens, distinguished by their prefix:

**API keys** begin with `ep_live_` and resolve to the wildcard scope `*`, so they pass every scope check and carry full account access. They cannot be narrowed. Use them for your own scripts and server-side integrations.

**OAuth access tokens** begin with `ep_at_` and carry only the scopes granted at authorization. Use them for third-party apps whose access should be limited—for example, an integration that only uploads documents holds `files:write` and not `readership:read`.

Both arrive in the same `Authorization: Bearer <token>` header, and the prefix is what selects the lane. A token with neither prefix, or one that is unknown, revoked, or expired, is rejected with 401—there is no fallback that could grant unintended access.

## Making a request

```bash
curl https://everypage.co/api/v1/files \
  -H "Authorization: Bearer ep_live_..."
```

`GET /api/v1/files` lists the files on your account and needs the `files:read` scope. Replace `ep_live_...` with your own key; OAuth tokens use the same header with the `ep_at_...` prefix.

## HTTP status codes

The API returns standard HTTP status codes. This table covers the most common ones and what they mean in this API:

| Code | Name | Meaning |
|------|------|---------|
| 200 | OK | Request succeeded. The response body carries the result. This includes `POST /api/v1/files` and `POST /api/v1/files/import`—uploads answer 200, not 201. |
| 201 | Created | Returned by `POST /api/v1/webhooks` and `POST /api/v1/files/{uuid}/variants`. |
| 400 | Bad Request | The request is malformed or violates a constraint (e.g. not a PDF, malformed body, unknown `type` on the events feed). |
| 401 | Unauthorized | The `Authorization` header is missing or malformed, or the token is unknown, revoked, or expired. |
| 403 | Forbidden | Your OAuth token lacks the required scope (`insufficient_scope: requires <scope>`); your plan does not include the feature (`This feature requires the pro plan or higher`); or you do not own the target file on a write or readership endpoint. |
| 404 | Not Found | The identifier does not resolve. It is also returned **deliberately in place of 403** where a 403 would confirm that a document exists: `GET /api/v1/files/{uuid}` answers 404 for a file you do not own, and the manage-token settings and content lane answers 404 once a file has been claimed. Note the asymmetry—write and readership endpoints answer 403 for a file owned by someone else. |
| 409 | Conflict | The request conflicts with existing state: the slug you asked for is already used by another of your files, the file was already claimed by a different account, or a concurrent change invalidated a design-lane republish (retry). |
| 410 | Gone | The link is dead—expired, or burned by its view limit. Both refuse settings changes. `POST /api/v1/files/{uuid}/content` refuses only expired links: replacing the content is the documented way to un-burn a link. |
| 413 | Payload Too Large | The upload or converted result exceeds your plan's size limit. |
| 429 | Too Many Requests | Rate limit exceeded. Wait before retrying. |

Send `Content-Type: application/json` with JSON request bodies; multipart file uploads (e.g. `POST /api/v1/files`) use `Content-Type: multipart/form-data` instead.

## Identifying documents

Endpoints that address a specific document accept either identifier in the path—both resolve to the same file:

- **UUID**: the canonical `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` identifier
- **Short ID**: a 12-character base62 code (e.g. `aB3xY9kQ2mZ7`) that appears in share links

Vanity slugs are **not** accepted by the API. A slug resolves only on its owner's active custom domain, on the viewer surface; API calls must use the UUID or the short ID. See [Share links and short IDs](https://everypage.co/docs/sharing/share-links-and-short-ids) for the identifier rules.

## Rate limiting

Requests to `/api/v1/` are limited to **120 per minute**, keyed on the value of the `Authorization` header—so the bucket is per token, and integrations sharing an egress IP do not share a budget. Over the limit the API returns HTTP 429.

`POST /api/v1/webhooks/{uuid}/test` carries an additional 10-per-minute limit on the same key.

If a 429 is returned, wait before retrying; exponential backoff (e.g. 1s, 2s, 4s) is a reasonable strategy.

## Error responses

Every error under `/api/v1/` is an [RFC 9457 Problem Details](https://www.rfc-editor.org/rfc/rfc9457) document with `Content-Type: application/problem+json`. Requests to unknown paths under `/api/` get the same shape. For example:

```json
{
  "type": "https://everypage.co/docs/developers/authentication#insufficient-scope",
  "title": "Forbidden",
  "status": 403,
  "detail": "insufficient_scope: requires files:write",
  "instance": "https://everypage.co/api/v1/files",
  "code": "insufficient_scope",
  "hint": "Reconnect with a token that grants files:write. API keys (ep_live_) hold every scope."
}
```

**Branch on `status` or `code`, never on `detail`.** `detail` is the human-readable message and may be reworded; `code` is stable. `type` links to the matching section below, `instance` is the request URL, and `hint` says what to do next. Rate-limited responses also carry `Retry-After` and the `X-RateLimit-*` headers; 401 and 403 responses carry an RFC 6750 `WWW-Authenticate: Bearer …` challenge.

Codes not listed here are derived from the HTTP reason phrase in lower snake case (for example `precondition_required`) and may be added without notice.

### Missing token

`401`, code `missing_token`. No `Authorization` header, or one that is not `Bearer <token>`. The response carries `WWW-Authenticate: Bearer realm="EveryPage API"`. Create an API key under **Account → API keys**.

### Invalid token

`401`, code `invalid_token`. The token is unknown, revoked, or expired. The challenge is `WWW-Authenticate: Bearer realm="EveryPage API", error="invalid_token"`. Rotate the key, or refresh the OAuth access token.

### Insufficient scope

`403`, code `insufficient_scope`. An OAuth token without the scope the route needs; `detail` names it (`insufficient_scope: requires files:write`) and the challenge is `WWW-Authenticate: Bearer error="insufficient_scope", scope="files:write"`. API keys hold every scope and never see this error.

### Plan required

`403`, code `plan_required`. The feature is above the account's plan; `detail` names the plan (`This feature requires the pro plan or higher`). Upgrade at [Pricing](https://everypage.co/pricing).

### Forbidden

`403`, code `forbidden`. Any other refusal: not the owner, a plan limit reached (file count, webhook count), or a manage token used on a plan-gated field.

### Validation error

`400`, code `validation_error`. Malformed JSON, a missing field, or an invalid value; `detail` says which.

### Not found

`404`, code `not_found`. Unknown id, or a file you do not own on routes where 404 replaces 403 to avoid leaking existence. Ids are the file UUID or its 12-character short id.

### Method not allowed

`405`, code `method_not_allowed`. The `Allow` header lists the supported methods.

### Conflict

`409`, code `conflict`. A vanity slug already in use, a file already claimed by another account, or a concurrent content replacement.

### Gone

`410`, code `gone`. The link is dead: expired or burned. Settings cannot revive it; a burned link is un-burned by replacing its content.

### Payload too large

`413`, code `payload_too_large`. The upload, the converted PDF, or a JSON body is above the limit for your plan.

### Unsupported media type

`415`, code `unsupported_media_type`. Reserved; no route returns it today (an unrecognised upload is a `400`).

### Unprocessable

`422`, code `unprocessable`. A Word or PowerPoint file could not be converted to PDF.

### Rate limited

`429`, code `rate_limited`. Over the 120-per-minute budget for this token (10 per minute on webhook tests). Wait for the number of seconds in `Retry-After` before retrying.

### Internal error

`500`, code `internal_error`. Something failed on our side; retrying is safe for reads. Please report it with the `instance` URL.

### Service unavailable

`503`, code `service_unavailable`. A dependency (the document converter, Canva key verification) is down. Retry with backoff.

Outside `/api/v1/` the shapes differ: `/oauth/token` returns the RFC 6749 `{ "error": "…", "error_description": "…" }` object, and the public oEmbed and QR endpoints return plain text.

## API reference

This article covers the conventions common to all routes. For the endpoint listing, see:

- **[OpenAPI schema](https://everypage.co/openapi.json)** — the machine-readable specification (also at [/openapi.yaml](https://everypage.co/openapi.yaml); consumable by Swagger UI, Postman, SDK generators, and LLM function-calling bridges)
- **[Developers page](https://everypage.co/developers)** — operation summaries

## Next steps

- [API keys and scopes](https://everypage.co/docs/developers/api-keys) — create and manage your credentials
- [OAuth and oEmbed](https://everypage.co/docs/developers/oauth-and-oembed) — grant narrowed permissions to third-party apps
- [Webhooks](https://everypage.co/docs/developers/webhooks) — subscribe to file events via HTTP callbacks
- [Events feed](https://everypage.co/docs/developers/events-feed) — fetch view sessions, downloads, and gate responses
- [Share links and short IDs](https://everypage.co/docs/sharing/share-links-and-short-ids) — understand document identifiers
