Every error returned by the API uses the RFC 7807 application/problem+json envelope. The same shape applies across pipelex domain errors, validation errors, auth failures, payload-size limits, and the catch-all 500.
A failure response looks like this:
{
"type": "https://docs.pipelex.com/latest/errors/validate-bundle-error/",
"title": "Validate bundle",
"status": 422,
"detail": "TOML syntax error at line 1, column 6: Expected '=' after a key in a key/value pair",
"instance": "/v1/validate",
"error_type": "ValidateBundleError",
"error_domain": "input",
"retryable": false,
"request_id": "9f2c1ab3-…"
}Content-Type is application/problem+json and X-Request-ID is always echoed in the response headers.
Standard RFC 7807 members — type, title, status, detail, instance.
Extension members — only error_type is always present; the others appear when the originating error populates them:
error_type— stable class name of the originating error (ValidateBundleError,EnvVarNotFoundError,WorkflowExecutionError, …). The same identifier thetypeURI is derived from.error_domain— one ofinput,config,runtime. See Status codes. Absent for domain-less pipelex errors (some pipelex tool errors, e.g.EnvVarNotFoundError, do not classify a domain — the HTTP status still defaults to 500).retryable— whether retrying the same request can plausibly succeed. Always emitted for API-authored 4xx/5xx (alwaysfalse). For pipelex-originated errors, present only when the source error populates it —trueonly when the originating error explicitly classifies itself as transient (e.g. inference provider rate limits); absent on pipelex errors that don't set it (e.g.EnvVarNotFoundError,PipelexConfigError).request_id— server-correlated identifier for the request. Echoed from inboundX-Request-IDif provided, otherwise generated. Use this when reporting issues.error_category— finer classification when the originating error provides one (currently used by inference errors — seeInferenceErrorCategoryupstream).user_action— structured suggestion of what the caller should do next, when the error can author one.model,provider,provider_metadata— populated when the failure originated in an upstream inference call. Stripped under STRICT disclosure (see Disclosure modes).validation_errors— structured per-error list carried by aValidateBundleError. On the build routes it rides this 422 problem document; on/validateit rides the 200InvalidReportbody instead (see Pipe Validate). See Structured validation errors.
When a bundle fails validation, the ValidateBundleError carries a validation_errors array — the per-error diagnostics an editor maps to per-line problems. Where it surfaces depends on the endpoint: on /validate it rides the 200 InvalidReport body (is_valid: false, the diagnostic-endpoint contract — see Pipe Validate); on the build routes (/build/{inputs,output,runner}) it rides the 422 problem document alongside the single human-readable detail. Built by pipelex's one shared builder, the items are identical wherever they appear (and to the agent CLI's). Each item is one categorized validation failure:
| Field | Meaning |
|---|---|
category |
The failure family — one of blueprint_validation, pipe_factory, pipe_validation, dry_run. |
message |
Human-readable description of this specific error. |
error_type |
Finer error subtype within the category, when the source error provides one. |
source |
The owning file of the error — present on pipe_validation and blueprint_validation items that the runtime could attribute to a file. On the in-memory submit path it is the matching mthds_sources[i] (see Sourcing submitted files); null when the caller sent no sources. Absent for pipe_factory and dry_run errors (the latter is graph-level), and absent on the parse-level blueprint_validation residual (a raw TOML-syntax error, an empty blueprint, or an elaborator failure), which carries the failure message but no file attribution — see the note below. |
pipe_code, concept_code, domain_code |
The pipe / concept / domain the error is about, when applicable. |
field_path, field_name |
The offending field within the bundle, when the error localizes to one. |
variable_names, missing_concept_code, declared_concepts |
Extra context for specific failure shapes (undefined variables, an unresolved concept reference, the set of concepts that were declared). |
Items carry only the fields that apply to their category — absent fields are omitted, not null. validation_errors is retained under STRICT disclosure (it describes the caller's own submitted bundle, not server internals). It is present only on ValidateBundleError; other error types omit it.
Every invalid verdict carries a non-empty validation_errors array — the structured-info invariant is total. A parse-level failure the runtime cannot attribute to a known pipe/concept/field — a raw TOML-syntax error, an empty blueprint, or an elaborator failure — still becomes one blueprint_validation residual item carrying the failure message (no source, no error_type at this layer), so the array is never empty on an invalid verdict. The richer, locator-bearing items appear only when the runtime could attribute the failure; the human-readable summary (the detail on a build-route 422, the message on a /validate 200 InvalidReport) stays available alongside, but a consumer can always read at least one structured item.
The HTTP status follows pipelex's error_domain_to_http_status:
error_domain = "input"→ 422 — the caller can fix it (bad.mthds, malformed arguments, validation failure).error_domain = "config"→ 500 — the deployment needs a fix (missing env var, bad TOML override).error_domain = "runtime"→ 500 — failure during execution (worker crash, upstream service failure).- Unclassified / unknown → 500.
A few specific statuses bypass the domain mapping:
- 401 — missing/invalid bearer token.
WWW-Authenticate: Beareris set. - 403 — authenticated but not authorized (storage-ownership mismatch).
- 409 —
error_type = "PipelineManagerAlreadyExistsError": the submittedpipeline_run_idis already registered for a run that is still in flight on this server. Completed and failed runs free their id, so this only fires for genuinely concurrent duplicates — resubmit after the in-flight run finishes, or pick a fresh id. - 413 — request body exceeds the configured size limit.
- 429 — set by
Retry-Afterwhen an upstream provider rate-limits and the originating error carriesprovider_metadata.retry_after_seconds. - 501 —
error_type = "AsyncExecutionNotEnabledError": this deployment does not provide async pipeline execution (/pipeline/start). Permanent under the current deployment — do not retry.
The HTTP status is the source of truth for success vs failure — there is no success: true/false field anywhere in the envelope.
The ERROR_DISCLOSURE env var controls how much of the originating error makes it onto the wire:
verbose(default) — renders the fullErrorReport. Use in dev, staging, and any deployment where the caller is trusted.strict— redactsdetailand provider fields for errors that do not author caller-facing messages. Specifically:detailis preserved only for error classes flagged as authoring caller-facing messages (today:PipelexInterpreterError,ValidateBundleError). Everything else hasdetailreplaced with a generic title-derived string.model,provider,provider_metadataare always stripped — they have no business on a caller-facing surface.- The redaction is keyed on the provenance of the message (
_authors_caller_facing_messageClassVar), not onerror_domain. ARuntimeErrorraisedfromanINPUT-domain cause does not leak the wrapper's internal message.
Server logs are always verbose regardless of ERROR_DISCLOSURE — the operator sees the full picture, the caller sees what the error chose to disclose.
type is a stable URI pointing at the per-class documentation page upstream:
https://docs.pipelex.com/latest/errors/<kebab-class-name>/
Every PipelexError subclass resolves to a live page (the trailing slash is canonical — pipelex emits it to match the MkDocs use_directory_urls: true form). The page describes the class, the typical cause, and how to recover.
API-authored errors (ValidationError, BadRequest, Unauthenticated, etc.) follow the same convention with their own slugs.
Every response carries X-Request-ID. The middleware respects an inbound X-Request-ID header if present, otherwise generates a UUID. The same id rides through onto JobMetadata.request_id, so it correlates the inbound HTTP call with the API-side log line — and, on a distributed-execution flavor, with every orchestrator worker-side log record produced during the run.
When opening an issue, include the request_id from the response (or response headers) and the timestamp.
POST /v1/validate
{
"mthds_contents": ["domain = \"broken\"\nmain_pipe = \"Not A Valid Pipe Code!\"\n"],
"mthds_sources": ["broken.mthds"]
}HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json
X-Request-ID: 9f2c1ab3-…
{
"type": "https://docs.pipelex.com/latest/errors/validate-bundle-error/",
"title": "Validate bundle",
"status": 422,
"detail": "Validation error(s):\n\nValue errors: 'main_pipe': Value error, Invalid main pipe syntax 'Not A Valid Pipe Code!'. Must be in snake_case.",
"instance": "/v1/validate",
"error_type": "ValidateBundleError",
"error_domain": "input",
"user_action": {
"kind": "change_input",
"detail": "Check the validation_errors array for specific issues"
},
"request_id": "9f2c1ab3-…",
"validation_errors": [
{
"category": "blueprint_validation",
"message": "Value error, Invalid main pipe syntax 'Not A Valid Pipe Code!'. Must be in snake_case.",
"error_type": "invalid_pipe_code_syntax",
"domain_code": "broken",
"source": "broken.mthds"
}
]
}The single detail is the human summary; validation_errors is the machine-readable list a client maps to per-line diagnostics. source echoes the mthds_sources entry the caller paired with that content — see Structured validation errors and Sourcing submitted files.
HTTP/1.1 500 Internal Server Error
Content-Type: application/problem+json
X-Request-ID: 9f2c1ab3-…
{
"type": "https://docs.pipelex.com/latest/errors/env-var-not-found-error/",
"title": "Environment variable not set",
"status": 500,
"detail": "Missing required environment variable: COMPLETION_CALLBACK_SECRET",
"instance": "/v1/start",
"error_type": "EnvVarNotFoundError",
"request_id": "9f2c1ab3-…"
}EnvVarNotFoundError is a domain-less pipelex tool error — neither error_domain nor retryable is populated on the report, so both extension members are absent on the wire. The HTTP status is still 500 (the deployment, not the caller, has to fix it), but the classification fields only ride along when the originating error sets them. See Fields.
Under ERROR_DISCLOSURE=strict the same failure has detail redacted to "Server configuration error" — the env var name lives in the server log only.
HTTP/1.1 401 Unauthorized
Content-Type: application/problem+json
WWW-Authenticate: Bearer
X-Request-ID: 9f2c1ab3-…
{
"type": "https://docs.pipelex.com/latest/errors/unauthenticated/",
"title": "Unauthenticated",
"status": 401,
"detail": "Missing bearer token.",
"instance": "/v1/execute",
"error_type": "Unauthenticated",
"error_domain": "input",
"retryable": false,
"request_id": "9f2c1ab3-…"
}For async pipeline runs registering a callback_url, the failure payload delivered to the caller's webhook does not use this envelope. The webhook body carries the raw ErrorReport dict under an error key, alongside pipeline_run_id (the protocol field) plus the runtime's legacy pipeline_run_id / status keys — a non-HTTP receiver (queue, log shipper) does not necessarily want an RFC 7807 wrapper.
The classification fields (error_type, error_domain, retryable, etc.) surface identically on both paths; only the envelope members (type, status, detail, instance, request_id) are sync-only. See Pipe Run → Async Completion Callbacks for the full webhook contract.