Errors
The error envelope
Almost all 4xx / 5xx responses share one shape:
{
"detail": "human-readable message",
"code": "optional.stable.code"
}
detailis a human-readable explanation. On this envelope it is always a string.codeis a stable, machine-readable reason such asno_valid_urlsorquota_exceeded_videos. It is present only when the failure has a stable reason and absent (ornull) otherwise, so branch on its presence rather than assuming it is there.
Three cases do not follow the envelope exactly, so check the type of detail before reading it:
- Schema validation returns FastAPI's array form (next section).
- A downstream error body the gateway cannot interpret is relayed as-is rather than rewritten.
- An error raised outside the API surface, such as an edge proxy, can arrive as an opaque body with no envelope at all.
Validation errors (422 array form)
A request rejected by schema validation returns FastAPI's list form: detail is an array of field errors instead of a string.
{
"detail": [
{
"loc": ["body", "url"],
"msg": "Field required",
"type": "missing"
}
]
}
Any endpoint that declares a body can return this. The common case is sending an over-length items array to POST /api/v1/videos/batch.
422 is not always the array form. Some routes raise their own 422 with the string envelope and a code, for example POST /api/v1/videos/batch/upload-sheet on an unsupported file type or a sheet with no url column. Branching on typeof detail handles both.
Special codes
upstream_error_too_large
This code is produced by the gateway itself, not by the failing service. It means a downstream error body was too large to relay. The HTTP status code is preserved, but the per-item reasons are gone. Retry with fewer items to get the detailed response.
rejected on sheet upload
Sheet parse failures on POST /api/v1/videos/batch/upload-sheet (413 and 422) carry one extra sibling key when code is no_valid_urls: rejected, the number of rows dropped while parsing the sheet. Those rows were never recorded in any batch.
{
"detail": "No valid URL rows found in the sheet",
"code": "no_valid_urls",
"rejected": 12
}
Job failures are not HTTP errors
A video that fails to index still returns 200 from GET /api/v1/jobs/{job_id}; the failure is in the body as status: "failed" with a stable reason_code and the raw error text. Branch on reason_code, not on the HTTP status.
HTTP status codes
| HTTP | Meaning | Handling |
|---|---|---|
401 | Missing or invalid API key. | Check the Authorization: Bearer ... header. See Authentication. |
403 | Key used outside the public API roots (/api/v1, /api/v1.1). | API keys do not open dashboard routes. See Authentication. |
404 | Referenced video, job or ad not found. | Typo, or the resource belongs to another organization. GET /api/v1/usage also returns 404 when no quota plan exists for the organization yet. |
409 | Ad not ready. | The ad_id exists but its status is not ready (still resolving, or failed). Poll GET /api/v1/ads/{ad_id} and retry once it is ready. |
413 | Upload too large. | On sheet upload: the file exceeds the 10 MB cap or has more valid URL rows than one batch accepts (currently 500). Split into multiple sheets. |
422 | Request body failed validation. | Fix the request. detail names the offending field (array form) or carries a code (envelope form). |
500 | Upstream service error. | Usually safe to retry. For file uploads, check the batch or video first; see Usage and limits. |
503 | Quota datastore temporarily unavailable. | Returned by GET /api/v1/usage. Retry. |
Retry guidance
500is usually retry-safe. The gateway relays an upstream failure; the request itself is usually fine. The exception is a file upload, which has no dedup key: look the batch or video up before resending.503onGET /api/v1/usageis transient. Retry.409resolves itself once the ad finishes resolving. Poll the ad, do not resubmit the screening blindly.upstream_error_too_largemeans the request was too big for the error path, not that it failed permanently. Retry with fewer items.4xxother than the above will not change on retry. Fix the request.
POST /api/v1/videos is idempotent for a URL that is still being indexed: resubmitting returns the existing video_id / job_id with created: false, so retrying a submission after a network error does not start a duplicate pipeline.