~/docs cat errors.md
Errors
Errors are conventional HTTP, and they are never dressed up as a verdict: a caller out of credits sees a 402, not a reassuring answer. Every error body carries a stable code to branch on and a message written for people, which may change. A 429 from the rate limiter is the one body without success: false: it is {error, message}, or plain text from the per-tier limiter.
| Status | Code | Meaning and fix |
|---|---|---|
| 400 | NO_FILE_UPLOADED | Neither file nor url was sent. Attach one as file, or send url. |
| 400 | INVALID_URL | url is not an http or https link. |
| 401 | UNAUTHORIZED | The x-api-key header is missing or the key is not recognised. |
| 402 | INSUFFICIENT_CREDITS | Out of credits. Top up in the dashboard, then retry. |
| 403 | API_KEY_SCOPE | The key is scoped to another service. Use a universal key. |
| 403 | FORBIDDEN | The account's email is not verified yet; the message begins EMAIL_VERIFICATION_REQUIRED. Verify it in the dashboard. |
| 404 | NOT_FOUND | No stored detection with that id for this account. Runs sent with save=false are not stored. |
| 413 | FILE_TOO_LARGE | The file is over the cap for its media type. The message names the cap. |
| 413 | LINK_MEDIA_TOO_LARGE | The video behind the link is over 100 MB. |
| 422 | UNSUPPORTED_MEDIA_TYPE | Not a file a detector can read: a text file, an archive, an unknown format. |
| 422 | UNDECODABLE_IMAGE | An empty file, or bytes that decode to nothing. |
| 422 | LINK_NOT_RESOLVABLE | The url does not resolve to a video page or a media file the server can fetch: a web page, a private or deleted video, a playlist, a live stream, a video over 5 minutes. The code arrives as a string in error, with retryable and the resolver's resolverCode beside it. Answered as 502 when the resolver itself failed. |
| 422 | NOT_ASSESSABLE | The platform looked and could not judge. Today an audio outcome: reasons[] carries no_voice_content (no speech in any window) or audio_too_short (shorter than the analyzer accepts). Every decodable image and video frame is scored, face or not. Show the reasons; do not retry the same file. Nothing is charged. |
| 429 | Too many requests this minute. Back off; honour Retry-After. | |
| 429 | DAILY_LIMIT | The account's daily usage ceiling is reached. Refused before the run starts; see Credits and limits. |
| 429 | MONTHLY_LIMIT | The account's monthly usage ceiling is reached. Refused before the run starts. |
| 500 | DETECTORS_UNAVAILABLE | No detector could take the run. Retry with backoff, then contact support. |
| 500 | INTERNAL_ERROR | An unexpected gateway failure. Retry with backoff, then contact support. |
| 501 | LINK_RESOLUTION_UNAVAILABLE | This deployment cannot fetch links. Upload the file instead. |
| 502 | DETECTION_NOT_BILLABLE | The run could not be metered and was not charged. Resubmit only if you still need the answer. |
| 502 | LINK_RESOLUTION_FAILED | The resolver could not fetch the media behind the link. Retry once; then upload the file. |
| 503 | BILLING_UNAVAILABLE | The usage gate could not be read, or the debit did not land after the run. Nothing is charged or stored and the verdict is withheld. A retry is an ordinary charged run. |
| 504 | DETECTION_PLATFORM_TIMEOUT | The platform did not answer in time. The run may still have completed; check history before re-sending. |
| 504 | DETECTION_PLATFORM_UNREACHABLE | The platform could not be reached. Retry with backoff. |
| 504 | LINK_RESOLUTION_TIMEOUT | The link took too long to fetch. Retryable. |
| 504 | LINK_RESOLUTION_UNAVAILABLE | The resolver could not be reached. Retryable; the same code as 501 means the deployment has no resolver at all. |
A 422 with reasons[] is an answer, not an outage. Its body looks like this:
{
"success": false,
"error": {
"code": "NOT_ASSESSABLE",
"message": "no detector could score this media"
},
"reasons": ["no_voice_content"],
"request_id": "6f1c2a3e-9b7d-4e5f-8a21-3c4d5e6f7a8b"
}Route and platform refusals carry the code as error.code with the message beside it, as above. Link refusals (INVALID_URL, LINK_*) and the key-scope refusal (API_KEY_SCOPE) carry the code as error directly, a string, with message next to it; link refusals also carry retryable and the resolver's own resolverCode. Errors answered by the gateway's central handler (401, 403 FORBIDDEN) also carry timestamp and path; refusals the route answers itself do not. Neither is part of the contract; error.code is. The SDKs fold every shape into one exception. Keep the id from every response, or the request_id when an error carries one (platform refusals, 502, 503), when you write to support@scam.ai.