Errors
Network reference: For the full cross-tool error code reference, see tools.fast/api/docs/errors.
Error envelope
All API errors return a JSON object with at least two fields:
{
"error": "machine.readable_code",
"detail": "Human-readable explanation."
}
Some errors include additional context fields. URL fetch errors include an isRetryable boolean indicating whether the request can be retried (e.g. transient network failures vs permanent validation errors).
400 Bad Request
Invalid request payload, parameters, or content type.
{
"error": "request.invalid_content_type",
"detail": "multipart/form-data or application/json required."
}{
"error": "request.empty_file",
"detail": "Uploaded file is empty."
}{
"error": "request.no_files",
"detail": "Exactly one file must be provided."
}{
"error": "request.invalid_extension",
"detail": "File extension '.mp3' is not accepted by this compressor."
}URL fetch errors (400)
Returned when URL-based input (inputUrl) fails validation or the remote server returns an error.
{
"error": "url_fetch.invalid_url",
"detail": "inputUrl is required in JSON body.",
"isRetryable": false
}{
"error": "url_fetch.invalid_scheme",
"detail": "Only HTTPS URLs are allowed.",
"isRetryable": false
}{
"error": "url_fetch.private_ip",
"detail": "URL resolved to blocked IP address (private/reserved).",
"isRetryable": false
}{
"error": "url_fetch.mutually_exclusive",
"detail": "Cannot provide both a file upload and inputUrl. Use one or the other.",
"isRetryable": false
}{
"error": "url_fetch.output_url_reserved",
"detail": "outputUrl is reserved for future use and not yet supported.",
"isRetryable": false
}{
"error": "url_fetch.http_error",
"detail": "Remote server returned 503 Service Unavailable.",
"isRetryable": true
}401 Unauthorized
The API key is missing, invalid, or not allowed from the caller's IP.
{
"error": "api_key.invalid_or_ip_not_allowed",
"detail": "X-Fast-Api-Key was provided but is invalid for this request (or IP not allowlisted)."
}
This error intentionally does not distinguish between an invalid key and an IP not in the allowlist.
402 Payment Required
The caller's account does not have enough credits.
{
"error": "entitlements.insufficient_credits",
"detail": "Insufficient credits. Required: 5, Available: 2"
}
403 Forbidden
The resource exists but the caller does not own it, or the action is not allowed for the caller's account.
{
"error": "jobs.forbidden",
"detail": "You do not have access to this job."
}{
"error": "url_fetch.guest_not_allowed",
"detail": "URL-based input is not available for guest users. Please sign in.",
"isRetryable": false
}404 Not Found
The requested job does not exist.
{
"error": "jobs.not_found",
"detail": null
}
409 Conflict
The request conflicts with the current state of the resource.
{
"error": "jobs.not_ready",
"detail": "Job is still processing or waiting to start."
}{
"error": "jobs.not_cancellable",
"detail": "Cannot cancel completed, failed, or canceled jobs."
}{
"error": "jobs.not_deletable",
"detail": "Cannot delete a job that is still queued or running. Cancel it first."
}410 Gone
The job completed but its output artifacts have expired and been cleaned up. Output artifacts are retained for 1 hour after job completion, then automatically deleted. To clean up immediately after downloading, call DELETE /compress/job/{id}.
{
"error": "jobs.expired",
"detail": "Job artifacts were deleted on 2025-01-15 12:00:00Z after the retention period expired."
}
413 Payload Too Large
The uploaded file exceeds the per-compressor or global size limit.
{
"error": "request.file_too_large",
"detail": "File exceeds the maximum allowed size.",
"megabytes": 50
}
429 Too Many Requests
The caller has been throttled. Check the Retry-After header.
{
"error": "rate_limited",
"detail": "Too many requests. Retry after 12 seconds."
}{
"error": "queue.limit_exceeded",
"detail": "Too many queued jobs. Wait for existing jobs to complete."
}{
"error": "url_fetch.rate_limited",
"detail": "Too many concurrent URL fetches. Please wait and retry.",
"isRetryable": true
}Headers on 429 responses:
| Header | Description |
|---|---|
Retry-After | Seconds until you can retry |
Best practice: implement exponential backoff in your client, or honour the Retry-After value directly.
5xx Server Error
Server-side errors are rare but possible. Retry with exponential backoff (1s, 2s, 4s, 8s, max 30s). If errors persist, contact support@tools.fast.
Error codes reference
| Code | Status | Description |
|---|---|---|
request.invalid_content_type | 400 | Must use multipart/form-data or application/json |
request.no_files | 400 | No file attached |
request.empty_file | 400 | Uploaded file has zero bytes |
request.invalid_extension | 400 | File extension not accepted by compressor |
request.multiple_files | 400 | Only one file per request |
request.file_too_large | 413 | File exceeds size limit |
api_key.invalid_or_ip_not_allowed | 401 | Bad API key or IP not in allowlist |
entitlements.insufficient_credits | 402 | Not enough credits |
jobs.forbidden | 403 | Job owned by another user |
jobs.not_found | 404 | Job ID does not exist |
jobs.not_ready | 409 | Download requested before job finishes |
jobs.not_cancellable | 409 | Job already completed/failed |
jobs.not_deletable | 409 | Cannot delete queued/running jobs — cancel first |
jobs.expired | 410 | Output artifacts cleaned up |
rate_limited | 429 | IP rate limit exceeded |
queue.limit_exceeded | 429 | Too many queued jobs |
compress.invalid_webhook | 400 | Webhook URL or secret failed validation |
compress.unsupported_format | 400 | File format not recognized by the universal compressor |
compress.{format}.validation.unsupported_format | 400 | File format not accepted by the format-specific compressor |
estimate.unsupported_format | 400 | No compressor found for the requested format |
schema.unsupported_format | 400 | No compressor found for the requested format |
url_fetch.invalid_url | 400 | URL is missing, empty, or malformed |
url_fetch.invalid_scheme | 400 | Only HTTPS allowed |
url_fetch.private_ip | 400 | URL resolved to private/reserved IP (SSRF blocked) |
url_fetch.dns_failure | 400 | DNS resolution failed for the URL host |
url_fetch.timeout | 400 | Remote server did not respond in time |
url_fetch.too_large | 400 | Remote file exceeds size limit |
url_fetch.invalid_content | 400 | Remote server returned an empty response |
url_fetch.http_error | 400 | Remote server returned an error status |
url_fetch.redirect_loop | 400 | Redirect chain exceeded maximum hops |
url_fetch.redirect_denied | 400 | Redirect target resolved to private/reserved IP |
url_fetch.domain_circuit_open | 400 | Domain temporarily blocked due to repeated failures |
url_fetch.url_expired | 400 | Presigned URL has expired (S3, GCS, Azure SAS) |
url_fetch.no_filename | 400 | Could not determine filename from URL or response headers |
url_fetch.mutually_exclusive | 400 | Cannot provide both file upload and inputUrl |
url_fetch.output_url_reserved | 400 | outputUrl is not yet supported |
url_fetch.guest_not_allowed | 403 | URL input requires a signed-in account |
url_fetch.rate_limited | 429 | Too many concurrent URL fetches |
compare.not_ready | 409 | Job not yet succeeded for comparison |
compare.expired | 410 | Output artifacts cleaned up |
compare.timeout | 504 | Preview generation timed out |
server.internal_error | 500 | Unexpected server error — retry with exponential backoff |