Search the docs

Find a page, a section, or an endpoint.

API keys

Every api keys endpoint, generated from the API's own schemas.

List the keys in this workspace

get/api/v1/api-keys

Names, prefixes, what each reaches and when it was last used — never a secret. The list is what you read before revoking something. `pod_id` narrows it to one pod’s keys, revoked ones included.

Parameters

  • pod_iduuid

Responses

  • 200A page of keys.
  • 400The request is not valid. `error.field` names the input at fault when there is one.
  • 401No API key, or one that is not valid.
  • 403Refused. `error.code` says why: `forbidden` when this key may not do this or the account is on hold, `abuse_blocked` when a limit says no.
  • 404No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
  • 409It conflicts with what is there: it already exists, or the `etag` you sent is not the current one.
  • 429Rate limited. `X-RateLimit-Reset` says when to retry.
  • 500Something went wrong on our side. Retry; if it keeps happening, tell us.
  • 503Something this needs is not answering right now. Retry in a moment.
bash
curl https://pidgeon.ai/api/v1/api-keys \
  -H "Authorization: Bearer $PIDGEON_API_KEY"

Generated · public/openapi.json

Mint a key

post/api/v1/api-keys

Say what the key reaches and what it may do: one of `identity_ids` or `pod_id`, and `access`. `identity_ids` must be addresses the calling key administers, at no more than its own `access`: a key cannot delegate reach it does not have, and `reaches_all` is refused to a key. Until 26 November 2026 a request without `access` still works — the new key takes the calling key’s level — and is answered with `Deprecation`, `Sunset` and a `warnings` entry. From that date it is a 400 with `field: "access"`. The `token` is in this response and nowhere else, ever.

Parameters

  • Idempotency-Keystring

    Any string unique to this attempt, reused on its retries. A repeat with the same key and the same request returns the first answer with `Idempotent-Replay: true` and does nothing — no second email. The same key with a different request is a `409`. Kept for 24 hours; a failure is not kept, so a retry after one runs.

Body

  • namestringrequired
  • identity_idsuuid[]

    The addresses this key may reach. One of `identity_ids`, `pod_id` or `reaches_all` is required. A key can only name addresses it administers itself.

  • pod_iduuid

    A pod: the key reaches every address in it, now and as it grows. Minted by an owner, or a key at `admin` over every address.

  • reaches_allboolean

    Every address in the account, now and later. Only an owner can ask for it, from the dashboard or a consent screen — a key cannot create a key that reaches everything.

  • accessenumrequired

    What a key may do. Give an agent the narrowest one that works.

    read · draft · send · admin

  • expires_atdate-time | null

    When it stops accepting mail. Expiry suspends; it never deletes, so the correspondence survives.

  • testboolean

    A test-mode key (`pgn_test_`): everything works, nothing is sent outside Pidgeon, and nothing counts against the plan. A test key mints only test keys.

Responses

  • 201The key, with its token.
  • 400The request is not valid. `error.field` names the input at fault when there is one.
  • 401No API key, or one that is not valid.
  • 402Your plan does not include this. `error.message` says what does.
  • 403Refused. `error.code` says why: `forbidden` when this key may not do this or the account is on hold, `abuse_blocked` when a limit says no.
  • 404No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
  • 409Nothing was done by this call. `error.code` says which: `idempotency_in_progress` when the first request with this `Idempotency-Key` has not answered yet (wait `Retry-After` seconds and send it again), `idempotency_mismatch` when the key was used with a different request (use a new key).
  • 429Rate limited. `X-RateLimit-Reset` says when to retry.
  • 500Something went wrong on our side. Retry; if it keeps happening, tell us.
  • 503Something this needs is not answering right now. Retry in a moment.
  • 504Test mode only: `Pidgeon-Test-Fail: drop` dropped the answer (`test_dropped`). The request was done; retry it with the same `Idempotency-Key` to get the first answer.
bash
curl https://pidgeon.ai/api/v1/api-keys \
  -X POST \
  -H "Authorization: Bearer $PIDGEON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ … }'

Generated · public/openapi.json

Fetch one key

get/api/v1/api-keys/{id}

What it reaches and when it was last used. Never the secret — that is shown once at creation and nothing can produce it again.

Parameters

  • idstringrequired

Responses

  • 200The key.
  • 400The request is not valid. `error.field` names the input at fault when there is one.
  • 401No API key, or one that is not valid.
  • 403Refused. `error.code` says why: `forbidden` when this key may not do this or the account is on hold, `abuse_blocked` when a limit says no.
  • 404No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
  • 409It conflicts with what is there: it already exists, or the `etag` you sent is not the current one.
  • 429Rate limited. `X-RateLimit-Reset` says when to retry.
  • 500Something went wrong on our side. Retry; if it keeps happening, tell us.
  • 503Something this needs is not answering right now. Retry in a moment.
bash
curl https://pidgeon.ai/api/v1/api-keys/{id} \
  -H "Authorization: Bearer $PIDGEON_API_KEY"

Generated · public/openapi.json

Rename a key

patch/api/v1/api-keys/{id}

Its name and its secret (`POST /api-keys/{id}/rotate`) are the only things about a key that can change. Not its scope — a key's reach is what somebody granted when they made it, and widening that in place would let a credential already in circulation reach further than whoever issued it agreed to.

Parameters

  • idstringrequired
  • Idempotency-Keystring

    Any string unique to this attempt, reused on its retries. A repeat with the same key and the same request returns the first answer with `Idempotent-Replay: true` and does nothing — no second email. The same key with a different request is a `409`. Kept for 24 hours; a failure is not kept, so a retry after one runs.

Body

  • namestringrequired

Responses

  • 200The key.
  • 400The request is not valid. `error.field` names the input at fault when there is one.
  • 401No API key, or one that is not valid.
  • 403Refused. `error.code` says why: `forbidden` when this key may not do this or the account is on hold, `abuse_blocked` when a limit says no.
  • 404No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
  • 409Nothing was done by this call. `error.code` says which: `idempotency_in_progress` when the first request with this `Idempotency-Key` has not answered yet (wait `Retry-After` seconds and send it again), `idempotency_mismatch` when the key was used with a different request (use a new key).
  • 429Rate limited. `X-RateLimit-Reset` says when to retry.
  • 500Something went wrong on our side. Retry; if it keeps happening, tell us.
  • 503Something this needs is not answering right now. Retry in a moment.
  • 504Test mode only: `Pidgeon-Test-Fail: drop` dropped the answer (`test_dropped`). The request was done; retry it with the same `Idempotency-Key` to get the first answer.
bash
curl https://pidgeon.ai/api/v1/api-keys/{id} \
  -X PATCH \
  -H "Authorization: Bearer $PIDGEON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ … }'

Generated · public/openapi.json

Revoke a key, and everything it minted

delete/api/v1/api-keys/{id}

Transitive, and that is the point: a leaked key would otherwise mint a second one and survive its own rotation. A key may revoke itself and keys it made — not a sibling, and not its parent.

Parameters

  • idstringrequired
  • Idempotency-Keystring

    Any string unique to this attempt, reused on its retries. A repeat with the same key and the same request returns the first answer with `Idempotent-Replay: true` and does nothing — no second email. The same key with a different request is a `409`. Kept for 24 hours; a failure is not kept, so a retry after one runs.

Responses

  • 200Revoked.
  • 400The request is not valid. `error.field` names the input at fault when there is one.
  • 401No API key, or one that is not valid.
  • 403Refused. `error.code` says why: `forbidden` when this key may not do this or the account is on hold, `abuse_blocked` when a limit says no.
  • 404No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
  • 409Nothing was done by this call. `error.code` says which: `idempotency_in_progress` when the first request with this `Idempotency-Key` has not answered yet (wait `Retry-After` seconds and send it again), `idempotency_mismatch` when the key was used with a different request (use a new key).
  • 429Rate limited. `X-RateLimit-Reset` says when to retry.
  • 500Something went wrong on our side. Retry; if it keeps happening, tell us.
  • 503Something this needs is not answering right now. Retry in a moment.
  • 504Test mode only: `Pidgeon-Test-Fail: drop` dropped the answer (`test_dropped`). The request was done; retry it with the same `Idempotency-Key` to get the first answer.
bash
curl https://pidgeon.ai/api/v1/api-keys/{id} \
  -X DELETE \
  -H "Authorization: Bearer $PIDGEON_API_KEY"

Generated · public/openapi.json

Give a key a new secret

post/api/v1/api-keys/{id}/rotate

A new secret for the same key id. Everything that hangs off the id is unchanged — its scope, its ceiling, its expiry, and **every key it minted**, which keep working — so rotating an admin key does not take its children down. Revoking is still how a key and its children end together. The old secret stops now, or after `grace_period_seconds` (at most 86400, a day), so the servers that hold it can move over. One previous secret at a time: rotating again inside the window ends the older one immediately. A key may rotate itself and keys it minted; a person must own the workspace. The `token` is in this response and nowhere else, ever.

Parameters

  • idstringrequired

Body

  • grace_period_secondsnumber

Responses

  • 200The key, with its new token.
  • 400The request is not valid. `error.field` names the input at fault when there is one.
  • 401No API key, or one that is not valid.
  • 403Refused. `error.code` says why: `forbidden` when this key may not do this or the account is on hold, `abuse_blocked` when a limit says no.
  • 404No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
  • 409It conflicts with what is there: it already exists, or the `etag` you sent is not the current one.
  • 429Rate limited. `X-RateLimit-Reset` says when to retry.
  • 500Something went wrong on our side. Retry; if it keeps happening, tell us.
  • 503Something this needs is not answering right now. Retry in a moment.
  • 504Test mode only: `Pidgeon-Test-Fail: drop` dropped the answer (`test_dropped`). The request was done; retry it with the same `Idempotency-Key` to get the first answer.
bash
curl https://pidgeon.ai/api/v1/api-keys/{id}/rotate \
  -X POST \
  -H "Authorization: Bearer $PIDGEON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ … }'

Generated · public/openapi.json