Authentication
The Ampout API uses bearer token authentication with API keys. Every authenticated request includes:
Authorization: Bearer ak_live_...There are no sessions, no OAuth flows, no JWTs. The API key is the credential. If you lose it, you must issue a new one and revoke the old.
API keys
Section titled “API keys”Keys have an ak_live_ prefix and a 32-byte URL-safe random suffix. Only the SHA-256 hash is stored server-side — once you receive the plaintext on creation, it’s never recoverable.
| Field | Notes |
|---|---|
prefix | First 12 characters (e.g. ak_live_abcd). Always visible — safe to log. |
plaintext | Full token. Returned only at creation. Save it. |
client_kind | direct (default) / mcp / sdk. Used to attribute traffic in /usage. |
label | Human-readable name (e.g. “Claude Desktop”). |
last_used_at | Touched on every authenticated request. |
Issuing keys
Section titled “Issuing keys”Two paths:
Via signup (one-shot, returns the first key for a fresh account):
curl -X POST https://ampout.fly.dev/signup \ -H "Content-Type: application/json" \Via the API (for an existing account, e.g. to mint a new key for an MCP client):
curl -X POST https://ampout.fly.dev/api_keys \ -H "Authorization: Bearer $AMPOUT_KEY" \ -H "Content-Type: application/json" \ -d '{"label": "Claude Desktop", "client_kind": "mcp"}'Response:
{ "id": "...", "prefix": "ak_live_xxxx", "label": "Claude Desktop", "client_kind": "mcp", "created_at": "...", "plaintext": "ak_live_xxxx...XYZ"}Listing & revoking
Section titled “Listing & revoking”curl https://ampout.fly.dev/api_keys -H "Authorization: Bearer $KEY"curl -X DELETE https://ampout.fly.dev/api_keys/<id> -H "Authorization: Bearer $KEY"The DELETE refuses with 422 cannot_revoke_last_key if it would leave the account with zero active keys (you’d lock yourself out).
Idempotency keys
Section titled “Idempotency keys”Every mutating endpoint accepts an optional Idempotency-Key header. If a request with a given key is replayed within 24 hours, the original response is returned verbatim — no duplicate side effects.
curl -X POST https://ampout.fly.dev/api_keys \ -H "Authorization: Bearer $KEY" \ -H "Idempotency-Key: my-job-2026-04-28-001" \ -H "Content-Type: application/json" \ -d '{"label": "From batch job"}'Replays carry a response header:
Idempotent-Replay: trueReusing a key with a different request body returns 409 idempotency_conflict so you don’t accidentally cache the wrong response under a stale key.
Endpoints that support Idempotency-Key:
POST /api_keysPOST /webhooks,POST /webhooks/:id/rotate_secretPOST /campaignsPOST /contact_importsPOST /outreachesPOST /billing/checkout,POST /billing/portal
POST /signup does not (no account exists yet to scope the key to).
Rate limits
Section titled “Rate limits”| Bucket | Limit | Period |
|---|---|---|
| Per API key | 60 requests | per minute |
Public POST /signup per IP | 5 requests | per hour |
Throttled requests return 429 rate_limited with a Retry-After header (seconds until the bucket resets):
HTTP/1.1 429 Too Many RequestsContent-Type: application/jsonRetry-After: 60
{ "code": "rate_limited", "type": "https://docs.ampout.dev/errors/rate_limited", "message": "Too many requests. Slow down and retry after the period resets.", "details": { "limit": 60, "period": 60 }}Plan-tier-aware limits will land later — for now everyone gets 60/min.
Error envelope
Section titled “Error envelope”Every error response follows the same shape:
{ "code": "campaign_not_found", "type": "https://docs.ampout.dev/errors/campaign_not_found", "message": "Campaign not found", "details": { ... }}code— machine-readable identifier; switch on it in your client.type— RFC-7807-flavored URI pointing at the docs page for this code.message— human-readable default. Sometimes overridden with a more specific message (e.g., a model validation error string).details— optional object with code-specific context (e.g.{ current_period_sends: 5000, monthly_limit: 5000 }onplan_limit_reached).
The full table of codes is in Errors.
Account isolation
Section titled “Account isolation”Every API key belongs to exactly one Account, and every resource (campaign, contact, webhook, etc.) is scoped to that account. A key from Account A returns 404 on resources owned by Account B — the same response as if the resource didn’t exist. There is no cross-account leakage.