Errors¶
Every non-2xx response uses one envelope. code is stable and machine-readable;
details appears only when there is something concrete to add.
{
"error": {
"code": "too_many_fields",
"message": "Submission carries 120 fields, which exceeds the limit of 100.",
"details": { "limit": 100, "received": 120 }
}
}
Nothing in the envelope exposes internal types, stack frames, file paths, table names or SQL.
Every code¶
| Status | code |
Cause |
|---|---|---|
| 400 | malformed_form_body |
Body does not parse as the declared content type |
| 400 | invalid_idempotency_key |
Idempotency-Key header breaks the key format rules |
| 401 | authentication_required |
A management route was called without a bearer credential |
| 401 | invalid_api_key |
The management API key is malformed, unknown or revoked |
| 404 | invalid_endpoint_id |
Submission path is not a well-formed endpoint ID |
| 404 | endpoint_not_found |
Endpoint ID is well formed but no such endpoint exists |
| 404 | delivery_not_found |
No delivery with that ID exists |
| 404 | submission_not_found |
No submission with that ID exists |
| 404 | not_found |
Unknown path |
| 405 | method_not_allowed |
Wrong method for a known path |
| 409 | endpoint_inactive |
Endpoint exists but is not accepting submissions |
| 409 | endpoint_already_exists |
Endpoint ID is already taken |
| 409 | idempotency_conflict |
Idempotency key already used for different content |
| 409 | delivery_not_replayable |
Delivery has not terminally failed |
| 413 | request_body_too_large |
Body exceeded FORMS_MAX_BODY_BYTES |
| 415 | unsupported_media_type |
Content type is not a supported form encoding |
| 422 | empty_submission |
No fields were submitted |
| 422 | invalid_cursor |
Pagination cursor does not continue from a known row |
| 422 | invalid_endpoint_id |
Endpoint ID in a request body breaks the ID rules |
| 422 | invalid_request |
Request body or query parameters failed schema validation |
| 422 | invalid_webhook_url |
Webhook destination is malformed or not permitted |
| 422 | invalid_time_range |
received_after is not strictly earlier than received_before |
| 422 | export_too_large |
An export matches more than FORMS_EXPORT_MAX_SUBMISSIONS |
| 422 | unsupported_export_format |
Export format is not json or csv |
| 422 | file_upload_not_supported |
A multipart part carried a file |
| 422 | ingestion rule codes | See below |
| 429 | rate_limit_exceeded |
A public ingestion rate limit was exhausted |
| 500 | internal_error |
Unexpected failure; no internals are exposed |
| 503 | storage_unavailable |
The database could not be reached or written to |
Ingestion rule codes¶
All 422, all from form ingestion:
code |
Cause |
|---|---|
too_many_fields |
More name/value pairs than FORMS_MAX_FIELDS |
field_name_too_long |
A field name exceeds FORMS_MAX_FIELD_NAME_LENGTH |
field_value_too_long |
A field value exceeds FORMS_MAX_FIELD_VALUE_LENGTH |
invalid_field_name |
A field name is empty or contains control characters |
invalid_field_value |
A field value contains a null byte |
Codes that need a note¶
invalid_endpoint_id carries two statuses¶
404 when the ID arrived as a submission path that addresses nothing, and 422
when it arrived as a field in a request body. The same rule was broken; what
differs is whether the caller was addressing something or describing something.
endpoint_not_found and invalid_endpoint_id share a status¶
Both are 404 on the submission route. From outside, both mean the path does not
address a form endpoint. The code tells the two apart.
rate_limit_exceeded carries a header¶
It is the one error that returns Retry-After, in whole seconds. Its details
name which limit tripped (scope is ip or endpoint), the limit, and the
window length. See Rate limiting.
Both 401s carry WWW-Authenticate: Bearer¶
They are deliberately 401 and not 403. A 403 says the caller is known and
not permitted, which needs a permission model this build does not have.
invalid_api_key is one answer for malformed, unknown and revoked credentials
alike, so that a guesser cannot sort their attempts into "nearly right" and
"wrong". The credential a request sent is never echoed back in an error.
export_too_large is a 422, not a 413¶
The request is well formed, and what has to change is the filter, which is part of the request. It is refused rather than truncated so that an export is either everything that matched or an error, never a quietly incomplete file. See Exporting submissions.
invalid_time_range is refused, an empty match is not¶
A filter matching nothing is an empty page, because that is a fact about the
data. A range where received_after is on or after received_before can never
match anything at all, which is a mistake in the request; answering it with an
empty page would hide the mistake.
storage_unavailable rather than 500¶
A database failure returns 503, because the request itself was fine and
retrying it may well succeed. Driver messages carry table names, SQL text and
sometimes connection details, so none of the underlying exception reaches the
client.
internal_error describes nothing¶
An unexpected failure returns an opaque 500. That is deliberate: an error the
service did not anticipate is exactly the one whose details should not be
published.
Related¶
- Submissions, Endpoints, Deliveries and Submission Management for which codes each route can return
- Authentication for the
401cases