Errors
Every failure has the same shape: an HTTP status, and a code that says what went wrong. Switch on the code.
The shape
Response
{
"error": {
"code": "bad_cursor",
"message": "Pass the `nextCursor` from the previous page unchanged."
}
}The message is written for a person reading a log, and its wording may change. The code won’t. Each endpoint’s page lists the codes it can give.
Statuses
What each status means
400bad_json, bad_limit…- The request was wrong: a body that isn’t JSON, a limit out of range, an unknown view, a bad cursor.
401invalid_key- No key, or a key that’s unknown, revoked or expired.
403missing_scope…- The key lacks the permission, its owner isn’t allowed to do this, or the plan has no API (
plan_no_api). 404not_found- There’s no such thing, or it exists and the key can’t see it.
409draft_locked…- It clashes with how things are: someone is editing the draft, the mail has already gone, the name is taken.
423organisation_deleting- The organisation is waiting to be deleted.
429rate_limited- Too many requests. See Rate limits.
Not found, on purpose
A 404 also covers “it exists, but this key can’t see it”. Answering 403 there would confirm the id is real, which is exactly what makes guessing ids worth trying.
Retrying
Sending a draft twice sends it once. The second call returns the same result with
alreadySent: true.Deleting something that’s already gone succeeds, so a retry after a lost response doesn’t read as a failure.
Creating a draft twice makes two drafts. Neither is sent until you send it.
After a 429, wait for the
Retry-Afterseconds before trying again.