Errors

The shapes an error takes, every request error code and task failure code, how a failed task differs from a rejected request, and what is safe to retry.

Two different things fail, and they look nothing alike. A request error rejects the call before anything is queued, with a 4xx or 5xx right away. A task failure happens after a 201: the task runs and the work fails seconds or minutes later.

POST /image/generate
  │
  ├─ rejected   4xx / 5xx   { "code": "UNAUTHORIZED", "message": "..." }
  │                         nothing queued, held, or charged
  │
  └─ 201 { taskId } ──▶ task runs ──┬─▶ completed   items can still carry an error
                                     └─▶ failed      errors: [{ code, message? }]

Handle both.

Request errors#

The body is flat, with code at the top level in SCREAMING_SNAKE_CASE:

{ "code": "INSUFFICIENT_BALANCE", "message": "Insufficient balance." }

Branch on code, never on message. message is optional: TASK_NOT_FOUND, API_KEY_NOT_FOUND, INVALID_TOKEN, and TOKEN_EXPIRED have none. INSUFFICIENT_SCOPE and SCOPE_ESCALATION add a scopes object.

StatusCodeMeansDo
400VALIDATION_ERRORA check that runs after the schema passed. See belowFix what message names
400API_KEY_LIMIT_REACHEDThe account already has 100 live keysDelete a key first
400WEBHOOK_API_KEY_NOT_FOUNDapiKeyIds names a key you don't haveDrop the unknown id
400DESTINATIONS_INVALID_PROVIDERA destination update changed provider.idCreate a new destination
401UNAUTHORIZEDNo usable bearer header, or an unknown or deleted keyFix the credential
401INVALID_TOKENA pat_ that is malformed or not signed by MynthGet a new token from your server
401TOKEN_EXPIREDA pat_ past its hourPoll from the server, or use a webhook
403INSUFFICIENT_SCOPEValid key, missing scope. Adds scopes.required and scopes.currentUse a key with the scope, or add it
403SCOPE_ESCALATIONAn API key asked for manage or keys. Adds scopes.requested, scopes.allowedDo it in the dashboard
404TASK_NOT_FOUNDUnknown task, another account's task, or a pat_ for a different taskCheck the id and the credential
404API_KEY_NOT_FOUNDUnknown key idCheck the id
404DESTINATION_NOT_FOUNDUnknown destination idCheck the id
404WEBHOOK_NOT_FOUNDUnknown webhook idCheck the id
404NOT_FOUNDNo route matches the pathCheck the URL. There is no /v1 prefix
409DESTINATION_NAME_TAKENYou already have a destination with that namePick another name
413VALIDATION_ERRORPOST /image/upload body over 100 MBUpload fewer or smaller files
422INSUFFICIENT_BALANCEAvailable balance is below the task's estimateTop up. Nothing was queued
429SPENDING_LIMIT_EXCEEDEDThe key's cap for the current day, week, or month is used upWait for the period, or raise the cap
500INTERNAL_SERVER_ERROROur faultRetry, after checking GET /tasks
500UNKNOWN_ERRORStoring or reading a destination secret failedRetry
502DESTINATION_TEST_FAILEDYour storage rejected the test upload. message is the provider'sFix the credentials or path

VALIDATION_ERROR covers the checks that run after the schema passed:

  • metadata over 2048 bytes
  • a destination name you don't own
  • inputs or options the chosen model cannot take
  • an upload with no files, more than 10 files, a file that is not JPEG, PNG, or WEBP, or a file under 1 KB or over 10 MB

A 429 other than SPENDING_LIMIT_EXCEEDED means back off and retry. There is no Retry-After header. Rate limits has the rest.

Schema rejections use a different shape#

A body that fails the schema never reaches the handler, so it gets no code. The validator answers 400 with its own shape, which depends on the endpoint:

Endpoints400 body
/image/*, /video/*{ "data": <your body>, "error": [issues], "success": false }
/tasks, /api-key, /webhook, /destinations{ "success": false, "errors": [issues] }
Any body that is not valid JSONMalformed JSON in request body as plain text
{
  "data": { "model": "auto", "count": 1 },
  "error": [
    {
      "path": ["prompt"],
      "code": "required",
      "expected": "a string",
      "actual": "missing",
      "message": "prompt must be a string (was missing)"
    }
  ],
  "success": false
}

Read path and message on each issue. count outside 1 to 20, an unknown model id, and a missing prompt all fail here. data echoes the request body, so keep these responses out of logs you share.

Generation bodies drop fields they do not recognize instead of rejecting them. {"promt": "..."} fails on the missing prompt, but {"prompt": "...", "widht": 512} succeeds and ignores widht. Check the task's echoed request when a setting seems to have no effect.

Task failures#

An accepted task settles as completed or failed, and both can carry error codes. Where the code lands depends on the task type:

Task typeA failure appears in
image.generateresult.images[].error, task stays completed
video.generateresult.videos[].error, task stays completed
image.remove_background, image.rate, image.alt, image.reviewerrors[], task is failed

So a refused prompt on POST /image/generate gives you 201 at creation and a completed task whose only image failed. Checking status === "completed" is not enough. Walk the array.

{
  "status": "completed",
  "result": {
    "model": "black-forest-labs/flux.2-pro",
    "images": [
      {
        "status": "failed",
        "error": {
          "code": "RESTRICTED_CONTENT",
          "message": "The request was blocked by content moderation."
        }
      }
    ]
  }
}

An image.generate or video.generate task reaches failed only when the work around generation broke: resolving the model, the inputs, or the size (a _4k size on a model without 4k fails with CAPABILITY_NOT_SUPPORTED), queueing the task, or the daily sweep that expires a task stuck on pending.

Codes#

CodeMeansRetry helps
RESTRICTED_CONTENTThe provider or the model refused the content. Mynth did notMynth does not retry. You may resend
INVALID_PROMPTThe prompt was empty or too long for the modelNo
INVALID_INPUTAn input image could not be fetched or readNo. Check the URL is publicly reachable
CAPABILITY_NOT_SUPPORTEDThe model does not support an option you sentNo. Use another model or option
PROVIDERS_BUSYNo provider for that model has capacity right nowYes, later
RATE_LIMITEDProviders kept rate-limiting the requestYes, shortly
TIMEOUTThe provider did not answer in timeYes
PROVIDER_ERRORThe provider failed for another reasonYes
FETCH_FAILEDimage.rate, image.alt, or image.review could not fetch urlOnly if the URL was flaky
RATING_FAILEDimage.rate ran but produced nothing usableYes
ALT_GENERATION_FAILEDimage.alt ran but produced nothing usableYes
REVIEW_FAILEDimage.review ran but produced nothing usableYes
TASK_EXPIREDStill pending 24 hours after its last update, then sweptYes. Something broke on our side
ENQUEUE_FAILEDQueueing failed right after creation. The create answered 500Yes
UNKNOWN_ERRORNothing else matchedOnce

Before a retryable code reaches you, Mynth has already retried across providers: up to four attempts per image and three per video. See tasks. What RESTRICTED_CONTENT means is on prompts and images.

Webhooks carry the same values: .completed events carry result, and .failed events carry errors.

What is safe to retry#

  • 400, 401, 403, 404, 409, 413, and 422 fail the same way every time. Fix the request.
  • 429 SPENDING_LIMIT_EXCEEDED clears when the key's period rolls over.
  • 502 DESTINATION_TEST_FAILED is your storage rejecting the upload. Fix the destination.
  • 500 and connection failures are worth retrying, with one catch: there are no idempotency keys. Every create mints a new task and holds cost again, so resending a create that actually succeeded runs and charges it twice. If a create times out, check GET /tasks before sending it again.
  • A create that fails while queueing marks its task failed with ENQUEUE_FAILED, releases the hold, and answers 500. Retrying that one is safe.
  • A settled task never runs again. Retrying a task failure means a new request. Failed items are not charged.

In the SDK and CLI#

The SDK throws MynthAPIError for a request error, with status and code, and TaskAsync* errors while waiting on a task. SDK errors covers both, including why a schema rejection leaves code undefined.

The CLI prints the message to stderr and exits with a code scripts can branch on: 2 usage and VALIDATION_ERROR, 3 auth, 4 balance or spending limit, 5 RESTRICTED_CONTENT, 6 another 429, 1 anything else. CLI commands has the table.

Next steps#