Error Reference

Every API error is one nested error object. Branch on error.type (coarse category) or switch on error.code (specific), with a human-readable error.message and an optional error.action that tells you exactly what to do next.

Response Shape

json
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limited",
    "message": "Too many requests. Retry later.",
    "action": { "type": "wait", "retry_after": 60, "label": "Retry later" }
  }
}

The action and details fields live inside the error object and are only present on certain errors. action.retry_after (seconds) is only present for wait actions; the Retry-After HTTP header carries the same value. Success responses never carry an error or an action.

Error types

error.type is a coarse, stable category. Branch on it to handle a whole class of errors without enumerating every code.

invalid_request_error
The request was malformed, referenced something invalid, targeted an unavailable workspace, or lost a guarded write race. Codes: validation_error, not_found, workspace_unavailable, state_conflict, bad_request, …
authentication_error
Missing or invalid credentials. Codes: unauthorized.
permission_error
Authenticated, but not allowed. Codes: forbidden, media_locked, media_blocked.
rate_limit_error
Too many requests. Codes: rate_limited.
quota_error
Reserved category for quota_exceeded. Not emitted during the free transition; plans do not block uploads, storage, transformations, or App creation.
idempotency_error
Idempotency-Key reused with a different body, retried while the original is still in flight, or no longer matches its canonical create row. Codes: idempotency_key_conflict, idempotency_key_in_progress, idempotency_recovery_conflict.
processing_error
Upload / import processing failed. Codes: upload_failed, fetch_failed, import_failed, media_failed.
api_error
Server-side failure or a temporarily indeterminate/unavailable server state. Retryable examples include app_context_unavailable and create_outcome_ambiguous; terminal examples include update_failed and delete_failed.

Action types

When present, error.action tells you exactly what to do next. The type is a closed enum:

wait retry unchanged
The operation is temporarily blocked by a rate limit, unavailable App API authority/context, an identical create still in flight, or an indeterminate create write being reconciled. Back off for error.action.retry_after seconds (the Retry-After header carries the same value), then retry the same operation.

Error Codes

unauthorized

HTTP 401 · type: authentication_error. Invalid or missing API key.

json
{
  "error": {
    "type": "authentication_error",
    "code": "unauthorized",
    "message": "Invalid or missing API key"
  }
}

forbidden

HTTP 403 · type: permission_error. Valid key but insufficient permissions.

json
{
  "error": {
    "type": "permission_error",
    "code": "forbidden",
    "message": "Insufficient permissions"
  }
}

media_locked

HTTP 403 · type: permission_error. The image is moderation-locked and can’t be modified: PATCH (single or batch) returns this. Deletion is not blocked: an owner can still DELETE a locked image. In a batch update, the locked id appears in the errors array while the rest of the batch still applies.

json
{
  "error": {
    "type": "permission_error",
    "code": "media_locked",
    "message": "Media cannot be modified"
  }
}

media_blocked

HTTP 403 · type: permission_error. The image was blocked by moderation and can’t be retrieved. Blocked images never appear in lists; this error is what a direct GET /v1/images/:id returns instead of the object. When the request is authenticated as the image’s owner, the message includes the coarse reason. The reason is always one of exactly four values: content_policy, dmca, spam, or terms.

json
{
  "error": {
    "type": "permission_error",
    "code": "media_blocked",
    "message": "This image was blocked (content_policy) and is no longer available."
  }
}

media_failed

HTTP 422 · type: processing_error. The image’s processing failed and it will never become servable. Failed images never appear in lists; direct GET and PATCH requests return this error instead of an Image object, and batch PATCH reports it for the affected item. The message explains what went wrong so you can correct it and re-upload.

json
{
  "error": {
    "type": "processing_error",
    "code": "media_failed",
    "message": "This HEIC image could not be read."
  }
}

rate_limited

HTTP 429 · type: rate_limit_error. Too many requests. Includes a Retry-After HTTP header.

Back off for error.action.retry_after seconds when a wait action is present; the Retry-After header carries the same value.

json
{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limited",
    "message": "Too many requests. Retry later.",
    "action": {
      "type": "wait",
      "retry_after": 2520,
      "label": "Retry later"
    }
  }
}

validation_error

For URL imports, a disallowed destination or redirect returns this code with details.url. Fix the URL before retrying.

HTTP 422 · type: invalid_request_error. Invalid input: a bad TTL, a missing required field, a file that’s too large, unsupported, or not decodable by the Images binding, a non-patchable field on PATCH, an unsupported or malformed user selector, a malformed public value on create or PATCH (including null or blank), an exceeded metadata or labels limit, a malformed label[…] list filter, and so on. Carries a details field with per-field messages; size/format/decoder problems report under file (for both multipart uploads and URL imports). A rejected create leaves no Image to poll or delete.

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "validation_error",
    "message": "Validation failed",
    "details": {
      "file": ["This JPEG image could not be decoded for transformation. The file may be damaged, exceed image dimension limits, or use an unsupported variant."],
      "ttl": ["TTL must be at least 5 minutes (300 seconds)"]
    }
  }
}

not_found

HTTP 404 · type: invalid_request_error. The media or resource doesn’t exist or isn’t accessible.

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "not_found",
    "message": "Media not found"
  }
}

workspace_unavailable

HTTP 409 · type: invalid_request_error. The destination workspace is being deleted or is otherwise fenced against new writes. Do not create a replacement blindly: let the user restore or select an active workspace, then retry the request.

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "workspace_unavailable",
    "message": "Workspace is being deleted"
  }
}

state_conflict

HTTP 409 · type: invalid_request_error. A PATCH request supplied if_labels, but at least one expected label changed before the update committed. No requested fields were applied. Reload the image, then decide from its current state whether a new transition is still appropriate.

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "state_conflict",
    "message": "Image labels changed before the update was applied"
  }
}

idempotency_key_conflict

HTTP 409 · type: idempotency_error. You reused an Idempotency-Key with a different request body. Use a fresh globally unique key for each logical create, or send the exact same body again during the 24-hour retry window to recover the original. After that window, replay and duplicate prevention are not guaranteed.

json
{
  "error": {
    "type": "idempotency_error",
    "code": "idempotency_key_conflict",
    "message": "Idempotency-Key was already used with a different request body"
  }
}

idempotency_key_in_progress

HTTP 409 · type: idempotency_error. A request with this Idempotency-Key is still being processed; a concurrent retry arrived before the original finished. The key is claimed before the work runs, so a retry backs off instead of creating a duplicate. Wait for Retry-After (mirrored in error.action.retry_after) and retry the same request.

json
{
  "error": {
    "type": "idempotency_error",
    "code": "idempotency_key_in_progress",
    "message": "A request with this Idempotency-Key is still being processed. Retry shortly.",
    "action": { "type": "wait", "retry_after": 2 }
  }
}

idempotency_recovery_conflict

HTTP 409 · type: idempotency_error. The canonical image identity for this key exists but no longer matches a safely recoverable version of the original operation. Stop automatic retries and inspect the operation or use a new key for genuinely new work.

json
{
  "error": {
    "type": "idempotency_error",
    "code": "idempotency_recovery_conflict",
    "message": "The original media operation can no longer be recovered safely"
  }
}

create_outcome_ambiguous

HTTP 503 · type: api_error. img.pro could not yet prove whether the canonical create write committed. Honor Retry-After and retry the identical request; do not change its body or key.

json
{
  "error": {
    "type": "api_error",
    "code": "create_outcome_ambiguous",
    "message": "The media operation is still being reconciled. Retry the same request.",
    "action": { "type": "wait", "retry_after": 2 }
  }
}

upload_failed

HTTP 500 · type: processing_error. Internal processing failure (an unexpected error while storing or transforming a valid upload). Caller-correctable problems, like too-large or unsupported files, are validation_error (422), not this; upload_failed means "retry later".

json
{
  "error": {
    "type": "processing_error",
    "code": "upload_failed",
    "message": "Upload failed"
  }
}

fetch_failed

HTTP 502 / 504 · type: processing_error. A URL import couldn’t fetch the source. An unavailable or invalid upstream response returns 502, and the 30-second request deadline returns 504.

json
{
  "error": {
    "type": "processing_error",
    "code": "fetch_failed",
    "message": "Could not fetch the URL"
  }
}

import_failed

HTTP 500 · type: processing_error. A URL import failed after the fetch succeeded.

json
{
  "error": {
    "type": "processing_error",
    "code": "import_failed",
    "message": "Import failed"
  }
}

update_failed

HTTP 500 · type: api_error. A media update failed server-side.

json
{
  "error": {
    "type": "api_error",
    "code": "update_failed",
    "message": "Update failed"
  }
}

delete_failed

HTTP 500 · type: api_error. A media deletion failed server-side.

json
{
  "error": {
    "type": "api_error",
    "code": "delete_failed",
    "message": "Delete failed"
  }
}

App API codes

App-wide keys (with optional X-Img-User) can also see a few App-specific codes: invalid_code (422), user_forbidden (403), app_suspended (403), and retryable app_context_unavailable (503). They use the same envelope; see Building an app → Errors for what each means and what to do.

Handling Errors in Code

Here’s a comprehensive example showing how to handle errors, including action-based responses:

python
import requests
import time

def upload_image(api_key, filepath, caption=None):
    response = requests.post(
        "https://test.api.img.pro/v1/images",
        headers={"Authorization": f"Bearer {api_key}"},
        files={"file": open(filepath, "rb")},
        data={"caption": caption} if caption else {}
    )

    if response.ok:
        return response.json()

    err = response.json().get("error") or {}
    action = err.get("action") or {}

    # Branch on the coarse category, or the specific code.
    if action.get("type") == "wait":
        # Rate-limited with a known retry window. Back off and retry.
        time.sleep(action.get("retry_after", 60))
        return upload_image(api_key, filepath, caption)

    raise Exception(f"Upload failed [{err.get('type')}/{err.get('code')}]: {err.get('message')}")