API Reference
The Hoziron HTTP API provides programmatic access to all platform capabilities. It is served by the api surface of the unified hoziron-server binary (see crates/surfaces/hoziron-api). Route registration is defined in crates/surfaces/hoziron-api/src/routes.rs — that file is the source of truth this reference is generated from.
Base URL
There is no fixed Hoziron API address — the base URL is wherever you configured the api surface to listen, via --api-listen or [surfaces.api].listen in config.toml (see TLS and networking). The examples on this page use http://localhost:4200/, the default bind address for a fresh install; substitute your own host, port, and scheme (http:// or https://, depending on whether you've enabled TLS or terminate it at a reverse proxy).
API Versioning
URLs are unversioned. The API version is selected per-request via the Api-Version header. When the header is absent, the daemon defaults to v1 (currently the only version). Send it explicitly so future version bumps don't silently change behavior:
curl -H "Api-Version: v1" \
-H "Authorization: Bearer hzn_sk_a1b2c3d4..." \
http://localhost:4200/agents
Authentication
Auth is configured platform-wide via [auth] mode in config.toml: "disabled", "local" (API keys), or "oidc" (bearer JWTs from an external IdP). See auth.md for the full model, including the boot-time posture check that refuses to start with auth disabled on a non-loopback bind.
When auth is enabled (local or oidc), include the bearer token on every request:
curl -H "Authorization: Bearer hzn_sk_a1b2c3d4..." http://localhost:4200/agents
GET /health is the only endpoint that bypasses auth entirely (public path, so orchestrator probes always work — /metrics deliberately does not, since it exposes provider health and spend/volume shape). When the key store or OIDC session is empty, /auth/keys supports a one-time bootstrap bypass to mint the first admin key.
Role-Based Access Control (RBAC)
Every non-public endpoint is gated by check_role()/require_role() against an Action (defined in hoziron-core-types::auth::permissions). There are six roles:
| Role | Purpose |
|---|---|
admin | Full access — all operations, config changes, key/vault management |
operator | Agent lifecycle, schedule/integration/catalog management, backups |
developer | Install skills/competencies/packages, invoke agents, read status |
viewer | Read-only access to most resources |
service | Invoke agents, send messages, run workflows (for automated integrations) |
auditor | Read-only access plus the audit trail and compliance/security reports |
Each endpoint's reference page states the exact Action it checks and which roles satisfy it. A caller lacking the required role gets 403 Forbidden:
{
"error": "forbidden",
"message": "Role 'viewer' cannot perform 'agent:invoke'"
}
Common Response Format
Success
Success bodies vary per endpoint (see each reference page), but list endpoints consistently wrap their collection in a named key, e.g.:
{
"agents": [...]
}
Error
All error responses — both from RBAC/body-parsing failures (415/422 before the handler even runs) and from handler-level failures — use the same envelope:
{
"error": {
"category": "ValidationError",
"message": "Invalid agent ID: 'not-a-uuid'",
"details": {}
}
}
403 Forbidden from RBAC checks is the one exception: it uses {"error": "forbidden", "message": "..."} (a flat string, not the category envelope), because it's produced by the shared hoziron-http middleware before a handler category is known.
Error Categories
category is the Debug rendering of hoziron_core::ErrorCategory (crates/platform/hoziron-core-types/src/error.rs) for handler-level errors, or a small set of route-local strings (ValidationError, UnsupportedMediaType, NotFound, CatalogError, ActivationError, Conflict, AuthError, ConfigError, InternalError) used directly by routes.rs for request-parsing and catalog/registry failures.
| Category | Typical HTTP Status | Description |
|---|---|---|
NotFound | 404 | Entity doesn't exist (agent, competency, skill, package, backup, model...) |
ValidationError | 400 | Malformed input, bad UUID, missing required field |
UnsupportedMediaType | 415 | Content-Type present and not application/json |
InvalidState | 500* | Lifecycle transition not valid for current state |
MissingDependency | 500* | Missing skill/competency dependency |
PermissionDenied | 403 | Agent-level capability check failed (distinct from RBAC 403 forbidden) |
TrustPolicyViolation | 403 | Agent attempted to exceed its PII/data-class boundary |
Duplicate | 500* | Resource already exists (e.g. duplicate competency install) |
SizeLimitExceeded | 500* | Message/manifest too large |
Timeout | 500* | Execution loop or workflow step timed out |
MemoryViolation | 503 | Memory subsystem unavailable / cross-agent memory access denied |
ProviderError | 502 | Upstream LLM provider failure |
RateLimited | 429 | Per-agent invocation rate limit exceeded |
DuplicateInvocation | 200 | Idempotency key already processed — cached result returned, not an error |
AuthenticationError / AuthorizationError | 500* | Reserved; not currently mapped by error_response() |
InitError | 500 | Platform initialization failure |
Internal | 500 | Unexpected internal error |
* Categories marked 500 fall through error_response()'s catch-all (crates/surfaces/hoziron-api/src/routes.rs, fn error_response) — only the categories explicitly matched (NotFound, ValidationError, PermissionDenied, TrustPolicyViolation, MemoryViolation, ProviderError, RateLimited, DuplicateInvocation) get a more specific status. This is current behavior, not necessarily intentional per-category design — don't assume InvalidState/Duplicate/Timeout will always mean "server error" in a future release.
There is no TriggerMismatch category — agents do not declare accepted invocation sources, so no such category exists in ErrorCategory or in error_response()'s match arms.
See errors.md for the full canonical error-handling conventions doc.
Content Type
All request and response bodies use application/json unless noted otherwise.
GET /metricsreturnstext/plain; version=0.0.4(Prometheus exposition format).- Request bodies are validated for
Content-Type: application/jsonbefore JSON parsing and before most RBAC checks fail with a body-shape error — this ordering exists so an unauthorized caller sees403, not a415/422leaking schema details (see theparse_json_body/parse_body!doc comment inroutes.rs, issue #222).
Endpoint Reference
| Section | Endpoints | Reference |
|---|---|---|
| Health & Status | /health, /status, /metrics, /providers, /models, /models/{key}, /gateway/pools, /gateway/traffic | health-status.md |
| Agents | /agents/... | agents.md |
| Workflows & Runs | /workflows/..., /runs/... | workflows.md |
| Competencies | /competencies/..., /agents/{id}/competencies/... | competencies.md |
| Skills | /skills/... | skills.md |
| Channels | /channels/... | channels.md |
| Memory | /agents/{id}/memory/... | memory.md |
| Schedules & Cron | /agents/{id}/schedules/..., /schedules/..., /cron/jobs/..., /conditions/jobs/{id}/enable | schedules.md, cron.md |
| Auth | /auth/... | auth.md |
| Security & Vault | /security/..., /vault/..., /audit/append | security.md |
| Integrations & Contracts | /integrations/..., /contracts, /connectors/health | integrations.md |
| Catalog | /catalog/... | catalog.md |
| Compliance | /compliance/packs, /compliance/packs/{id}/report, /compliance/pii-coverage, /compliance/forced-local-routing | compliance.md |
| Usage | /usage/..., /licence/status | usage-budget.md |
| OpenAI Compatible | /chat/completions | openai-compat.md |
| Config & Daemon Control | /config, /config/reload, /shutdown, /templates/..., /backup/... | see below |
MCP Server (Hoziron as a server, mcp surface) | /mcp (5 tools) | mcp-server.md |
Registry (direct HTTP API, registry surface) | search/publish/collections/admin — a different surface than /catalog/... above | registry.md |
There is no /approvals concept in the API — no human-in-the-loop approval queue exists anywhere in routes.rs. See approvals.md for a pointer to the closest real concepts.
There is no /devices concept in the API — no device-pairing route exists anywhere in routes.rs. See devices.md in this directory for a pointer to the closest real concepts (API keys and OIDC sessions — auth.md).
Config & Daemon Control
Small, ungrouped endpoints that don't have their own reference page yet:
| Method | Path | Action | Description |
|---|---|---|---|
| GET | /config | ConfigRead | Return the raw config file contents ({"config": "<toml text>" | null}) |
| POST | /config/reload | ConfigReload | Hot-reload config.toml from disk |
| POST | /shutdown | DaemonShutdown | Signal the daemon to shut down gracefully |
| GET | /templates | TemplateRead | List built-in agent/competency templates |
| GET | /templates/{name} | TemplateRead | Get a single template by name |
| POST | /backup/create | BackupCreate | Create a backup archive (scope: "full" or "agents_only") |
| GET | /backup/list | BackupList | List available backup archives |
| POST | /backup/restore | BackupRestore | Restore from a backup by id or file |
curl -X POST -H "Api-Version: v1" -H "Authorization: Bearer hzn_sk_admin..." \
http://localhost:4200/config/reload
# {"status": "reloaded"}
curl -X POST -H "Api-Version: v1" -H "Authorization: Bearer hzn_sk_admin..." \
-H "Content-Type: application/json" \
-d '{"scope": "full"}' \
http://localhost:4200/backup/create
# {"id": "...", "path": "...", "scope": "Full", "created_at": "...", "checksum": "...", "size_bytes": 12345}
Per ADR-053, /config (GET) surfaces the raw file — there is no structured provider/model config API; providers and models are boot-time config.toml only (see health-status.md for the read-only /providers, /models, /gateway/pools endpoints and why there is no corresponding write path).
ID Format
Agent, workflow, and run IDs are UUIDs (v4):
550e8400-e29b-41d4-a716-446655440000
Competency, skill, integration, trigger, schedule, and package IDs are opaque strings (not necessarily UUIDs) — treat them as returned by the API, don't assume UUID format.
Invalid agent/workflow/run IDs return:
{
"error": {
"category": "ValidationError",
"message": "Invalid agent ID: 'not-a-uuid'"
}
}
HTTP Status Codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created (POST that creates a resource) |
| 204 | No Content (successful DELETE, or a legacy-endpoint fallthrough) |
| 400 | Bad Request (validation error) |
| 403 | Forbidden (insufficient role, or an agent-level permission/trust-policy denial) |
| 404 | Not Found |
| 409 | Conflict (e.g. duplicate registry name) |
| 410 | Gone (deprecated legacy endpoint — see the competency config endpoints in competencies.md) |
| 415 | Unsupported Media Type (non-JSON Content-Type on a body request) |
| 422 | Unprocessable Entity (malformed JSON body) |
| 429 | Too Many Requests (rate limited) |
| 502 | Bad Gateway (upstream provider or catalog/registry error) |
| 503 | Service Unavailable (memory subsystem down, or auth not enabled for key-management calls) |
| 500 | Internal Server Error |