Skip to content

Authentication

Authenticate every request with an API key in the Authorization header. Keys carry a permission, a live or test mode, and optionally a single domain they may send from.

API keys#

Send your key as a Bearer token on every request to https://api.poststack.dev. Keys look like sk_live_ or sk_test_ followed by 64 hex characters.

import { PostStack } from '@poststack.dev/sdk';

const poststack = new PostStack(process.env.POSTSTACK_API_KEY!);

A missing or wrong key on a GET returns 401 with a WWW-Authenticate: Bearer header. On POST, PATCH, PUT and DELETE, a request with no Authorization: Bearer header is treated as a browser request and refused with 403 Invalid request origin before the key is even looked at. The scheme is case-sensitive: write Bearer.

Keep keys on the server

Anyone holding a key can send mail as your domains. Never put a key in browser or mobile code, a public repository, or a container image — read it from an environment variable or secret store at runtime. Resetting your password does not revoke keys; revoke a leaked key under API keys straight away.

Create a key#

  1. Open API keys

    In the dashboard, go to Configuration → API keys and choose Create API key. Your email address must be verified first.

  2. Choose what the key may do

    Give it a Name, a Permission (Full access or Sending only), a Mode (Live or Test) and, optionally, a domain under Restrict to domain.

  3. Copy the key

    The full key is shown once. PostStack stores only a hash, so a lost key cannot be recovered — rotate it to get a new one.

How many keys a team can hold depends on the plan: 5 on Free, 10 on Starter, 50 on Pro, 1,000 on Scale and unlimited on Enterprise.

Permissions#

PermissionDashboard labelCan do
full_accessFull accessEverything the API offers: emails, domains, contacts, templates, broadcasts, webhooks, mailboxes, workflows, and managing API keys.
sending_accessSending onlySend, list, read, cancel and reschedule emails; preview emails; read, add and remove suppressions; validate addresses; reply to and forward inbound email; the hosted MCP server’s send and read tools; and list the team’s keys (names and prefixes only).

A sending-only key calling anything else gets 403 { "error": "insufficient_scope", "required": [...], "missing": [...] }. Use sending-only keys in application servers that just send mail; keep full-access keys for setup scripts and infrastructure tools.

Domain-restricted keys#

A key restricted to one domain can only send from addresses on that domain. Any other from is refused with 403 API key is restricted to a different domain. Without a restriction a key may send from every domain on the team.

One restricted key per customer or per app is the safest setup: a leaked key can only ever send as its own domain, which keeps many domains under one account isolated from each other. Deleting a domain expires the keys restricted to it immediately, so a restricted key never silently becomes an unrestricted one.

Test keys#

A key created in Test mode starts with sk_test_. It runs every check a live key does but never delivers anything, and its sends do not count against your plan. See Test mode.

OAuth access tokens#

Third-party integrations do not handle your API keys: you authorise them through PostStack's OAuth flow and they receive an access token starting with pat_, sent the same way (Authorization: Bearer pat_…). A token can only do what its scopes allow — for example email:send, domains:read or contacts:manage — and an endpoint outside them returns 403 insufficient_scope. An expired or revoked token returns 401 Invalid or expired access token.

Managing keys over the API#

These endpoints need a full-access key, a dashboard session, or an OAuth token with api-keys:manage. Reading the key list is also open to sending-only keys.

Create an API key#

POST
/api-keys

Mint a new key. The response is the only time the secret is shown.

json
{
  "name": "Production app server",
  "permission": "sending_access",
  "mode": "live",
  "domain_id": "dom_8k2m4q7x1c5w9r3t6z0p2n4v"
}
json
HTTP/1.1 201 Created

{
  "key": "sk_live_3f9a2c7e1b8d4f6a0c9e5b2d7f1a3c8e6b4d9f2a7c1e5b8d3f6a0c9e2b7d4f1a",
  "id": 214,
  "name": "Production app server",
  "permission": "sending_access",
  "mode": "live",
  "keyPrefix": "sk_live_3f9a"
}
typescript
const { key, id } = await poststack.apiKeys.create({
  name: 'Production app server',
  permission: 'sending_access',
  domain_id: 'dom_8k2m4q7x1c5w9r3t6z0p2n4v',
});
bash
curl -X POST https://api.poststack.dev/api-keys \
  -H "Authorization: Bearer $POSTSTACK_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Production app server", "permission": "sending_access" }'

Body parameters

namestringrequired
1–100 characters.
permission"full_access" | "sending_access"required
What the key may do.
mode"live" | "test"default "live"
Test keys simulate sends and never deliver.
domain_idstring
Restrict the key to one of your domains, by dom_… id or numeric id. Omit (or send an empty string) for an unrestricted key.
allow_full_accessbooleandefault false
Must be true to create a full_access key while authenticated with an API key — a guard against scripts minting powerful keys by accident.

Response fields

keystring
The secret key. Shown only in this response — store it now; it cannot be retrieved later.
idinteger
The key’s id, used by get, rotate and revoke.
namestring
The name you gave it.
permission"full_access" | "sending_access"
What the key may do.
mode"live" | "test"
Live keys send; test keys simulate.
keyPrefixstring
The first 12 characters (sk_live_3f9a), shown in the dashboard to tell keys apart.

Status codes

StatusMeaning
201Created. Store key now.
400Validation failed (missing name, unknown permission or mode).
401Invalid, revoked or expired credentials.
403insufficient_scope — a sending-only key, or an OAuth token without api-keys:manage. Also Invalid request origin when the request has no Authorization: Bearer header.
403Also: your email address is not verified; the plan's key limit is reached (API key limit reached for free plan (5 keys)); a full_access key requested with an API key but without allow_full_access; or a full_access key requested by an OAuth client, which is never allowed.
404Domain not found — domain_id is not one of your domains.
429More than 10 key operations per minute.
500Unexpected server error.

List API keys#

GET
/api-keys

All of the team's keys, oldest first. Secrets are never returned.

json
{
  "keys": [
    {
      "id": 214,
      "name": "Production app server",
      "keyPrefix": "sk_live_3f9a",
      "permission": "sending_access",
      "mode": "live",
      "domainId": 41,
      "domainName": "acme.com",
      "lastUsedAt": "2026-09-29T09:58:12.004Z",
      "expiresAt": null,
      "createdAt": "2026-08-14T12:00:00.000Z"
    }
  ]
}
typescript
const { keys } = await poststack.apiKeys.list();
bash
curl https://api.poststack.dev/api-keys \
  -H "Authorization: Bearer $POSTSTACK_API_KEY"

Response fields

keys[].idinteger
Key id.
keys[].namestring
Key name.
keys[].keyPrefixstring
First 12 characters of the key.
keys[].permissionstring
full_access or sending_access.
keys[].modestring
live or test.
keys[].domainIdinteger | null
Restricted domain, or null.
keys[].domainNamestring | null
That domain’s name.
keys[].lastUsedAtstring | null
Last successful authentication.
keys[].expiresAtstring | null
Set when the key was expired (its restricted domain was deleted).
keys[].createdAtstring
Creation time.

Status codes

StatusMeaning
200The keys. The list is not paginated.
401Invalid, revoked or expired credentials.
403An OAuth token without api-keys:manage.
500Unexpected server error.

Retrieve an API key#

GET
/api-keys/:id

One key's metadata. The secret is never returned.

json
{
  "id": 214,
  "name": "Production app server",
  "keyPrefix": "sk_live_3f9a",
  "permission": "sending_access",
  "mode": "live",
  "domainId": 41,
  "lastUsedAt": "2026-09-29T09:58:12.004Z",
  "expiresAt": null,
  "createdAt": "2026-08-14T12:00:00.000Z"
}
typescript
const key = await poststack.apiKeys.get(214);
bash
curl https://api.poststack.dev/api-keys/214 \
  -H "Authorization: Bearer $POSTSTACK_API_KEY"

Path parameters

idintegerrequired
The key id.

Response fields

(object)object
The same fields as a list entry, without domainName, and not wrapped in an envelope.

Status codes

StatusMeaning
200The key.
400id is not a positive integer.
401Invalid, revoked or expired credentials.
403An OAuth token without api-keys:manage.
404API key not found.
500Unexpected server error.

Rotate an API key#

POST
/api-keys/:id/rotate

Replace a key with a new secret that keeps its name, permission, mode and domain. The old key stops working immediately and the new one has a new id.

json
HTTP/1.1 201 Created

{
  "key": "sk_live_9c1e4b7d2f8a5c3e0b6d9f1a4c7e2b8d5f0a3c6e9b1d4f7a2c5e8b0d3f6a9c1e",
  "id": 231,
  "name": "Production app server",
  "permission": "sending_access",
  "mode": "live",
  "keyPrefix": "sk_live_9c1e"
}
typescript
const { key } = await poststack.apiKeys.rotate(214);
bash
curl -X POST https://api.poststack.dev/api-keys/214/rotate \
  -H "Authorization: Bearer $POSTSTACK_ADMIN_KEY"

Path parameters

idintegerrequired
The key to replace.

Response fields

keystring
The secret key. Shown only in this response — store it now; it cannot be retrieved later.
idinteger
The key’s id, used by get, rotate and revoke.
namestring
The name you gave it.
permission"full_access" | "sending_access"
What the key may do.
mode"live" | "test"
Live keys send; test keys simulate.
keyPrefixstring
The first 12 characters (sk_live_3f9a), shown in the dashboard to tell keys apart.

Status codes

StatusMeaning
201Rotated. Deploy the new key; the old one is already dead.
401Invalid, revoked or expired credentials.
403insufficient_scope — a sending-only key, or an OAuth token without api-keys:manage. Also Invalid request origin when the request has no Authorization: Bearer header.
403Also: an OAuth client rotating a full_access key, which is never allowed.
404API key not found.
429More than 10 key operations per minute.
500Unexpected server error.

Revoke an API key#

DELETE
/api-keys/:id

Delete a key. Requests made with it fail with 401 from the moment this returns.

json
{
  "success": true
}
typescript
await poststack.apiKeys.revoke(214);
bash
curl -X DELETE https://api.poststack.dev/api-keys/214 \
  -H "Authorization: Bearer $POSTSTACK_ADMIN_KEY"

Path parameters

idintegerrequired
The key to revoke.

Response fields

successtrue
The key is gone.

Status codes

StatusMeaning
200Revoked.
401Invalid, revoked or expired credentials.
403insufficient_scope — a sending-only key, or an OAuth token without api-keys:manage. Also Invalid request origin when the request has no Authorization: Bearer header.
404API key not found.
429More than 10 key operations per minute.
500Unexpected server error.

SMTP credentials#

The SMTP relay authenticates with the same keys:

SettingValue
Hostsmtp.poststack.dev
Port587 (STARTTLS or implicit TLS)
Usernamepoststack — not checked when the password is an API key
PasswordYour API key (sk_live_… or sk_test_…)

Troubleshooting#

401 Invalid API key

Check for a truncated key or a stray space or quote from your secret store. A key that used to work was revoked, rotated, or restricted to a domain that has since been deleted. The dashboard's API keys page shows each key's prefix and when it was last used.

403 Invalid request origin on a POST

The Authorization header did not arrive, or does not start with exactly Bearer . Also call https://api.poststack.dev, not http://: plain HTTP answers with a redirect, and many clients follow a redirected POST as a GET without its body.

403 insufficient_scope

The key is Sending only and the endpoint manages resources, or an OAuth token lacks the scope named in missing. Use a full-access key for that call.

Next steps

Was this page helpful?

Related