---
title: API reference
description: The full /api/v1 REST surface — every endpoint, grouped by resource, with a link to the machine-readable OpenAPI description.
---

# API reference

Every Postfleet endpoint lives under `/api/v1/` and authenticates with a bearer
`pf_` API key:

```
Authorization: Bearer pf_...
```

A machine-readable OpenAPI 3.1 description of this surface is served at
[`/openapi.json`](/openapi.json). It documents shipped behavior only — every
path, method, status code, request field, and error `code` is transcribed from
the route handlers and kept honest by a drift test. Point your OpenAPI client,
code generator, or agent at that file.

The narrative guides —
[sending & idempotency](/docs/sending),
[drafts & HITL](/docs/drafts),
[send lists](/docs/lists),
[authentication](/docs/auth),
and [webhooks](/docs/webhooks) — explain the semantics behind these endpoints.

## Endpoints

### Send

- `POST /api/v1/send` — send an email. Optional `client_id` for idempotent
  duplicate protection. On a `require_approval` mailbox the send is queued as a
  forced draft (`202`).

### Messages

- `GET /api/v1/messages?mailbox_id=` — list recent messages in a mailbox
  (`can_read`).
- `GET /api/v1/messages/{id}` — get one message with body, comprehension, and
  attachments (`can_read`).

### Drafts

- `POST /api/v1/drafts` — create a voluntary draft (`can_send`).
- `GET /api/v1/drafts?mailbox_id=` — list open drafts in a mailbox (`can_read`).
- `GET /api/v1/drafts/{id}` — get a draft (`can_read`).
- `PATCH /api/v1/drafts/{id}` — edit a draft's envelope (`can_send`).
- `DELETE /api/v1/drafts/{id}` — discard a draft (`can_send`).
- `POST /api/v1/drafts/{id}/send` — send a draft. Delivers (`201`) or, on a
  `require_approval` mailbox, promotes to the human queue (`202`).

### Mailboxes

- `POST /api/v1/mailboxes` — provision a mailbox. Account-plane: rejected for a
  mailbox-bound key.

### Domains

- `POST /api/v1/domains` — add a custom sending domain (paid plan).
- `GET /api/v1/domains` — list custom domains.
- `POST /api/v1/domains/{id}/verify` — re-check a domain's verification.

## Error shapes

Errors are JSON. Client-facing failures with a stable machine-readable `code`:

- `403` `key_scope` — the key cannot perform this action, or is scoped to a
  different mailbox, or is a mailbox-bound key on an account-plane route.
- `403` `send_blocked_by_list` — the recipient is blocked by a send list;
  carries `reason` (`block_match` / `no_allow_match`), `scope`
  (`account` / `mailbox`), and `list_entry_id` on a block match.
- `409` `idempotency_conflict` — the `client_id` was reused with a different
  payload.
- `409` `idempotency_in_progress` — a send with this `client_id` is still
  running; retry shortly.
- `409` `approval_conflict` — the draft is no longer editable / deletable /
  sendable (a concurrent transition moved it).
- `502` `delivery_outcome_unknown` — an ambiguous provider outcome; the send may
  or may not have gone out and is left for reconciliation.

Other statuses: `400` validation, `401` bad bearer, `402` quota exhausted, `404`
not found (also returned for cross-account and cross-mailbox ids as
anti-enumeration), `422` suppressed recipient. Cross-account and cross-mailbox
object ids return `404` rather than `403` so a caller cannot enumerate ids it
does not own.
