Skip to content

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.

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.

FieldNotes
prefixFirst 12 characters (e.g. ak_live_abcd). Always visible — safe to log.
plaintextFull token. Returned only at creation. Save it.
client_kinddirect (default) / mcp / sdk. Used to attribute traffic in /usage.
labelHuman-readable name (e.g. “Claude Desktop”).
last_used_atTouched on every authenticated request.

Two paths:

Via signup (one-shot, returns the first key for a fresh account):

Terminal window
curl -X POST https://ampout.fly.dev/signup \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "name": "You"}'

Via the API (for an existing account, e.g. to mint a new key for an MCP client):

Terminal window
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"
}
Terminal window
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).

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.

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

Reusing 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_keys
  • POST /webhooks, POST /webhooks/:id/rotate_secret
  • POST /campaigns
  • POST /contact_imports
  • POST /outreaches
  • POST /billing/checkout, POST /billing/portal

POST /signup does not (no account exists yet to scope the key to).

BucketLimitPeriod
Per API key60 requestsper minute
Public POST /signup per IP5 requestsper hour

Throttled requests return 429 rate_limited with a Retry-After header (seconds until the bucket resets):

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-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.

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 } on plan_limit_reached).

The full table of codes is in Errors.

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.