Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The API

Base URL https://api.postrun.dev. Authenticate with Authorization: Bearer <your API key>.

Only that header — never a query parameter. A credential in a URL ends up in access logs, browser history and referrer headers.

Errors

Every failure has the same shape:

{ "name": "validation_error", "message": "`subject` is required" }

name is stable and machine-readable. message is prose and may be reworded, so do not match on it.

nameHTTPMeaningRetry?
unauthorized401The key is missing, wrong or revokedNo
validation_error422The request is malformedNo
domain_not_found422from is not one of your sending domainsNo
domain_not_verified422The domain is yours but its DNS is not verified yetNo
key_not_permitted403This key may not do this: a sending-access key reading, or a key scoped to another domainNo
email_unconfirmed403Confirm your email address before sendingNo
account_suspended403The account may not sendNo
suppressed422Every recipient is on your suppression listNo
already_sent422The message has already left, so it cannot be changed or cancelledNo
not_found404No such message or contactNo
already_exists409on_conflict: "error" and that contact is already on your listNo
forbidden403The account may not do thisNo
failed_precondition422A state the request cannot argue with, e.g. re-subscribing an address that complainedNo
rate_limit_exceeded429Too fast. Retry-After says how longYes
quota_exceeded402An allowance spent: the daily send limit, or the plan’s contact ceilingNo — see below
internal_error500OursYes, with an Idempotency-Key

The 403s are about the account or the key, not the request: your JSON is fine and re-sending it will not help. email_unconfirmed in particular is the one a new account meets first — spend the link in the mail we sent at signup and the same request works.

domain_not_found is returned for a domain that does not exist here and for one belonging to somebody else, with identical wording. Which of the two it is is not something this endpoint will tell you.

quota_exceeded is deliberately not a 429. A quota does not clear in a moment, and telling a client to retry shortly would be a lie they would act on.

A 500 does not mean the message was not accepted. Sending is several steps, and a failure in a later one — recording the send against your usage, say — leaves a message that is already stored and will still go out. Discarding it to make the error tidier would trade a duplicate for a message that silently never arrives.

So retry with an Idempotency-Key: it is the key that makes the retry safe, not the failure.

What is here, and what is not

Everything about sending and reading email is on this API: send, batch, retrieve, list, reschedule, cancel, and the webhook stream that tells you what happened afterwards.

Your audience is here too. Contacts can be created, listed, updated, deleted and unsubscribed with a key, because the people on your list arrive in your own application and a list you can only update by uploading a CSV is a list that is out of date by definition. Those are the pages that follow.

Everything else is the dashboard. Domains, segments, broadcasts, templates, suppression lists, webhook endpoints and API keys are managed there and have no HTTP API a key can reach — the control surface is an RPC surface that takes a signed-in session, not a bearer key. So a full_access key is “full access to the email API and the audience”, not to the account.

The two permissions

A key is full_access or sending_access, and both are about this API alone — a script that needs to add a domain cannot, whichever it holds.

sending_access may send and nothing else: it is the credential for the machine you trust least. Reading the log, or touching contacts, answers 403 key_not_permitted.

full_access may also read the message log and manage the audience.