Errors
Fotovid returns RFC 9457 problem details with a stable, machine-readable error code. Branch on the code, not the message.
When a request fails, Fotovid returns an RFC 9457
problem details body with the media type application/problem+json. Successful
synchronous responses are plain JSON with no status field — a 2xx is success,
everything else is a problem document.
Shape
{
"type": "https://api.fotovid.co/errors/validation-error",
"title": "Validation Error",
"status": 400,
"code": "VALIDATION_INVALID_ARGUMENT",
"detail": "source_url must be a valid http(s) URL",
"request_id": "req_01JAB...",
"errors": [
{ "field": "source_url", "code": "VALIDATION_INVALID_ARGUMENT", "message": "must be a valid URL" }
]
}| Field | Always present | Meaning |
|---|---|---|
type | ✓ | URI identifying the error category. |
title | ✓ | Short, human-readable summary. May be reworded over time. |
status | ✓ | HTTP status code, mirrors the response line. |
code | ✓ | Stable UPPER_SNAKE application code. Branch on this. |
detail | — | Human-readable, per-occurrence message (public-safe). |
request_id | — | Correlation id — include it in support requests. |
errors[] | — | Per-field breakdown for validation failures (field, code, message). |
Branch on code, never on detail, title, or status alone. One HTTP
status maps to several codes — e.g. 429 can be RATE_LIMIT_EXCEEDED,
RESOURCE_LIMIT_EXCEEDED, or CONCURRENT_LIMIT_EXCEEDED, and 413 can be
REQUEST_BODY_TOO_LARGE or SOURCE_TOO_LARGE. detail and title are
human-readable and may change.
The code contract
Error codes are additive-only: new codes are added over time, but a shipped
code is never renamed or repurposed. Treat any code your client doesn't
recognize as a generic failure bucketed by status — and don't crash on it.
Common codes
Client / validation
| Code | Status | When |
|---|---|---|
VALIDATION_INVALID_ARGUMENT | 400 | A parameter is invalid. |
VALIDATION_MISSING_FIELD | 400 | A required field is missing. |
REQUEST_BODY_INVALID_JSON | 400 | Body isn't valid JSON. |
UNKNOWN_TASK_TYPE | 400 | Unrecognized type. |
CONTENT_TYPE_UNSUPPORTED | 415 | Request Content-Type unsupported. |
REQUEST_BODY_TOO_LARGE | 413 | The request body is too large. |
Auth / permission
| Code | Status | When |
|---|---|---|
AUTH_MISSING_TOKEN | 401 | No API key supplied. |
AUTH_INVALID_TOKEN | 401 | Invalid or malformed key. |
AUTH_TOKEN_EXPIRED | 401 | Key expired. |
PERMISSION_FORBIDDEN | 403 | Authenticated but not allowed. |
INSUFFICIENT_CREDITS | 402 | Not enough credit balance to run the job. |
Resources & tasks
| Code | Status | When |
|---|---|---|
TASK_NOT_FOUND | 404 | No such task. |
TASK_NOT_CANCELABLE | 409 | Task already in a terminal state. |
TASK_ALREADY_CANCELED | 409 | Task already canceled. |
RESOURCE_NOT_FOUND | 404 | Generic resource not found. |
Source media & processing
| Code | Status | When |
|---|---|---|
SOURCE_UNREACHABLE | 502 | Couldn't fetch source_url. |
SOURCE_TOO_LARGE | 413 | Source media exceeds the size cap. |
UNSUPPORTED_MEDIA | 415 | Source format not supported. |
PROCESSING_TIMEOUT | 504 | Exceeded the synchronous time budget — retry via async. |
PROCESSING_FAILED | 500 | Processing failed. |
SERVICE_UNAVAILABLE | 503 | Temporarily unavailable / sync pool full. Carries a Retry-After header (seconds) — honor it before retrying, as with 429. |
Rate limiting
| Code | Status | When |
|---|---|---|
RATE_LIMIT_EXCEEDED | 429 | Request-rate limit (L1). |
RESOURCE_LIMIT_EXCEEDED | 429 | Generation-rate limit (L2). |
CONCURRENT_LIMIT_EXCEEDED | 429 | Too many concurrent tasks (L3). |
See Rate limits for the headers and how to back off.