Windpaint
Overview

Errors

The error envelope, every status and code the API returns, what causes the common ones, and which requests are safe to retry.

Errors use standard HTTP status codes and one JSON envelope:

{
  "error": {
    "code": "billing.insufficient_credits",
    "message": "Not enough credits: this needs 4.00 and 1.20 are available.",
    "details": { "required": "4.00", "available": "1.20" },
    "request_id": "59630881.7c2e4a"
  }
}
error.codestringrequired

Stable, machine-readable reason, named domain.reason. Branch on this.

error.messagestringrequired

Human-readable description, safe to show a user. The wording can change; don’t parse it.

error.detailsobject

Structured specifics, such as the field that failed or the amounts involved. Omitted when there’s nothing to add.

error.request_idstring

Identifies the request in Windpaint’s logs. Include it when you contact support. It’s only in the body; there’s no request id header.

Two responses don’t use this envelope:

  • Authentication failures return {"status": false, "error": "Unauthorized"} (401) or {"status": false, "error": "Forbidden"} (403). See Authentication.
  • Request timeouts return 408 with {"error": "Request timeout", "message": "Request took longer than <n> seconds to process"}, where <n> is the limit that applied: 120 for uploads, a few seconds for other routes.

Check whether error is an object before reading error.code.

Status codes

StatuscodeMeaning
400request.validation_failedThe body doesn’t match the endpoint’s schema: wrong type, missing required field, or unknown field.
400request.bad_requestThe request is malformed, for example invalid JSON or a bad query parameter.
401(auth body)Missing, invalid, revoked or expired API key.
402billing.insufficient_creditsYour available credits don’t cover the job or run.
403(auth body) or auth.forbiddenYour key’s role lacks the permission, or you tried to grant more access than you hold.
404request.not_found, api_key.not_found, webhook.not_found, role.not_foundThe resource doesn’t exist in your organization, or the route doesn’t exist.
405request.method_not_allowedThe route exists but not for this HTTP method.
408(timeout body)The request took longer than the route’s time limit. The message says how long that is.
409request.conflictThe request conflicts with current state: a taken slug, an archived project, the default project.
413request.bad_requestThe request body is too large. An upload body can be up to 51 MiB; a file over 50 MiB that fits returns 422 File exceeds 50 MiB. instead.
422request.validation_failedThe body is well-formed but breaks a rule: an unsupported resolution, a missing prompt, an input asset from another project.
422webhook.unknown_events, webhook.invalid_url, webhook.insecure_urlA webhook’s event names or URL were rejected. See Webhooks.
500request.internal_errorSomething failed on Windpaint’s side. The message is generic; quote request_id to support.
501request.not_implementedThe operation exists but isn’t built for this kind of resource yet.
503request.service_unavailable, billing.not_configuredA model, product or the payment provider isn’t available right now.

A resource in another organization returns 404, the same as one that doesn’t exist.

Common errors

400: schema validation

The body failed type checking. details.errors lists each problem with the field (key) and where it was (source):

{
  "error": {
    "code": "request.validation_failed",
    "message": "Validation failed for POST /v1/projects",
    "details": {
      "errors": [
        { "message": "Expected `str | null`, got `int`", "key": "slug", "source": "body" }
      ]
    },
    "request_id": "59630881.1f9a0b"
  }
}

A field the endpoint doesn’t accept is reported as Object contains unknown field `foo` . Fix the request; retrying won’t help.

402: insufficient credits

{
  "error": {
    "code": "billing.insufficient_credits",
    "message": "Not enough credits: this needs 4.00 and 1.20 are available.",
    "details": { "required": "4.00", "available": "1.20" },
    "request_id": "59630881.7c2e4a"
  }
}

required is the job’s price (or a product run’s estimate) and available is your spendable balance, both as decimal strings. Nothing was created or charged. Top up (see Billing) or wait for running jobs to release their holds, then submit again. Credits held by running jobs aren’t available, so a burst of submits can hit 402 before your balance looks empty.

409: conflict

MessageCauseWhat to do
A project with this slug already exists.Creating or renaming a project to a slug that’s taken. details.slug names it.Pick another slug. A slug derived from the name can collide too; pass slug explicitly.
The project is archived.Writing (submit, upload, run) into an archived project. details.project_id names it.Use another project. Archiving can’t be undone.
The default project cannot be archived.Archiving the project with is_default: true.Nothing; the default project stays.

422: invalid inputs

422 means the request was well-formed but Windpaint can’t do what it asks. The message says exactly what’s wrong. Common ones from generation:

MessageWhat to do
prompt is required.Send a non-empty prompt for capabilities that need one.
No model implements image.edit yet.The capability is defined but has no model. Check GET /v1/generation/capabilities for models: [].
Unknown model 'x'. Known: ...Use a model id from GET /v1/generation/models. The list in the message holds the same models.
File exceeds 50 MiB.Upload a smaller file.
z-image-turbo resolution must be one of 1k, 2k.Use a resolution the model lists.
aspect_ratio must be one of 1:1, 4:3, ...Use a listed aspect ratio.
z-image-turbo has no quality setting.Drop quality. No model takes it today.
wan-2.2-i2v duration must be one of 5 seconds.Use a listed duration, or omit it.
video.generate on wan-2.2-i2v: slot 'start_frame' takes 1 asset(s); got 0.Fill each input slot with the number of assets the model needs.
Inputs must be asset ids or URLs returned by /v1/generation.Upload external files first with POST /v1/generation/uploads and pass the asset id.
Input asset not found in this project.The asset is in another project, or doesn’t exist. details.asset_ids lists them.
Input slot 'start_frame' takes image assets.The asset is the wrong kind for that slot.
Unsupported content type 'image/gif'.Uploads accept PNG, JPEG, WebP, MP4, MP3 and WAV. details.allowed lists them.
File is not a readable image.The upload’s bytes don’t decode as the declared image type.

Outside generation: name is required. and the slug format message on projects, A top-up must be between 100 and 100000 credits. on top-ups, and low_balance_threshold cannot be negative. on billing settings.

503: unavailable

A model that can’t run right now:

{
  "error": {
    "code": "request.service_unavailable",
    "message": "wan-2.2-i2v is not available on this deployment.",
    "details": { "model": "wan-2.2-i2v" },
    "request_id": "59630881.d40c11"
  }
}

The same code with "... has no price for this tier and cannot be run." means the model has no price for the resolution or duration you asked for; details names the capability, model, resolution and duration. Try another tier, or check available and prices in GET /v1/generation/models.

A product that can’t run reports why in details.reasons:

{
  "error": {
    "code": "request.service_unavailable",
    "message": "photo-to-clip cannot run on this deployment yet.",
    "details": {
      "product": "photo-to-clip",
      "reasons": ["..."]
    },
    "request_id": "59630881.e2a7f4"
  }
}

Check available and unavailable_reasons on GET /v1/workflows/products before starting a run.

details only names the model. Pick another model from GET /v1/generation/models, or try again later.

On POST /v1/billing/topups, 503 means the payment provider couldn’t start checkout. Nothing was charged; try again shortly.

Failed jobs

A job that’s accepted and then fails doesn’t return an HTTP error; its status becomes failed and its error field holds one of a fixed set of messages, each ending Credits were released.:

  • Generation failed upstream (HTTP <status>). Credits were released.
  • Generation failed: the model backend is unavailable. Credits were released.
  • Generation failed: the model backend could not complete the request. Credits were released.
  • Generation failed: the model returned no output. Credits were released.
  • Generation failed due to an internal error. Credits were released.

A failed product run’s error is the failing step’s key followed by its reason, for example clip: Generation failed upstream (HTTP 500). Credits were released. See failure messages for what each one means.

Retrying

There are no idempotency keys, so retry decisions depend on the request:

RequestSafe to retry?Notes
Any GETYesBack off on 5xx and network errors.
Status pollsYesPoll at 1 to 15 second intervals.
POST .../cancelYesCanceling a finished job returns it unchanged.
PUT /v1/billing/settings, PATCH requestsYesThey set state; repeating gives the same result.
DELETE requestsYesDeleting again returns 204.
POST /v1/generation/estimate, product estimateYesRead-only.
POST /v1/generation/capabilities/{capability}, POST /v1/generation/models/{model}NoA retry creates and bills a second job.
POST /v1/workflows/products/{slug}/runsNoA retry starts a second run.
POST /v1/generation/uploadsMostlyUploads are free, but a retry creates a duplicate asset.
POST /v1/projects, POST /v1/api-keys, POST /v1/webhooksNoCreates a duplicate key or webhook; a duplicate project gets 409.
POST /v1/billing/topupsYesEach call opens a new checkout; only completed checkouts charge.

By status:

StatusRetry?
400, 401, 403, 404, 405, 409, 413, 422No. Fix the request.
402After topping up or once running jobs settle.
408, 500, network errorGETs, yes. For a submit or run, list recent jobs or runs first: the request may have gone through.
503Yes, with backoff, for payment errors. For an unavailable model or product, not until it’s available.

If a submit fails with a timeout or a dropped connection, check GET /v1/generation/requests?limit=10 (or GET /v1/workflows/runs?limit=10) for a job created in the last few seconds with your prompt before submitting again.