The code string is part of the API contract: SDK consumers pattern-match on it, so the broker treats renames as breaking changes. The message is human-readable and is not stable. The details map carries structured context (e.g. the ID that wasn’t found); keys are documented per code when present.
Some responses are produced before a handler runs and therefore carry no ErrorResponse body and no code. Client code must not assume every 4xx is parseable:
401 from the authentication middleware — missing Authorization header, malformed PAK, or a PAK that matches no identity. Bare 401 Unauthorized.
403 from the authentication middleware — a read-only credential (the console token) attempting anything other than a read. Reads plus two allowlisted POST routes are permitted; every other method is a bare 403 Forbidden. See Read-only credentials.
400 from request parsing — a query parameter or request body that fails to deserialize before dispatch (for example a malformed UUID in ?pak_id=) is rejected by the HTTP framework with its own plain response.
The broker’s zero-config console token authenticates as a read-only admin. It passes the admin gate on GET/HEAD requests anywhere in the API, plus POST /auth/pak and POST /deployment-objects/{id}/diagnostics. Any other request is rejected by the authentication middleware with a bare 403 Forbidden — no ErrorResponse body and no code, so an SDK’s error-code accessor returns nothing. Distinguish this case by the absence of a body rather than by a code.
These come from the database classifier (ApiError::from_diesel) on create/update paths. For unique_violation and foreign_key_violation, the constraint name is included under details.constraint when available.
Code
Status
Meaning
unique_violation
409
Duplicate of an existing row (e.g. two generators with the same name).
foreign_key_violation
422
Referenced row does not exist (e.g. payload references a missing parent).
check_violation
422
A CHECK constraint failed.
not_null_violation
422
A required column was null.
not_found
404
Row not found at the database layer (safety net; most handlers return a resource-specific *_not_found code instead).
Caller is neither admin nor the generator referenced in the path.
not_authorized
403
Listing a generator’s registered agents (GET /generators/{id}/registered-agents) requires admin or that same generator’s PAK.
cannot_delete_system_generator
403
DELETE /generators/{id} targeted the built-in system generator, which cannot be deleted.
system_generator_not_a_tenant
403
A non-admin caller asked for a tenant-scoped view of the system generator (GET /agents under a system-generator PAK, or GET /generators/{id}/registered-agents with that id). Every agent is auto-registered with the system generator, so it is not a tenant and has no scoped view; admin callers are unaffected.
generator_required
403
GET /agents was called by a credential that is neither admin nor a generator — an agent PAK, for example. Distinct from system_generator_not_a_tenant, which means the caller is a generator but not a tenant one.
missing_agent_id
400
An admin caller invoked POST /generators/{id}/register without an agent_id in the body; only an agent self-registering may omit it.
already_registered
409
The agent is already registered with this generator. POST /generators/{id}/register is not idempotent; a repeat returns this. (The agent’s startup self-registration and brokkr register treat it as success.)
invalid_generator_id
400
A generator_ids entry supplied to POST /agents references a generator that does not exist.
forbidden
403
A generator PAK called a registration endpoint (POST/DELETE /generators/{id}/register); only an agent (self) or an admin may.
Caller is neither admin nor the template’s owning generator (modification paths).
template_not_accessible
403
Caller is not authorized to read this template (owned by another generator, or caller has no generator identity).
templates_not_accessible
403
Listing or creating templates requires admin or generator access.
invalid_template_syntax
400
Template body failed Tera syntax validation.
invalid_parameters_schema
400
The declared parameters schema is not valid JSON Schema.
invalid_parameters
400
Instantiation parameters failed schema validation. Validation messages are included under details.validation_errors.
template_render_failed
400
Template parsed but failed to render against the provided parameters.
template_stack_mismatch
422
The template’s labels/annotations do not all match the target stack. Missing keys are listed under details.missing_labels and details.missing_annotations.
invalid_annotation
400
Annotation key/value rejected by validation.
template_label_not_found
404
The named label does not exist on the template.
template_annotation_not_found
404
The named annotation does not exist on the template.
Webhook subscription payload rejected by validation.
unsupported_field
422
The request body carries a field the webhook API no longer accepts. details.field names the offending key and details.use_instead names its replacement.
invalid_request_body
422
A webhook create/update body parsed as JSON but did not match the expected shape.
url_required
400
Webhook URL is empty.
invalid_url_scheme
400
Webhook URL must start with http:// or https://.
webhook_test_failed
400
Test delivery failed: the endpoint returned a non-success status (status and truncated body under details.status_code / details.body) or the request itself failed.
delivery_not_found
404
No webhook delivery with the given ID.
delivery_not_acquired_by_agent
403
The reporting agent did not acquire this delivery.
Codes are short, lowercase, snake_case strings and are stable across releases; new broker error paths add new codes rather than repurposing existing ones. This catalog is maintained by hand alongside broker changes.