Search the docs
Find a page, a section, or an endpoint.
Pidgeon for platforms
An address and a key for each of your users, made and removed in one call.
If your product gives each of its own users an address — every agent you host, every customer you serve — this is the shape that keeps them apart. Each user gets a pod, one address in it, and a key that reaches that pod and nothing else. Your server holds one admin key and makes the rest.
POST /v1/pods a pod, its address and its key, in one call
GET /v1/identities?pod_id=… a pod's addresses
GET /v1/api-keys?pod_id=… a pod's keys
POST /v1/api-keys/{id}/rotate a new secret for a key; nothing below it changes
DELETE /v1/api-keys/{id} a key, and every key it minted, stop working
DELETE /v1/pods/{id}?delete_addresses=true a pod, its keys, its addresses and their mail, goneYour admin key, and the keys it mints
The key on your server is an admin key: a key over every address in
your account, at admin. Make it in Developers → API keys, and leave the
addresses unchosen. It is the only key that can create or delete pods,
and the only one that can mint a key for a pod.
A pod key is what you hand one user. It reaches every address in its
pod, including ones the pod gains later, and nothing outside it. It cannot
see another pod, list your keys, mint a key for another pod, or create or
delete a pod, its own included. Asking about another pod gets 403 or
404, and the answer says nothing about what is in it.
Provisioning a user in one call
POST /v1/pods with address and key makes the pod, an address in it and
a pod key for it. All three are made or none is: if the address can't be
made, the pod is removed again and you get the error.
curl https://pidgeon.ai/api/v1/pods \
-H "Authorization: Bearer $PIDGEON_ADMIN_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: provision-user-8812" \
-d '{
"name": "user-8812",
"client_id": "user-8812",
"address": { "address": "user-8812@yourapp.pidgeon.email", "type": "agent" },
"key": { "access": "send" }
}'{
"data": {
"id": "2f0f6a1c-9a4e-4e26-9a60-2b4c0f5e1d33",
"name": "user-8812",
"client_id": "user-8812",
"is_default": false,
"identity_count": 1,
"created_at": "2026-10-24T09:00:00.000Z",
"address": { "id": "…", "email": "user-8812@yourapp.pidgeon.email", "pod_id": "2f0f6a1c-…", "…": "…" },
"key": { "id": "…", "pod_id": "2f0f6a1c-…", "access": "send", "test": false, "token": "pgn_live_…", "…": "…" }
}
}addresstakes the same fields asPOST /v1/identities: anaddress, or alocal_partwith adomainordomain_id, andtype,display_nameand the rest. It always goes in the new pod.keytakesname(the pod's name if you leave it out),access,expires_atandtest.{}is a pod key at your admin key's rung.access: "send"is the usual choice for a user's agent: it can read and send, and cannot delete its address or change who reaches it.- Leave either out and it is not made. A body with neither is a plain pod.
nameandclient_idare each unique in your account, so a second call for the same user is a409, not a second pod.
The token is shown once, and a retry says so
Send an Idempotency-Key. A retry with the same key and body returns the
same pod, address and key, marked Idempotent-Replay: true, with
"token": null. Pidgeon never stores a key's secret, so it cannot show it
twice.
If your first answer was lost and you never saw the token, rotate the key:
POST /v1/api-keys/{id}/rotate with the key.id from the replay gives it a
new secret, and the pod and address are untouched. An Idempotency-Key
is kept for a day (Idempotency).
Rotate or revoke
Two different tools, and the difference is everything below the key.
- Rotate (
POST /v1/api-keys/{id}/rotate) gives a key a new secret and keeps its id, so every key it minted keeps working. Rotate your admin key on a schedule, or when someone who had it leaves: no user goes down. The old secret can keep working for up to a day withgrace_period_seconds. - Revoke (
DELETE /v1/api-keys/{id}) ends a key and every key it minted, however deep. Revoking a user's pod key cuts that user off. Revoking your admin key cuts off every user it provisioned, at once. That is the kill switch: use it when the admin key has leaked, not to replace it.
Details and the rules on who may do which are in Authentication.
Test mode, for CI and development
A test key (pgn_test_…) runs the whole pipeline and
nothing it sends leaves Pidgeon or counts against your plan. Give each user
a test key as well as a live one, from the same call:
POST /v1/pods
{ "name": "user-8812-ci", "address": { "address": "user-8812-ci@yourapp.pidgeon.email" }, "key": { "test": true } }Or mint one for an existing pod with POST /v1/api-keys and
{ "pod_id": "…", "test": true }.
A test key can't create or delete pods or addresses: a test key calling
POST /v1/pods gets 403. Provision with your live admin key, then hand
the test key to CI or a development environment.
Simulated inbound
Nobody outside will ever answer a test send, so POST /v1/test/inbound
plays the other side. It delivers a message to one of the test key's
addresses through the real inbound pipeline: the sender rules, the spam
filter, the Firewall, threading, message.received and your webhooks.
curl https://pidgeon.ai/api/v1/test/inbound \
-H "Authorization: Bearer $PIDGEON_TEST_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "user-8812-ci@yourapp.pidgeon.email",
"from": "customer@example.com",
"subject": "Where is my order?",
"text": "It never arrived."
}'Listing one user's pod
Every address carries its pod_id, and every key its pod_id, so a pod
lists like this:
curl "https://pidgeon.ai/api/v1/identities?pod_id=$POD_ID" \
-H "Authorization: Bearer $PIDGEON_ADMIN_KEY"
curl "https://pidgeon.ai/api/v1/api-keys?pod_id=$POD_ID" \
-H "Authorization: Bearer $PIDGEON_ADMIN_KEY"The key list includes revoked keys, so you can tell which key was which
after the fact. It needs your admin key; a pod key gets 403.
Tearing a user down
DELETE /v1/pods/{id} removes a user from Pidgeon. It deletes their mail,
permanently, and nothing can bring it back. In order, it:
- revokes every key of the pod, and every key those minted, so nothing can send while the rest happens;
- deletes each address in the pod with all of its mail: messages, threads, attachments and the stored originals;
- deletes the pod.
A pod that still holds an address is refused with 409 unless you pass
delete_addresses=true, so deleting mail is never something a call does
by accident.
curl -X DELETE "https://pidgeon.ai/api/v1/pods/$POD_ID?delete_addresses=true" \
-H "Authorization: Bearer $PIDGEON_ADMIN_KEY"{ "data": { "id": "2f0f6a1c-…", "deleted": true, "addresses_deleted": 1, "keys_revoked": 2 } }- It needs your live admin key. A pod key gets
403, on its own pod too. So does a test key. - Refused, always: your account's default pod, and a pod holding a
domain. Delete the domain first (
DELETE /v1/domains/{id}). - Refused before anything happens: a pod holding a person's own mailbox, which only that person can delete.
- If it fails part way, call it again. It carries on from where it
stopped. Once the pod is gone, the call answers
404. - To keep the mail, don't delete the pod. Suspend the addresses
(
PATCH /v1/identities/{id}with"status": "suspended") and revoke the pod's key: nothing can send or arrive, and the conversation stays on record.
Limits
Each pod counts against your plan's pods, and each key, live or test, against its active keys. A user with a live key and a test key uses two. Revoked keys count for nothing. The numbers per plan are in Limits.