Skip to main content
Every failure returns a JSON body with a detail field and an HTTP status that says what kind of failure it was.
detail is a string in almost every case. Request validation is the exception: FastAPI returns an array, one entry per field it rejected.
A client that renders errors should handle both. loc is the path to the offending field, joined with dots, and msg is the message worth showing.

Status codes

Reading a rejected submission

POST /api/jobs refuses before anything is scheduled, and the detail names the gate that refused it. These are the ones worth recognising:
The recipe.lock.json in your submission does not match the catalog on this server — the name, version, digest, or framework.runtime_id differs. The platform published a new recipe version and your experiment is still pinned to the old one.sf recipe status <exp> shows the difference; sf recipe upgrade <exp> applies it.
The requested GPU count exceeds what your account may hold at once. Waiting in the queue is not this error — a queued job was admitted. This is a refusal at the door, and only an administrator can change it.
--image points at a registry host the deployment does not permit. The allowlist is server-side on purpose: it is what stops a submission from running arbitrary code from anywhere.
spec.source.entrypoint was non-empty. The recipe owns the entrypoint; a job cannot replace it. Use a custom/custom recipe if you need to run your own command.
The packager found .env, *.pem, id_rsa*, or similar and refused to build the archive — this fails on your machine, before any request is sent. Secrets reach a job through the platform’s injection channel, never through the working directory.
Some error strings raised by older code paths are still in Chinese. They are being translated as the surrounding code is touched. If you hit one, the status code and the endpoint are the reliable parts to quote in a bug report.