# Sellat auth.md

> How a program or an AI agent authenticates to the Sellat API v2: who it is for, how a credential is provisioned, which method is supported, how the credential is used, what the errors mean and which limits apply.

- Base URL: https://sellat.app/api/v2
- Supported method: API key sent as a Bearer token (`Authorization: Bearer sellat_...`). No OAuth, no agent self-registration.
- Reference: https://sellat.app/en/developers/docs · OpenAPI: https://sellat.app/developers/openapi.json · API catalog: https://sellat.app/.well-known/api-catalog

## Audience

- Programs and AI agents that call the Sellat API v2 on behalf of a Sellat account: registering SHA-256 fingerprints as proofs, requesting qualified timestamps, and downloading certificates, evidence packages and portable proofs.
- Every credential belongs to a human-owned Sellat account. What an agent creates belongs to that account, counts against its limits and its seal balance, and shows up in its dashboard. The API never charges a card.

## Supported methods

| Method | Status |
| --- | --- |
| API key as a Bearer token (`Authorization: Bearer sellat_...`) | Supported |
| OAuth 2.0 (authorization code, client credentials, token exchange) | Not available |
| Agent self-registration (verified email, identity assertion, anonymous) | Not available |

## Provisioning a credential

- There is no registration endpoint for agents: a credential cannot be obtained programmatically.
- A person with a Sellat account creates the key in the dashboard, API tab: https://sellat.app/en/dashboard?tab=api#api-keys (sign in at https://sellat.app/en/login first; an account is free). The dashboard's own key endpoint requires that signed-in browser session; it is not an agent API.
- The key looks like `sellat_` followed by 64 hexadecimal characters. It is shown once, when it is created: Sellat stores only its SHA-256 and cannot show it again. A lost or leaked key is revoked and replaced, never recovered.
- An account can hold up to 5 active keys; create one per integration so each can be revoked on its own.
- An agent cannot create a key by itself: ask the account owner for one. Keep it in a server-side secret, never in code that reaches a browser (the API sends no CORS headers).

## Using the credential

```sh
curl https://sellat.app/api/v2/account \
  -H "Authorization: Bearer $SELLAT_API_TOKEN"
```

`GET /account` is the cheapest way to check that a key works: it answers `200` with the account’s plan, quota and remaining seals, or `401` if the key is missing, malformed or revoked.

## How the API answers

Every request carries your key in the `Authorization: Bearer sellat_...` header. Keys are created and revoked from your dashboard; an account can hold up to 5 active keys, one per integration.

Without a key, or with a revoked one, the API answers `401 unauthorized` with a `WWW-Authenticate: Bearer` header. The only public route is the portable proof (`/proof/{id}.json`).

## Errors

An error is always JSON with `error.code` (stable, for your code) and `error.message` (for people). Some add fields, such as `seals_remaining` and `buy_url` on a `402`. The portable proof, being public, answers `{"error": "..."}`.

If the gateway cuts a request before it reaches the API (too many requests from one IP, or a body over 25 MB), that `429` or `413` does not use this format.

| Code | HTTP | What happened |
| --- | --- | --- |
| invalid_request | 400 | A field is missing or not in the expected format. The message says which. |
| unauthorized | 401 | No key, a malformed key or a revoked one. |
| payment_required | 402 | No qualified seals left. Carries `seals_remaining` and `buy_url`. |
| key_without_account | 403 | The key is not attached to an account (old keys). |
| not_found | 404 | The proof does not exist or belongs to another account. |
| conflict | 409 | Another account registered these bytes first, or the original is not stored yet. |
| hash_mismatch | 409 | The file you upload does not have the proof’s fingerprint. |
| file_too_large | 413 | The file exceeds your plan’s per-file limit (`max_file_bytes`). |
| rate_limited | 429 | Too many requests per minute. Honour `Retry-After`. |
| quota_exceeded | 429 | Daily proof limit reached. `Retry-After` runs to 00:00 UTC. |
| download_quota_exceeded | 429 | Daily limit of original downloads reached. |
| integrity_failed | 500 | The stored original no longer matches its fingerprint, so it is not served. |
| unavailable | 502 · 503 | An internal service is not answering. Retry later. |
| seal_unavailable | 503 | The timestamping authority is not available right now. |
| custody_unavailable | 503 | Custody is not available right now. |
| custody_quota_exceeded | 507 | No custody space left on your account. |

## Limits

Each account can create a number of API proofs per day, across all its keys. Proofs made on the web do not count, and neither does sending bytes you already own again. The day resets at 00:00 UTC.

At the limit, `POST /proofs` answers `429 quota_exceeded` with a `quota` object and `Retry-After`. Each route also has a per-minute brake (for example 60 creations and 10 seals a minute) that answers `429 rate_limited`, and the gateway accepts up to 120 requests a minute per IP. A request body can be 25 MB at most.

Qualified seals have no daily limit: each one spends a seal from your balance. Check what you have left at any time with `GET /account`, and if you need more volume, write to hola@sellat.app.

|  | Free account | With Custody |
| --- | --- | --- |
| API proofs per day | 1,000 | 1,000 |
| Polygon and Bitcoin anchoring | Every 4 hours, at fixed UTC times | Every 4 hours, at fixed UTC times |
| Active keys | 5 | 5 |
| Original in custody, per file | 10 MB | 50 MB |
| Total custody | 1 GB | 20 GB |
| Original downloads per day | 20,480 | 20,480 |

## Without a key

The portable proof is public: `GET https://sellat.app/api/v2/proof/{id}.json`. It exists so that anyone holding a proof can verify it without Sellat.

An agent with a wallet can also create proofs and buy qualified timestamps with no key at all, paying each call with x402 (USDC on Base): the signed payment is the credential, and the paying wallet becomes the account. Prices and how to pay: `GET https://sellat.app/api/v2/x402`. The first paid proof returns a read-only key for the wallet’s account.
