Errors
The standard error envelope and the canonical code to HTTP status table.
Every error response has the same shape:
{
"error": {
"code": "credits_exhausted",
"message": "Workspace has no email credits remaining.",
"details": { "channel": "email" },
"request_id": "req_06F4Y9CP1GQB39AA",
"documentation_url": "https://docs.dev.messagepipe.dev/errors/credits_exhausted"
}
}details is optional and varies by code; the other four fields are always
present. The request_id is the value of X-Request-Id on the same
response, quote it to support.
Codes
| Code | Status | Meaning |
|---|---|---|
invalid_json | 400 | The request body wasn't valid JSON. |
validation_failed | 400 | A required field is missing or malformed. details.field names it, and details.constraint says which rule it broke. key:case means a field was spelled with different case than documented (Subject for subject). |
template_not_found | 400 | The referenced template doesn't exist in this project. |
unauthorized | 401 | Missing or invalid credential (Bearer token, session cookie). |
credits_exhausted | 402 | Workspace has zero credits on this channel. |
forbidden | 403 | Authenticated, but not allowed (role, scope). |
insufficient_scope | 403 | API key doesn't carry the scope this endpoint requires. |
sender_not_allowed | 403 | An email from isn't on the project's sender domains. details.domain is the domain you sent; details.allowed_domains lists the allowed ones. |
not_found | 404 | The resource doesn't exist or isn't visible to you. |
conflict | 409 | The resource's state prevents the operation. |
idempotency_in_progress | 409 | A previous request with this Idempotency-Key is still running. |
idempotency_mismatch | 422 | Request body differs from the first call with this key. |
rate_limit_exceeded | 429 | See the Retry-After header. |
internal_error | 500 | A bug. Open a ticket with the request_id. |
provider_error | 502 | An upstream provider returned a 5xx after the worker exhausted retries. |
service_unavailable | 503 | Planned maintenance or a hard outage. |
Failures after the 202
These codes describe a request that was rejected. A send that is accepted
returns 202 Accepted and can still fail later, at render or at the provider.
That failure has no HTTP status: it lands on the send itself, as
failure_code and failure_reason on GET /v1/sends/{id}
(Delivery logs).
One of those codes comes from your own project settings: provider_not_pinned means
the project pins its providers
exclusively and none of the pinned ones could take the send. The send is not routed to any
other provider, and its credits are refunded.
A missing template variable is neither. It renders as an empty string, with no
error and no failure — a forgotten variable ships a message with a blank
subject or a hole in the body. Use the default helper to make the fallback
explicit (Template syntax).