# Overview

> How the Proof REST API works, from authentication and errors to pagination, versions and rate limits.

The Proof REST API is how your server tells Proof who your users are, which documents exist and who may open them, and how it reads back what happened: provenance, seals and agents. Browsers never call it. They get a short-lived [session token](/reference/api/sessions/) from your server and talk to Proof through the [embedded editor](/reference/embed/).

## Base URL

```
https://api.proof.exalto.ai/v1
```

Every request goes over HTTPS. Plain HTTP requests are refused, not redirected.

## Authentication

Send your secret key as a bearer token on every request:

```bash
curl https://api.proof.exalto.ai/v1/users/usr_5Kq2Lm8R \
  -H "Authorization: Bearer $PROOF_SECRET_KEY"
```

In Node, the SDK sends it for you:

```ts

const proof = new Proof(process.env.PROOF_SECRET_KEY);
```

Secret keys start with `sk_test_` or `sk_live_`. Make them, and revoke them, in the [console](https://developer.proof.exalto.ai/console). Each key also has an id (`key_…`) that is safe to log; seals record the id of the key that started each writing session, so a leaked key can be traced to the sessions it started. See [Platforms and keys](/concepts/platforms-and-keys/).

:::caution
A secret key can start a session as any of your users. Keep it on your server, out of your client bundle and out of source control.
:::

A request without a key, or with an unknown or revoked one, gets `401` with an error of type `authentication`.

### Test and live data

Test keys and live keys see separate data. A document created with a test key doesn't exist for a live key, and the other way round. Test seals have codes that start with `T`, and their pages at `proof.exalto.ai` say "Test seal". Use test keys in development and CI; nothing you do with them is billed.

## Requests and responses

- Request and response bodies are JSON (`Content-Type: application/json`), except [exports](/reference/api/documents/#export-a-document).
- Field names are `snake_case`.
- Timestamps are ISO 8601 in UTC, such as `2026-10-30T18:20:00Z`.
- Fields that have no value are present and `null`, not left out.
- Every response carries a `Proof-Request-Id` header. Include it when you contact us about a request.

New fields can appear in any response at any time (see [Versioning](#versioning)), so read the fields you need and ignore the rest.

## IDs

Everything Proof creates has an id with a prefix that says what it is, users included. Your own id for a user is kept on the user as `external_id`, and appears only there: everywhere else, in requests, responses, events and seals, users are named by Proof id. Store the Proof id beside your user, or look it up with [`GET /v1/users/external/{external_id}`](/reference/api/users/#retrieve-a-user-by-your-id).

| Prefix | Object |
| --- | --- |
| `usr_` | [User](/reference/api/users/) |
| `doc_` | [Document](/reference/api/documents/) |
| `est_` | [Session](/reference/api/sessions/) token |
| `agt_` | [Agent](/reference/api/agents/) |
| `areq_` | [Agent request](/reference/api/agent-requests/) |
| `srq_` | [Reader request](/reference/api/seals/#reader-requests) |
| `we_` | [Webhook endpoint](/reference/api/webhook-endpoints/) |
| `whsec_` | Webhook signing secret |
| `evt_` | [Webhook event](/reference/webhooks/) |
| `key_` | Secret key id |

Seals are identified by their code: nine capital letters and digits, such as `AH271NSHS`.

Ids are opaque: don't parse them or rely on their length.

## Errors

Proof uses HTTP status codes, and every error response has the same body:

```json
{
  "error": {
    "type": "invalid_request",
    "code": "invalid_parameter",
    "param": "role",
    "message": "role must be one of owner, editor, suggester, viewer."
  }
}
```

`type` tells you what kind of problem it is, `code` names the specific one, `param` names the field at fault when there is one, and `message` is for people reading logs, not for your code to match on.

| Type | Status | Meaning |
| --- | --- | --- |
| `invalid_request` | 400 | The request is malformed or a parameter is missing or wrong. |
| `authentication` | 401 | No key, or the key is unknown or revoked. |
| `permission` | 403 | The key is fine, but the action isn't allowed, such as a session for someone without access. |
| `not_found` | 404 | The object doesn't exist in this mode (test or live). |
| `conflict` | 409 | The object's state doesn't allow it, such as sealing a document in the trash. |
| `rate_limited` | 429 | Too many requests. Wait for `Retry-After` seconds. Reserved: the API doesn't limit requests yet. |
| `server` | 500 | Something went wrong on our side. Retry. |

Codes you'll see most often:

| Code | Type | When |
| --- | --- | --- |
| `missing_parameter` | `invalid_request` | A required field is absent. |
| `invalid_parameter` | `invalid_request` | A field has the wrong type or value. |
| `invalid_body` | `invalid_request` | The body isn't a JSON object. |
| `missing_key` | `authentication` | No `Authorization` header. |
| `invalid_key` | `authentication` | The key isn't one of yours, or has been revoked. |
| `no_access` | `permission` | The user can't open the document. |
| `not_owner` | `permission` | Only one of the document's owners can do this. |
| `user_not_found` | `not_found` | No user with that id. Create it with `PUT /v1/users/external/{external_id}`. |
| `document_not_found` | `not_found` | No document with that id, or it was deleted. |
| `document_trashed` | `conflict` | The document is in the trash. Restore it first. |

Each resource page lists the codes specific to its endpoints.

## Pagination

Endpoints that list things return a page:

```json
{
  "data": [ ... ],
  "next": "c_2Fh8Kq"
}
```

Pass `limit` (1 to 100, default 50) to choose the page size and `after` with the previous page's `next` to get the following page. `next` is `null` on the last page. Each endpoint says what order its list is in.

```bash
curl "https://api.proof.exalto.ai/v1/users/usr_5Kq2Lm8R/documents?limit=20&after=c_2Fh8Kq" \
  -H "Authorization: Bearer $PROOF_SECRET_KEY"
```

In Node, `await` a list method for one page, or loop over it with `for await` and the SDK follows `next` for you:

```ts
for await (const doc of proof.users.listDocuments("usr_5Kq2Lm8R")) {
  console.log(doc.title);
}
```

## Idempotency

Proof doesn't take idempotency keys yet: a `POST` sent twice is carried out twice, and the Node SDK doesn't retry one that failed. Retry a `POST` that failed without an answer only where doing it twice is harmless, such as starting a session, or check first, for example by listing a document's seals before sealing again. `PUT` and `DELETE` are idempotent already.

## Versioning

The version is in the path: `/v1`. Within `/v1`, changes are only additive:

- new endpoints, and new optional parameters on existing ones
- new fields in responses
- new error codes and new webhook event types
- new values for fields that are documented as open-ended, such as a seal's `method`

Anything that would break working code ships as `/v2`, and `/v1` keeps working for at least 12 months after `/v2` is released. The embed script follows the same rule: `v1.js` changes only in compatible ways. The [changelog](/changelog/) lists every change.

## Rate limits

The API doesn't limit requests yet. When it does, a request over the limit will get `429 rate_limited` with a `Retry-After` header saying how many seconds to wait, which the [Node SDK](/reference/node/) already honours, retrying twice by default.

Writing in the editor isn't rate limited per request: people's edits always go first, and each agent request gets a short slice of work at a time per document, so a burst of agent edits never holds up someone's typing.

## Resources

| Resource | What it's for |
| --- | --- |
| [Users](/reference/api/users/) | Tell Proof who your users are, merge and delete them, and set their check-ins. |
| [Documents](/reference/api/documents/) | Create documents, set link access, move them to the trash, export them and read their provenance. |
| [Members](/reference/api/members/) | Decide who can open a document and with which role. |
| [Sessions](/reference/api/sessions/) | Start the short-lived tokens the embedded editor runs on. |
| [Agents](/reference/api/agents/) | List, limit and revoke the agents your users connected. |
| [Agent requests](/reference/api/agent-requests/) | Approve or deny an agent from your consent page. |
| [Seals](/reference/api/seals/) | Seal documents, look seals up, and let in the readers of private ones. |
| [Webhook endpoints](/reference/api/webhook-endpoints/) | Choose where Proof sends events. |
| [Platform](/reference/api/platform/) | Your consent, reader and document URLs, embed origins, and your own agent tools. |