Errors

All non-2xx responses are JSON with at least an error field.

{ "error": "quota_exhausted" }
HTTPerrorMeaning
400invalid_inputBody or query missing/invalid (details usually in hint or details).
400unsupported_or_corrupt_imageThe bytes don't decode as a supported image.
400unsupported_formatDecoded fine, but the format isn't in the allowed set.
400invalid_url / unsupported_scheme / blocked_private_address / dns_resolution_failedSSRF guards on /v1/shrink-from-url.
400invalid_widths?widths=… empty or out of bounds (16…8192, up to 6).
401missing_token / invalid_token / missing_api_key / invalid_api_keyAuth issue.
402quota_exhaustedFree quota and token balance both at 0.
403account_blocked / forbiddenAccount blocked by admin or admin token invalid.
404job_not_foundGET /v1/jobs/:id for a job that doesn't belong to your account.
409email_taken / no_customer / no_active_subscriptionState conflict; body explains.
410expired / already_usedVerify/reset token used or past TTL.
413file_too_largeUpload over MAX_UPLOAD_BYTES (25 MB by default).
429(rate-limit body)Slow down; honor Retry-After.
500(Fastify default)We log and Sentry these — retry with backoff.
501stripe_not_configuredServer has no Stripe keys configured.
503(readiness body)GET /ready returns 503 when Postgres or Redis is unreachable.
504(Traefik default)Upstream timeout — guetzli on big JPEGs can exceed 30 s; use ?async=true and poll.