Errors
Every error answer of the API is a problem details object (RFC 9457) with Content-Type: application/problem+json. MCP tools return the same code and detail; see Tool rules.
The error body
{
"type": "https://postwind.dev/errors/key_invalid",
"title": "API key invalid",
"status": 401,
"detail": "the API key is not valid",
"code": "key_invalid"
}
| Field | Meaning |
|---|---|
type |
https://postwind.dev/errors/<code> |
title |
A short summary, the same for every occurrence of the code |
status |
The HTTP status code |
detail |
What went wrong this time, for people |
code |
The error code, stable; the table below lists every code |
errors |
Every invalid field or unmet precondition, on 422 only; on other statuses the key is left out |
Each item of errors is { "field", "code", "message" }, for example { "field": "name", "code": "invalid", "message": "must not be empty" }. The full schema is Problem.
detail never repeats a key string sent in the request and never carries the cause of an internal failure. The cause is in the node’s log under the request’s trace_id.
The type URIs are identifiers, as RFC 9457 allows. Use code in your code; this page is the readable reference for each one.
Status codes
| Status | Meaning |
|---|---|
400 |
The body is not JSON |
401 |
No key, or the key is not valid |
403 |
The key lacks a scope or a kind the operation needs |
404 |
No such resource or path, or a resource of another project |
409 |
The state of the resource does not allow the operation |
413 |
The body is larger than 10 MB |
415 |
The body is not sent as application/json |
422 |
Validation failed, or preconditions are not met; every violation is listed |
429 |
The key is over its rate limit; Retry-After says when to try again |
500 |
A failure inside Postwind; the cause is in the log |
504 |
The storage did not answer in time; the outcome is unknown |
Every 401 carries WWW-Authenticate: Bearer. Every 429 carries Retry-After. A 504 has no Retry-After.
An unknown path under /api/v1 is 404 not_found, answered before the key is checked.
Error codes
The code values are the same in MCP tool errors.
Field codes
On 422, each item of errors names a field and a code. Nested fields are named by path, for example settings.tracking.opens. An error about the whole body has an empty field.
| Field code | When |
|---|---|
unknown_field |
The operation has no such field, nested ones included (settings.colour) |
required |
A required field is missing |
invalid_type |
The JSON type is wrong, or the field is null and cannot be cleared |
invalid |
The value breaks the rule of the field: a pattern, a length, the syntax of an email address |
out_of_range |
A number is outside the allowed range |
unknown_relay |
relay:<name> is not declared in the node’s config |
transport_unavailable |
Only in precondition_failed, field transport: the node has no adapter for the project’s transport. available_transports in GET /api/v1/server lists those it has |
domain_not_verified |
Only in precondition_failed of a launch, field from_email: the sender’s domain is not a sending domain of the project, or it is not verified |
no_recipients |
Only in precondition_failed of a launch, on the campaign’s lists: no recipients are left after deduplication and suppression |
quota_exceeded |
Only in precondition_failed of a launch, field settings.daily_send_quota: there are more recipients than the project’s daily quota has left |
Form errors come first. A body with errors of form and of values gets the form errors only; after the form is fixed, the values are checked, and again every violation comes back at once. The full order of checks is on Request bodies.
What a 504 means
504 storage_timeout means that the outcome is unknown, not that nothing happened. A write that the node stopped waiting for can still reach the storage later.
GETcan be repeated at any time.PATCH,DELETEand revoking a key can be repeated: the same call gives the same result. A revoked key answers204again.- A
POSTwithoutIdempotency-Keyis checked first. A project that was created makes the repeat fail with409 conflicton itsslug. A key that was created makes the repeat create a second key, and nobody has the first key string: find it bynameandcreated_atin the key list and revoke it. - A
POSTwithIdempotency-Keynever runs twice: the repeat gets409 idempotency_key_in_progressuntil the key’s 24 h end. Check whether the resource exists, then go on with a new key.
A request whose storage call hangs answers no later than twice the time limit after its usual time: 10 s with the default of 5 s. A client timeout shorter than that can cut off an answer, including a 201 with the string of a new key.