All Tupshar API errors use a consistent JSON envelope and a fixed set of error codes.
Response Envelope
{
"error": {
"code": "not_found",
"message": "Document 01HXYZ not found",
"request_id": null
}
}
The error field is an object (not a string) with:
| Field | Type | Description |
|---|---|---|
code | string | Machine-readable error code (see table below) |
message | string | Human-readable description |
request_id | string | null | Opaque request identifier for support; may be null |
details | object | Present on validation_failed — field-level validation errors |
Error Code Reference
| HTTP Status | Code | When you'll see it |
|---|---|---|
| 400 | bad_request | Invalid query mode/parameters, unparseable scopes, or unknown filename match mode |
| 401 | unauthorized | Missing or invalid Authorization header, or revoked/expired key |
| 403 | forbidden | Key lacks permission for the requested resource |
| 403 | quota_exceeded | Over your document count or storage quota |
| 404 | not_found | Document ID does not exist, or endpoint URL is wrong |
| 409 | conflict | A document with that filename already exists |
| 412 | precondition_failed | If-Match header provided but ETag did not match |
| 413 | payload_too_large | Request body exceeds 8 MiB, or document contents exceed 4 MiB |
| 415 | unsupported_media_type | Document contents contained a NUL byte or a forbidden control character |
| 422 | validation_failed | Request parsed but failed semantic validation; see details |
| 422 | unprocessable_entity | Request parsed but cannot be acted on as submitted |
| 428 | precondition_required | PUT sent without an If-Match header |
| 429 | rate_limited | Over 60 requests/min sustained rate |
| 500 | internal | Unexpected server error |
| 501 | not_implemented | Endpoint or feature not yet available |
Examples
validation_failed (422)
{
"error": {
"code": "validation_failed",
"message": "Request validation failed",
"request_id": null,
"details": {
"filename": "must not be empty"
}
}
}
precondition_required (428)
PUT /v1/file/:id requires an If-Match: <etag> header to prevent lost updates. Sending a PUT without one returns:
{
"error": {
"code": "precondition_required",
"message": "PUT requires an If-Match header with the current ETag",
"request_id": null
}
}
quota_exceeded (403)
{
"error": {
"code": "quota_exceeded",
"message": "Storage quota exceeded",
"request_id": null
}
}
rate_limited (429)
Rate-limit responses include a Retry-After header (seconds to wait). There are no X-RateLimit-* headers.
HTTP/1.1 429 Too Many Requests
Retry-After: 14
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded",
"request_id": null
}
}
Retry Guidance
| Code | Retryable? | Guidance |
|---|---|---|
bad_request | No | Fix the request |
unauthorized | No | Check/rotate API key |
forbidden | No | Check permissions or quota |
quota_exceeded | No | Free up quota first |
not_found | No | Document does not exist |
conflict | No | Document already exists; use PUT to update |
precondition_failed | Conditional | Re-fetch the ETag, then retry |
precondition_required | Conditional | Add If-Match header and retry |
payload_too_large | No | Reduce request/document size |
unsupported_media_type | No | Add Content-Type: application/json |
validation_failed | No | Fix the failing fields |
unprocessable_entity | No | Review request semantics |
rate_limited | Yes | Wait for Retry-After seconds, then retry |
internal | Yes | Retry with exponential backoff |
not_implemented | No | Feature not available |
For quota limits and rate limit details see @/docs/reference/quotas.md.