Errors
Handle stable error codes
Every non-2xx API response uses the same envelope so callers can branch on code, log request_id, and retry safely when appropriate.
RESTBearer authJSON responses
Error envelope
{
"error": {
"code": "validation_error",
"message": "source_url must be an https URL",
"request_id": "req_01HXYZ..."
}
}Server-side only. Keep API keys out of client bundles.
Errors
Every non-2xx response shares the same envelope.
{
"error": {
"code": "validation_error",
"message": "source_url must be an https URL",
"request_id": "req_01HXYZ..."
}
}Branch on error.code — it is the stable contract. Include request_id when you escalate.
Error codes
| Code | Status | Meaning | Caller action |
|---|---|---|---|
| unauthorized | 401 | Authorization header missing or malformed. | Send `Authorization: Bearer $AIFACESWAP_API_KEY` and retry. |
| invalid_api_key | 401 | Bearer token does not match a live API key. | Issue a new key in /dashboard/api-keys and replace the token. |
| validation_error | 400 | Request body or query failed schema validation. | Fix the offending field per the `message`. Do not retry blindly. |
| source_unreachable | 400 | `source_url` or `target_url` resolved to a non-public address or failed DNS. | Host the asset on a public HTTPS URL and retry. |
| unsupported_media_type | 415 | Content-Type is not `application/json`, or fetched media is outside the allowlist for the declared `type`. | Send `Content-Type: application/json` and supply correctly typed URLs. |
| payload_too_large | 413 | Fetched `source_url` or `target_url` exceeds the per-type byte cap. | Re-encode or shrink the asset to fit the image/GIF cap. |
| nsfw_content_detected | 422 | One or more inputs were flagged by the content analyser. | Use different inputs. This is terminal — retrying with the same URLs will fail again. |
| insufficient_credits | 402 | Credit balance below the computed cost for this swap. | Top up at /pricing, then retry with a fresh `Idempotency-Key`. |
| rate_limited | 429 | Per-minute (60 req/min) sliding-window budget exceeded. | Back off until `Retry-After` seconds, or `x-ratelimit-reset` (unix epoch), elapses. |
| rate_limited_daily | 429 | Daily cap (10,000 req/day, UTC) exceeded. | Wait until 00:00 UTC. Spread bursty workloads across the day. |
| rate_limited_concurrent | 429 | More than 10 in-flight `POST /swaps` for this API key. | Wait for an existing swap to finalise (poll `GET /swaps/{taskId}` or use a webhook), then retry. |
| not_found | 404 | Task does not exist, was cleaned up, or is owned by a different key. | Confirm the `taskId` and that it was created by this key. Identical envelope is returned for foreign tasks. |
| method_not_allowed | 405 | HTTP method not supported by this endpoint (e.g. POST to `/me`). | Use the documented method. |
| api_temporarily_disabled | 503 | Public API feature flag is off, or required NSFW checks have not completed yet. | Honour `Retry-After`; if absent, back off exponentially. |
| internal_error | 500 | Queue, DB, or Redis side-effect failed; credits and idempotency claim are rolled back before the response. | Safe to retry. Include the `request_id` from the error envelope if you escalate. |