API keys
Create, list, scope, and rotate project API keys over the API, not just in the console.
A project API key (pb_live_..., or pb_test_... in a test project)
is the credential your backend uses to call Postbox. You can create keys in the
console, but you can also manage them over the API: mint them, list them, and
revoke them, which is what makes automated key rotation possible.
Keys are scoped to one project, so key calls take an X-Project-Id header.
Create
The project needs a verified domain first (a key with no sending identity can't
do anything). The full secret is returned once, in the apiKey field. Store
it now; it is never shown again.
import { Postbox } from "@postbox/sdk";
const pb = new Postbox({
apiKey: process.env.PB_KEY!,
projectId: process.env.PROJECT_ID!,
});
const created = await pb.apiKeys.create({
label: "backend-prod",
permissions: ["send", "receive"],
expiresIn: 7776000, // seconds (90 days)
});
console.log(created.apiKey); // pb_live_… shown onceimport os
from postbox import Postbox
pb = Postbox(api_key=os.environ["PB_KEY"], project_id=os.environ["PROJECT_ID"])
created = pb.api_keys.create(
{
"label": "backend-prod",
"permissions": ["send", "receive"],
"expiresIn": 7776000, # seconds (90 days)
}
)
print(created["apiKey"]) # pb_live_… shown onceusing Postbox;
var pb = new PostboxClient(
new PostboxOptions
{
ApiKey = Environment.GetEnvironmentVariable("PB_KEY")!,
ProjectId = Environment.GetEnvironmentVariable("PROJECT_ID")!,
}
);
var created = await pb.ApiKeys.CreateAsync(
new ApiKeysCreateBody
{
Label = "backend-prod",
Permissions = new List<string> { "send", "receive" },
ExpiresIn = 7776000, // seconds (90 days)
}
);
Console.WriteLine(created!.ApiKey); // pb_live_… shown onceimport cloud.postboxapp.*;
import cloud.postboxapp.gen.*;
import java.util.*;
Postbox pb = new Postbox(PostboxOptions.builder().apiKey(System.getenv("PB_KEY")).projectId(System.getenv("PROJECT_ID")).build());
CreatedApiKey created = pb.apiKeys.create(ApiKeysCreateBody.builder().label("backend-prod").permissions(List.of("send", "receive")).expiresIn(7776000).build());
System.out.println(created.apiKey()); // pb_live_… shown onceimport postbox "github.com/postboxapp/postbox-go"
pb, err := postbox.New(postbox.Options{
APIKey: os.Getenv("PB_KEY"),
ProjectID: os.Getenv("PROJECT_ID"),
})
ctx := context.Background()
created, err := pb.ApiKeys.Create(ctx, &postbox.ApiKeysCreateBody{
Label: "backend-prod",
Permissions: []string{"send", "receive"},
ExpiresIn: 7776000,
})
fmt.Println(created.ApiKey) // pb_live_… shown oncecurl -X POST https://api.postboxapp.cloud/v1/api-keys \
-H "Authorization: Bearer $PB_KEY" \
-H "X-Project-Id: $PROJECT_ID" \
-H "Content-Type: application/json" \
-d '{
"label": "backend-prod",
"permissions": ["send", "receive"],
"expiresIn": 7776000
}'| Field | Required | Notes |
|---|---|---|
label | yes | A human name, so you can tell keys apart when rotating. |
permissions | no | Scopes: read, send, receive, manage. See Permissions. |
expiresIn | no | Lifetime in seconds. Omit for a non-expiring key. |
An empty scope list means full access
Omitting permissions (or passing []) does not mean "no access", it means
unscoped, full access. Always name the scopes a key actually needs. A
leaked send-only key cannot read mail or manage the account.
List
Listing never returns the secret, only its prefix and metadata, so it's safe to call from anywhere:
const keys = await pb.apiKeys.list();keys = pb.api_keys.list()var keys = await pb.ApiKeys.ListAsync();var keys = pb.apiKeys.list();keys, err := pb.ApiKeys.List(ctx)curl https://api.postboxapp.cloud/v1/api-keys \
-H "Authorization: Bearer $PB_KEY" -H "X-Project-Id: $PROJECT_ID"{
"success": true,
"data": [
{
"id": "key_...",
"keyPrefix": "pb_live_a1b2",
"label": "backend-prod",
"permissions": ["send", "receive"],
"lastUsedAt": "2026-08-02T14:03:00.000Z",
"expiresAt": "2026-10-31T00:00:00.000Z",
"active": true
}
]
}lastUsedAt is how you spot a key that nothing is using anymore, or one that is
still live and can't be retired yet.
Revoke
await pb.apiKeys.delete(keyId);pb.api_keys.delete(key_id)await pb.ApiKeys.DeleteAsync(keyId);pb.apiKeys.delete(keyId);pb.ApiKeys.Delete(ctx, keyID)curl -X DELETE https://api.postboxapp.cloud/v1/api-keys/$KEY_ID \
-H "Authorization: Bearer $PB_KEY" -H "X-Project-Id: $PROJECT_ID"Revocation is immediate. In-flight requests using that key start failing with
401.
Rotating a key
Rotation is create-then-revoke, never revoke-then-create, so there is no window where your service has no working key:
createa new key with the same scopes and a freshlabel.- Deploy it to your service and confirm traffic is flowing on it (watch
lastUsedAton the new key). deletethe old key.
Give each service its own key. Then a rotation, or a leak, is contained to one service instead of taking down everything at once.