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
Every status the API answers with has a row here, and a status that two conditions share has a row each, because the extension members are what tell those apart.
| Status | Condition | How to recognize it |
400 | The body is malformed JSON | Title Bad Request, and no errors array |
401 | The request carried no API key, or one the platform rejects | type ends in #unauthorized |
402 | Your organization's credit balance cannot back the capacity a create, deploy, resume or scaling change would start | type ends in #insufficient-credit, and shortfall is present |
402 | Your organization's credit is suspended after a refund of credit already spent | type ends in #credit-suspended, and shortfall is present |
403 | Your organization has no Serverless access, or it was revoked | type ends in #forbidden. Distinct from an app that does not exist |
404 | The app does not declare that endpoint | endpointPath is present and names the rejected path |
404 | The app does not exist, or was deleted | No endpointPath member |
405 | The path exists but does not serve this method | type ends in #method-not-allowed, and an Allow header names the methods it serves |
413 | The body is bigger than the route accepts | type ends in #request-entity-too-large, and detail names the limit in bytes |
409 | The app is stopped, deleting or failed | Title Conflict, and no source-upload- type |
409 | A source upload cannot move the way you asked | type ends in one of the #source-upload- forms below |
409 | The request would take the environment past its worker or GPU cap | type ends in #environment-cap-reached, and detail names the cap, what the environment holds and what the request adds |
409 | The environment is paused and the request would add capacity | type ends in #environment-paused |
415 | The body's Content-Type is not one the operation declares, or the body has none | type ends in #unsupported-media-type |
422 | The body violates the endpoint's schema | errors array, one entry per field, each with a JSON Pointer such as /payload/image |
422 | A path or query parameter breaks its rule, for example limit=0 | errors entries name the parameter in detail and carry no pointer |
422 | The archive you uploaded breaks an archive rule | type ends in #code-zip-invalid or #container-zip-invalid |
429 | The organization's read allowance for logs, metrics and request errors is spent | Retry-After header, and type does not end in #environment-paused |
429 | The environment is paused and you invoked an app in it | type ends in #environment-paused, and there is no Retry-After |
503 | The platform has no GPUs to place your workers | type ends in #capacity-unavailable, with a Retry-After |
503 | Something the route depends on is unreachable, or the tenancy is still provisioning | Title Service Unavailable and no Retry-After |
500 | The platform could not complete the request and no narrower answer fits | type ends in #internal-server-error |
501 | An OIDC connector write on a platform that has no OIDC issuer | type ends in #not-implemented |
502 | A service this route depends on was unreachable or refused the request | type ends in #bad-gateway |
504 | The platform waited as long as it will wait and gave up | type ends in #gateway-timeout |
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".
A log tail whose query the platform will not run also answers 400, and that one is final: the query has to change before the call can work, so a client that retries it loops on the same refusal.
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.
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"
}Method not allowed
405 means the path exists but does not serve the method you sent, and the response carries an Allow header naming the methods it does serve. DELETE /v1/app-summary is answered this way, because that path serves GET.
A path that matches nothing at all is 404 instead. Both are decided before your credentials are read, so neither of them says anything about your key.
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.
An environment refuses capacity with this status too, either because the request crosses a cap or because the environment is paused. Invoking an app in a paused environment answers 429 instead, since nothing about the app's own state is wrong.
Environment cap reached
The request would take the environment past the worker or GPU cap in force on it, so it is refused before anything starts. The count is what the environment's other apps hold, each the larger of its live workers and its keep-warm floor, plus the workers this request starts and their GPUs. detail names all three, as in the environment's worker cap is 4; it holds 3 and this request adds 2.
Every request that adds capacity is checked: creating an app, deploying a version, resuming a stopped one, and a configuration change that adds workers or restarts a failed app. Lowering a cap below what is already running refuses the next deploy rather than stopping anything, so stop or scale down another app, or raise the cap, and retry.
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.
Unsupported media type
415 means the request body's Content-Type is not one the operation declares. The body is never decoded, so this is neither malformed JSON (400) nor a schema failure (422).
The detail separates the two cases. A body sent under another type reads Content-Type "text/plain" is not supported, and a body sent with no type at all reads Content-Type is required.
application/json with a charset parameter is still JSON, so a body of that type that cannot be parsed is a 400, and one that parses and breaks the schema is a 422.
Four answers compete for the same request and the order between them is fixed. A failed credential wins everything and returns 401, which tells you nothing about your body. A parameter that cannot be read comes next, so ?limit=nope sent as text/plain is a 400 rather than this. Then the content type. Then the body against the schema.
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.
Base image unreadable
The base image could not be pulled. It does not exist, the registry refused it, the reference does not parse, the registry host does not resolve, or the reference names the platform's own image registry.
detail reads base-image-unreadable: <host>: <reference> could not be pulled, with Docker Hub reported as docker.io because the pull talks to index.docker.io. Two endings carry the whole answer when they appear: Private base images are not supported in this version, which is a registry that answered 401 or 403, and Base images cannot come from the platform registry. A code source adds one entry pointing at /appSource/source/baseImage.
A registry that did not answer at all is a different failure, a retryable 503 rather than this type. A base image that reads fine but has no linux/amd64 build is a plain 422, with a detail that opens base-image-arch-unsupported:.
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.
Secret not found
An attachment named a secret the environment holds no active secret for, on POST /v1/apps or PATCH /v1/apps/{appId}. The whole request is refused, so the app is neither created nor changed.
There is one entry per offending attachment, in request order, each carrying no active secret with this name in this environment and a pointer at /secrets/<i>/secretName. Create the secret in that environment first, or copy one in.
The single-attach route answers differently. POST /v1/apps/{appId}/secrets returns a 404 for the same condition, because that handler reports a missing secret rather than a validation failure.
Secret not attachable
A registry secret cannot be attached to an app. A registry login is read when an image is pulled, which is before a worker exists, so it has no unseal path into a worker's environment. Nothing attaches it: it is stored for a build to read.
The entry points at /secretName on the single-attach route and at /secrets/<i>/secretName on create and update.
It outranks a missing secret. A request that names both a registry secret and one that does not exist is answered with this type alone.
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.
Reading an app, deploying and listing run outside that allowance. One other condition answers 429, and it is the one below.
Environment paused
The app's environment has spent its monthly budget and is set to pause there, so its apps are stopped and no task starts. The answer leaves out Retry-After, because the pause ends on a budget or on a month rather than on a timer: raise the budget past the spend, or wait for the UTC month to turn, then resume the app.
A request that would add capacity to a paused environment carries the same type with 409, the status for a state that refuses the change.
Internal server error
500 means the platform could not complete the request and no narrower answer fits.
One case 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.
Not implemented
501 answers a write to an OIDC connector where the platform has no OIDC issuer. Creating, updating and attaching one answer it before they read or write anything, so nothing partial happens.
Listing, reading, deleting and detaching keep working, which means a connector that is already there can always be inspected and removed.
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.
Invoking an app is the case to handle with care. A 502 from a task route leaves the request's fate unknown, because it may already have been accepted. Retry it with the same taskId: an id the platform has already claimed returns the task it names rather than running the work twice. Reading a task or listing tasks can simply be retried.
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.