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.
| Status | Condition | How to recognize it |
400 | The body is malformed JSON | Title Bad Request, and no errors array |
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 | Title 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 |
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 |
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. No other route returns 429 |
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 |
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.
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.
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.