Errors

Every failure the Serverless API returns, how to recognize it, and what to do about it.

Introduction

Every error the Serverless API returns is an RFC 9457 problem document, served as application/problem+json.

{
  "type": "https://docs.runware.ai/serverless/errors#not-found",
  "title": "Not Found",
  "status": 404,
  "detail": "app does not declare endpoint \"does-not-exist\"",
  "endpointPath": "does-not-exist",
  "requestId": "..."
}

Switch on type and on the extension members, never on detail. The type URL is stable and points at the section of this page that explains it. The detail string is written for a human reading a log and can change.

Keep the requestId. It is what support needs to find your request.

Recognizing each failure

Two different conditions can share a status code, so the extension members are what tell them apart.

StatusConditionHow to recognize it
400The body is malformed JSONTitle Bad Request, and no errors array
402Your organization's credit balance cannot back the capacity a create, deploy, resume or scaling change would starttype ends in #insufficient-credit, and shortfall is present
402Your organization's credit is suspended after a refund of credit already spenttype ends in #credit-suspended, and shortfall is present
403Your organization has no Serverless access, or it was revokedTitle Forbidden. Distinct from an app that does not exist
404The app does not declare that endpointendpointPath is present and names the rejected path
404The app does not exist, or was deletedNo endpointPath member
409The app is stopped, deleting or failedTitle Conflict, and no source-upload- type
409A source upload cannot move the way you askedtype ends in one of the #source-upload- forms below
422The body violates the endpoint's schemaerrors array, one entry per field, each with a JSON Pointer such as /payload/image
422A path or query parameter breaks its rule, for example limit=0errors entries name the parameter in detail and carry no pointer
422The archive you uploaded breaks an archive ruletype ends in #code-zip-invalid or #container-zip-invalid
429The organization's read allowance for logs, metrics and request errors is spentRetry-After header. No other route returns 429
503The platform has no GPUs to place your workerstype ends in #capacity-unavailable, with a Retry-After
503Something the route depends on is unreachable, or the tenancy is still provisioningTitle Service Unavailable and no Retry-After

Bad request

400 means the request could not be parsed at all, most often a malformed JSON body. It carries no errors array, because nothing got far enough to be validated field by field.

A well-formed request that breaks a rule is a 422 instead. The split is worth keeping straight: 400 is "I could not read this", 422 is "I read it and it is wrong".

Container config yaml invalid

The container.yaml beside your Dockerfile is not valid YAML, so the platform never reached the document's own rules. detail names where the parser stopped, as in /endpoints/0: mapping keys must be strings.

It is the one container config failure that lands here rather than under 422, for the same reason any 400 does: nothing was parsed, so there is nothing to report field by field.

Unauthorized

401 means the request carried no API key, or one the platform rejects. The key identifies your organization, so the fix is the Authorization header.

A key that is valid but belongs to an organization without Serverless access is a 403, not a 401.

Payment required

402 always means money rather than capacity, and it always carries shortfall, the amount to add. Nothing is recorded when it is returned, so the same request succeeds as sent once the balance is there.

Two types share the status, and they differ in how you got here.

Insufficient credit

402 with the insufficient-credit type means your organization's credit balance cannot back the GPU capacity the request would start. It comes from creating an app, updating its source, deploying a version, resuming, or a configuration change that raises minWorkers or moves to another gpuType. Invocations never return it.

Read shortfall, not detail. It is the amount to add to your balance.

{
  "type": "https://docs.runware.ai/serverless/errors#insufficient-credit",
  "title": "Payment Required",
  "status": 402,
  "detail": "credit balance is short 12.5 credits: capacity needs 20.0 in cover, 0.0 is held and 7.5 is available",
  "shortfall": { "amount": "12.5", "currency": "USD" },
  "requestId": "..."
}

An app that scales to zero still needs credit for one worker, because that is the smallest ceiling it can be deployed with. A configuration change that lowers or keeps capacity is never answered with a 402, though the rollout it starts is checked again.

The balance is checked again when a rollout starts, so a build that finishes after the balance has fallen can still fail to roll out. The app's error event then names insufficient credit.

Credit suspended

402 with the credit-suspended type means a refund took back credit your organization had already spent, and the balance is below zero. Until a top-up brings it back to zero or above, every request that starts capacity is refused, including ones the balance covered before. shortfall is the top-up that lifts the suspension, and the request then succeeds as sent.

Forbidden

403 means the credentials are good and the block is on the organization: it has no Serverless access, or the access it had was revoked.

It does not clear on its own. That is the difference from a 503, which can look the same from the outside. A tenancy still being provisioned is retryable. One that was revoked or never existed is not.

Not found

An unknown endpoint and a missing app both return 404, and the endpointPath member is what separates them. When it is present, the app is fine and the path is wrong.

The path is checked against the active version's endpoint set before anything is queued, so an unknown endpoint never creates a task and never costs you anything.

{
  "type": "https://docs.runware.ai/serverless/errors#not-found",
  "title": "Not Found",
  "status": 404,
  "detail": "app does not declare endpoint \"does-not-exist\"",
  "endpointPath": "does-not-exist"
}

Conflict

409 means the resource exists but its current state cannot accept what you asked for. A stopped app has released its workers, and a deleting or failed one is not coming back on its own. Deploy a version over a failed app to bring it back, because a stop is not accepted from failed.

Resume a stopped app before invoking it. An app still initializing its first version also returns 409, and that one clears by itself once the build finishes.

The upload flow uses the same status for its own states, and each one has its own type. See Source uploads for the flow they belong to.

Source upload incomplete

The session is open but storage holds no object for it yet. Finish sending the archive to the transfer URL, then complete again.

Source upload object changed

The staged object was rewritten between being inspected and being read. Nothing is judged on bytes that may be two different objects, so the session stays open: complete again once your writes have settled.

Source upload rejected

Completion already refused this archive, and the reason it returns is the one it stored then. The object is not read a second time and its key is never reissued, so fix the archive and upload it under a new session.

Source upload expired

The upload window closed. This session can never become ready, so open a new one.

Source upload completed

The upload already published its source. Completing again returns that same source rather than creating a second one, and aborting is refused for the same reason: the source exists in its own right now.

Source upload deleted

The upload was aborted. The record survives as a tombstone so its staging key is never reissued, and the session itself is over. Start a new session.

Request entity too large

413 means the request body is bigger than the route accepts, and detail names the limit in bytes.

The limit is 10 MiB on invoke-sync and invoke-async, whose body carries your endpoint's payload, and 1 MiB everywhere else. An image or any other large input belongs behind a URL your handler fetches, not inside the payload.

The check runs before validation, so a body over the limit is refused without being parsed and you get this rather than a list of field violations.

Unprocessable entity

A body that breaks the endpoint's schema returns 422 with one entry per offending field, each carrying a JSON Pointer to it. Read errors rather than parsing detail.

The pointers are rooted at the whole body, not at your payload. Your fields live under payload in the envelope, so a problem with image reads /payload/image.

{
  "type": "https://docs.runware.ai/serverless/errors#unprocessable-entity",
  "title": "Unprocessable Entity",
  "status": 422,
  "detail": "/payload/image: Field required; /payload/prompt: Extra inputs are not permitted",
  "errors": [
    { "pointer": "/payload/image", "detail": "Field required" },
    { "pointer": "/payload/prompt", "detail": "Extra inputs are not permitted" }
  ]
}

Extra inputs are not permitted means the field belongs to a different endpoint or to nothing at all. A code app's endpoint schemas are closed, so an unrecognized field is an error rather than something quietly dropped.

A container app declares its own schemas, and an endpoint that declares no input is a passthrough: it takes whatever JSON you send and never returns this error. See Bringing a container.

Like a 404 on the path, this happens before a worker is involved.

A path or query parameter that breaks its rule, such as limit outside 1 to 100 on a listing, is a 422 too. Its entry names the parameter in detail and has no pointer, because a query string is not a JSON document.

Code zip invalid

The archive behind a code app broke an archive rule: it could not be read, it held an unsafe entry path or a link, it carried a duplicate name, it was oversized or a zip bomb, or its modelFile was missing or pointed outside the root.

Container zip invalid

The archive behind a container app broke an archive rule: a required file missing or misnamed, an unsafe entry path, a link, or an oversized or bomb archive.

It is a separate type from the code one on purpose, so a client that can submit either source type knows which archive it got wrong.

Container config unknown version

configVersion names a version of the document format the platform does not support. See Bringing a container.

Container config invalid

The document parsed and broke one of its own rules, most often a key that does not belong. /endpoints/0/hardware: unknown field is the common one, because hardware is set on the app rather than per endpoint.

Container config duplicate endpoint

Two entries in endpoints declare the same path. A path identifies an endpoint on its own, so two rows on one path would leave no answer to which a request meant.

Container config schema invalid

An endpoint's input or output is not a valid JSON Schema. You write these yourself on the container path, where a code app derives them from a handler signature instead.

Container config too large

The container.yaml exceeds what the platform accepts, either the document itself or the 200 properties counted across every input and output together.

Container config reserved port

port names one of the ports the platform's own containers hold. Pick anything outside that set, listed in Bringing a container.

Too many requests

429 answers the reads that draw on your organization's allowance: logs, metrics and an app's request errors. It carries a Retry-After that says how long to wait, and a client that ignores it keeps being refused.

No other route returns 429. Deploying, invoking and reading an app run outside this allowance.

Internal server error

500 means the platform could not complete the request and no narrower answer fits.

One case is worth knowing because it is deliberate: a usage summary refuses to report a total it cannot price in full. A worker whose recorded life cannot be priced fails the whole request, because a total that silently omits usage is a wrong number the caller cannot see is wrong. There are no partial totals.

Bad gateway

502 means a service this route depends on was unreachable or refused the request. It is retryable, and it says nothing about your app or your account.

Service unavailable

503 is the only error worth retrying blindly. It covers an active app the platform has observed to have no workload able to serve, and an organization whose Serverless tenancy is still being provisioned.

No task is created, so a retry is not a duplicate and cannot be charged twice.

It also answers a create, source update, deploy, resume or scaling change when your credit balance could not be read at that moment. That is not a refusal on credit, and the same request can be sent again.

Two types share the status. capacity-unavailable is the platform being short of GPUs, and it carries a Retry-After. Everything else keeps service-unavailable and sends none, because a workload that was removed does not come back by waiting.

503 and 403 both mean "not available to you right now", but only one of them clears on its own. A tenancy still provisioning is a 503 and is retryable. A tenancy that was revoked or never existed is a 403 and retrying will not help.

Capacity unavailable

The platform has no GPUs to place your app's workers. The request is refused before a task is minted, so retrying it is safe and it succeeds as sent once a worker can be placed.

{
  "type": "https://docs.runware.ai/serverless/errors#capacity-unavailable",
  "title": "Service Unavailable",
  "status": 503,
  "detail": "app has no workload able to serve this invocation",
  "requestId": "6f1c..."
}

Wait the Retry-After, which is 30 seconds. Retrying sooner returns the same verdict, which has yet to be recalculated.

Only an active app is refused on this. An app still initializing that already has a version to route to accepts invocations while it reports itself unavailable, which is the ordinary case during a first deploy, and a draining app skips the gate entirely. See Monitoring for the health verdict this reads.

A worker the platform never placed is not charged for, because a pod with no node holds no GPU. That is not the same as saying an unavailable app is free: a worker that was placed and cannot serve still holds its GPU, and a container that crash-loops or is still loading is charged for, while one still pulling its image is not. An app reporting itself unavailable may still be accruing.

Gateway timeout

504 means the platform waited as long as it will wait and gave up.

The one you are most likely to meet is invoke-sync reaching the platform deadline. The task keeps running, and you go on reading it by its taskId. Raising an app's requestTimeoutSecs bounds the worker, not this deadline, so invoke a long job asynchronously. See Invoking an app.

Failures inside your own code

The errors above are the platform refusing a request. When your handler itself raises, the request was accepted and the failure is on the task instead.

The task reaches failed and carries the reason in error. That string is not part of the contract: it carries no stable code and no retry signal, so read it, log it, show it to a human, but do not parse it to classify a failure.

{
  "id": "6b0c...",
  "appId": "image-tools",
  "status": "failed",
  "endpointPath": "generate",
  "createdAt": "...",
  "output": null,
  "error": "..."
}

error is a plain string with no code and no structured cause. Read the status to detect the failure and surface the string to whoever has to debug it.