Skip to content

Errors ​

Every failure from the V1 API is an application/problem+json document (RFC 9457): every type the server can answer with, every code that can appear on a errors[] entry, what triggers it, and what a client should do about it.

crates/retrograd-server/tests/error_catalog.rs checks this file against the code, not the other way around: it fails if ProblemKind::ALL or ErrorCode::ALL (crates/retrograd-server/src/error.rs) gains a variant with no row here. A catalogue that can drift silently from what the server actually emits would be worse than no catalogue.

Document shape ​

jsonc
{
  "type": "https://retrograd.dev/problems/invalid-request",
  "title": "invalid request",
  "status": 422,
  "detail": "the dataset could not be read",
  "trace_id": "01JQ8...",                       // correlates with the server's logs
  "errors": [
    {"pointer": "/recipe/data/path",
     "code": "path_not_found",                  // closed vocabulary, see below
     "message": "no such file or directory",
     "hint": "upload it with POST /v1/datasets instead"}   // only when there is a short answer
  ],
  "meta": {}                                     // typed payload specific to `type`, see below
}

type and code are the stable, testable parts of the contract. title, detail, message and hint are prose and may be reworded without notice.

type (ProblemKind) ​

typeStatusWhenWhat to do
invalid-request422Malformed body, a recipe or configuration that fails validation, an unsupported data format, a bad TOML document.Fix the field(s) named in errors[].
server-declared422The client supplied a reward command, a judge endpoint/key, or an MCP server command directly instead of referencing a catalogue id. Executable commands and credentials are declared by the operator, never sent by a client.Declare the value server-side and send its id instead.
unknown-catalog-id422A reward, judge, MCP server, or fork_from.run's reward id is not declared by this server.GET /v1/rewards, /v1/judges, /v1/mcp-servers to see what is declared.
insufficient-memory422No arrangement of the memory levers makes the estimate (or, after calibration, the measurement) fit the effective budget.Read meta.dominant_posts for what to shrink, meta.unlocks for opt-ins that would help, or lower training.ctx/limits.
unauthorized401The Authorization header is missing or its bearer token is wrong. Answers with WWW-Authenticate: Bearer.Send a valid token.
forbidden-path403A client-supplied path resolves outside every root this server was configured with (path_roots).Upload the file instead of naming a local path, or ask the operator to widen path_roots.
not-found404Unknown run, route, checkpoint, or artifact.Check the id/URL; list the resource's collection endpoint.
conflict409A legal request against a resource whose current state forbids it: a control command on a run that cannot accept it, a fork that would change the training trajectory, a checkpoint request with nowhere to write one.Read detail; wait for a different state, or start a new run instead of forking.
payload-too-large413The request body is larger than this server accepts.Send a smaller body, or use POST /v1/datasets for large data instead of a bare params/config payload.
unsupported-media-type415The body did not arrive labelled as JSON. POST /v1/plan and POST /v1/runs also accept application/toml; POST /v1/datasets reads application/x-ndjson.Send content-type: application/json (or the content type that route documents).
method-not-allowed405The path exists but not for this HTTP method.Check the method against GET /v1/openapi.json.
device-busy503Every run slot is taken, or the probe/device queue is closed (server shutting down).Retry later, or increase max_concurrent_runs.
timeout504evaluate or generate did not get an answer from the run's loop before command_timeout_seconds.Retry; check the run is actually progressing.
not-implemented501A route that exists in the V1 contract but is not wired in this build.Not yet available; nothing a client can do.
internal500A failure that is this server's fault, not the caller's.Retry; if it persists, report trace_id and the server's logs to the operator.

code (ErrorCode, on errors[]) ​

codeMeaningTypical type
missing_fieldA required field was absent.invalid-request
unknown_fielddeny_unknown_fields rejected a field the schema does not have. hint names the closest known field, when one is within edit distance 2.invalid-request
invalid_valueThe field is present but its value does not fit: wrong shape, wrong enum member, or a combination with a sibling field that is not allowed.invalid-request
out_of_rangeA numeric field is outside the range that field accepts (zero where positive is required, a temperature outside [0, ∞), …).invalid-request
unsupported_formatA data or media-type field named something this server does not parse.invalid-request
path_not_foundA path field names something that does not exist on this server's filesystem.invalid-request (the status stays 422: the body is syntactically valid, only its content resolves nowhere)
forbidden_pathA path field names something outside every root this server serves.forbidden-path
unknown_catalog_idA catalogue reference (reward, judge, an MCP server, a forked run's reward) the operator has not declared.unknown-catalog-id
needs_opt_inThe request needs a degradation (Allow) and did not opt into it. detail says what the degradation would have done.invalid-request
override_conflictA client-supplied params value conflicts with the rest of the request. A client-supplied parameter is never silently dropped or moved.conflict
conflictA legal request against a resource whose current state forbids it.conflict
unauthorizedThe caller's credentials are absent or wrong.unauthorized
not_foundThe referenced resource does not exist.not-found
server_declaredThe field is reserved for the operator; a client may not set it.server-declared

Saturation and shutdown have no code: device-busy, timeout and internal are about the server, not about a field of the request, so they answer with a detail and an empty errors[]. tests/error_catalog.rs enforces the converse of the table - a code this server never emits does not get to sit here looking actionable.

meta ​

Typed, problem-specific payload. Only two shapes exist today:

jsonc
// insufficient-memory
"meta": {
  "overflow_bytes": 402653184,
  "vram_budget_bytes": 6442450944,
  "ram_budget_bytes": 68719476736,
  "dominant_posts": [{"post": "logits_bytes", "bytes": 1866989568}, "…"],
  "levers_applied": ["gradient_checkpointing"],
  "unlocks": ["truncate_context"]
}

levers_applied and unlocks are empty when the caller supplied a whole configuration (config/TOML forms) rather than a recipe: those forms skip phase 3, so there is nothing to report beyond the overflow itself.

A future dataset-invalid problem (per-line dataset validation) will carry meta.line_errors; it is not implemented yet.

trace_id ​

Every response - success or failure - is handled inside one request span tagged with a random trace_id. A problem document echoes it; every log line written while handling that request carries the same one (tracing::info_span!("request", trace_id = …)). When redact_error_paths is on, trace_id is the only way to get from a truncated <path>/model.gguf in a response back to the full path in the server's own logs - that is its only purpose.

Warnings ​

plan.warnings[] (on a successful /v1/plan or /v1/runs response) is a separate, smaller vocabulary - it never fails a request, it tells a client what the resolver decided on its behalf. Each entry is {code, field, message} with field optional (absent when the warning is about the request as a whole rather than one dotted path). It is not yet unified with the code vocabulary above; the missing piece is mainly an impact field on each warning, still deferred.

Retrograd documentation