Skip to content

Errors

Every error response from the BoostMail API uses the same envelope, regardless of status code.

{
"error": {
"code": "...",
"message": "...",
"details": [ ... ]
}
}
  • code: a fixed string identifying the error. See the table below.
  • message: a human-readable summary.
  • details: populated only on 422 responses (see below). Empty or absent on every other status.
StatuscodeMeaning
400invalid_requestThe request is malformed: bad syntax, a bad parameter value, or a missing required field.
401invalid_credentialThe key is missing, invalid, revoked, or suspended.
403insufficient_scopeThe key is valid but lacks the scope the endpoint requires.
404not_foundThe resource does not exist.
409conflictThe request conflicts with the resource’s current state.
422validation_failedThe request is well-formed but fails validation on one or more fields.
429rate_limitedYou have exceeded a rate limit.
500internal_errorSomething went wrong on BoostMail’s side.

code is a fixed string for a given HTTP status. It does not vary by what specifically triggered the error. Every 409 response carries conflict, whether it came from:

  • a transactional send whose idempotency key was reused with a different payload,
  • a transactional send to a recipient whose identity has been erased,
  • a transactional send that is still processing an earlier, identical request, or
  • a campaign send repeated after that campaign already started sending.

Because code never changes, you tell a transactional 409 apart from a campaign 409 by endpoint and your own request history: a 409 from POST /v1/transactional/send is always one of the first three causes; a 409 from POST /v1/campaigns/{id}/send is always the fourth. Endpoint and request history do not, however, tell the three transactional causes apart from each other. Separating those needs a bounded retry with backoff. See the Sending guide for that rule.

details is populated only on 422 validation_failed responses, one entry per offending field:

{ "field": "...", "code": "...", "message": "..." }

On every other status, details is empty or absent.

For example, this request tries to create a subscriber with an email that already exists:

Terminal window
curl -X POST https://api.boostmail.app/v1/subscribers \
-H "Authorization: Bearer bm_live_..." \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]"}'
{
"error": {
"code": "validation_failed",
"message": "validation_failed",
"details": [
{
"field": "email",
"code": "already_exists",
"message": "A subscriber with this email already exists"
}
]
}
}

The top-level code is always validation_failed on a 422. The specific cause, already_exists, lives inside details, never at the top level.

A named write is a request that targets one specific recipient by email: creating or updating a subscriber (POST /v1/subscribers or PATCH /v1/subscribers/{id}), or sending a transactional email (POST /v1/transactional/send). If the target email’s identity has been erased, BoostMail rejects the write with 409 conflict instead of performing it.

Every one of these rejections is written to an audit log, so the erasure is provably honored. The audit entry stores metadata only, a hashed identifier, never the plaintext email.

This 409 applies only to a named write. A campaign send targets many recipients at once, and handles an erased recipient differently: silently skipped, not rejected, with no per-recipient error. See the Sending guide for that half of the split.

The generated API reference marks every subscriber field as required. That is an artifact of how its schema is produced, not a guarantee about what you will get back. In practice, a subscriber response can omit non-essential fields. Do not write client code that assumes a field is always present just because the reference lists it as required.