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.

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

`identity_ids` is required and must be a subset of what the calling key already reaches: a key cannot delegate reach it does not have. The `token` is in this response and nowhere else, ever.

Body

  • namestringrequired
  • identity_idsuuid[]
  • pod_iduuid

    The pod this belongs to — your account’s default when you name none. A pod is a sending boundary: mail from it lands on its own reputation, so one of your customers cannot damage another’s delivery. It does not change what an address can do, and no key is scoped to one.

  • accessenum

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

    read · 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”.
  • 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 \
  -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

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”.
  • 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} \
  -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

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”.
  • 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} \
  -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.
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