# Authentication

Every request except `GET /catalog` is authenticated. This page covers the two kinds of credential, the scopes they carry, and the sending permissions that let them spend your credits.

Send credentials as a bearer token over HTTPS:

```http
Authorization: Bearer <api-key-or-token>
```

`GET /catalog` is public. Every other endpoint needs a credential, and you can only reach resources your own account owns. Browser cookies don't work for `/api/v1`.

There are two kinds of credential:

| | API key | OAuth |
|---|---|---|
| Used by | Your own code, over HTTP | AI assistants such as ChatGPT and Claude, over MCP or HTTP |
| Created by | You, in **Account → Credits → API keys** | The assistant's sign-in flow on Pony |
| Format | `pony_live_…`, or `pony_test_…` for [test mode](/docs/test-mode.md) | Short-lived access tokens |
| Lifetime | One year by default, or until revoked | Short-lived, refreshed by the assistant |

## API keys

The simple form on **Account → Credits → API keys** asks for a name and an optional daily limit. In one step it creates:

- a key with the scopes `letters:read`, `letters:write`, `orders:read`, `orders:send`, `orders:cancel`, `wallet:read`, `grants:read` and `webhooks:write`, valid for one year
- the key's own [sending permission](#sending-permissions), capped by the daily limit if you set one

So a new key can write letters, send them from your balance and manage webhooks straight away. Sending with it needs no `grant_id`.

For a narrower key, such as a read-only reporting key, open **Advanced: choose scopes and an existing permission**. There you pick the scopes, the expiry and, optionally, an existing permission to bind the key to.

The secret is shown once. Store it in a secret manager or a file only you can read. Don't put it in a URL, a log, a chat or a code repository. Revoking a key on the same page stops it at its next request.

## OAuth

AI assistants connect through Pony's OAuth sign-in; see [Connect an assistant](https://www.mailbypony.com/agents). If you build your own OAuth client, discover the authorization server from `https://www.mailbypony.com/.well-known/oauth-protected-resource` rather than hard-coding it. Use the authorization code flow with PKCE (S256), and check the issuer and resource. Each client needs its own registration and your consent; a script can't reuse an assistant's connection.

## Scopes

| Scope | Allows |
|---|---|
| `letters:read` | Read your drafts, proofs and address references; render proofs |
| `letters:write` | Create, edit, archive and attach files to drafts; manage templates |
| `drafts:read`, `drafts:write` | Aliases for `letters:read` and `letters:write` |
| `addresses:read` | List confirmed address references |
| `wallet:read` | Read your balance, credit history and top-up status |
| `wallet:topup` | Create a payment link to add credits. It can't charge a card. |
| `grants:read` | Read the sending permissions that apply to this credential |
| `orders:send` | Request final quotes and send letters within a sending permission |
| `orders:read` | Read your orders and batches |
| `orders:cancel` | Cancel orders, if the order's sending permission allows it |
| `webhooks:write` | Register, list, test and remove webhook endpoints (HTTP only) |
| `checkout:write` | Start a card checkout that you finish in your browser |

No scope lets a credential create or widen a sending permission, or create other keys. Only you can, on the website.

## Sending permissions

A sending permission (called a *grant* in the API) lets one key or assistant send letters from your credits without asking you each time. API keys from the simple form get one automatically. For an assistant, open **Account → Credits → Let my assistant send letters**, enter the assistant's client ID (ask it to run `get_connection_identity`) and an optional daily limit.

Every restriction is optional. Leave one out and it doesn't apply:

| Restriction | Field |
|---|---|
| Maximum spend per day (resets at midnight UTC) | `daily_limit_minor` |
| Maximum price for one letter | `per_order_minor` |
| Maximum spend over the permission's life | `lifetime_limit_minor` |
| Maximum number of letters | `count_limit` |
| Allowed recipients and senders | `recipient_ids`, `sender_ids` |
| Allowed papers, envelopes and type styles | `papers`, `envelopes`, `styles` |
| Expiry (default one year) | `expires_at` |
| Whether it may cancel orders (default yes) | `allow_cancel` |

On the website, the restrictions beyond the daily limit are under **Advanced limits**.

Every limit is checked again at the moment of sending, in the same transaction that deducts credits. A letter cancelled later still counts toward the limits.

To change a permission, revoke it and create a new one. Revoking stops future sends straight away. It doesn't recall letters already accepted.

When you send without a `grant_id`, Pony uses the newest active permission for your key or assistant.
