Skip to content

Overview

View .md

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 from your server and talk to Proof through the embedded editor.

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

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

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

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

In Node, the SDK sends it for you:

import Proof from "@exalto/proof";
const proof = new Proof(process.env.PROOF_SECRET_KEY);

Secret keys start with sk_test_ or sk_live_. Find them, and roll them, in the 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.

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

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.

  • Request and response bodies are JSON (Content-Type: application/json), except exports.
  • 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), so read the fields you need and ignore the rest.

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}.

Prefix Object
usr_ User
doc_ Document
est_ Session token
agt_ Agent
areq_ Agent request
srq_ Reader request
we_ Webhook endpoint
whsec_ Webhook signing secret
evt_ Webhook event
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.

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

{
"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.

Endpoints that list things return a page:

{
"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.

Terminal window
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:

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

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.

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 lists every change.

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 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.

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