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:

RolePurpose
adminFull access — all operations, config changes, key/vault management
operatorAgent lifecycle, schedule/integration/catalog management, backups
developerInstall skills/competencies/packages, invoke agents, read status
viewerRead-only access to most resources
serviceInvoke agents, send messages, run workflows (for automated integrations)
auditorRead-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.

CategoryTypical HTTP StatusDescription
NotFound404Entity doesn't exist (agent, competency, skill, package, backup, model...)
ValidationError400Malformed input, bad UUID, missing required field
UnsupportedMediaType415Content-Type present and not application/json
InvalidState500*Lifecycle transition not valid for current state
MissingDependency500*Missing skill/competency dependency
PermissionDenied403Agent-level capability check failed (distinct from RBAC 403 forbidden)
TrustPolicyViolation403Agent attempted to exceed its PII/data-class boundary
Duplicate500*Resource already exists (e.g. duplicate competency install)
SizeLimitExceeded500*Message/manifest too large
Timeout500*Execution loop or workflow step timed out
MemoryViolation503Memory subsystem unavailable / cross-agent memory access denied
ProviderError502Upstream LLM provider failure
RateLimited429Per-agent invocation rate limit exceeded
DuplicateInvocation200Idempotency key already processed — cached result returned, not an error
AuthenticationError / AuthorizationError500*Reserved; not currently mapped by error_response()
InitError500Platform initialization failure
Internal500Unexpected 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 /metrics returns text/plain; version=0.0.4 (Prometheus exposition format).
  • Request bodies are validated for Content-Type: application/json before JSON parsing and before most RBAC checks fail with a body-shape error — this ordering exists so an unauthorized caller sees 403, not a 415/422 leaking schema details (see the parse_json_body/parse_body! doc comment in routes.rs, issue #222).

Endpoint Reference

SectionEndpointsReference
Health & Status/health, /status, /metrics, /providers, /models, /models/{key}, /gateway/pools, /gateway/traffichealth-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}/enableschedules.md, cron.md
Auth/auth/...auth.md
Security & Vault/security/..., /vault/..., /audit/appendsecurity.md
Integrations & Contracts/integrations/..., /contracts, /connectors/healthintegrations.md
Catalog/catalog/...catalog.md
Compliance/compliance/packs, /compliance/packs/{id}/report, /compliance/pii-coverage, /compliance/forced-local-routingcompliance.md
Usage/usage/..., /licence/statususage-budget.md
OpenAI Compatible/chat/completionsopenai-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/... aboveregistry.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:

MethodPathActionDescription
GET/configConfigReadReturn the raw config file contents ({"config": "<toml text>" | null})
POST/config/reloadConfigReloadHot-reload config.toml from disk
POST/shutdownDaemonShutdownSignal the daemon to shut down gracefully
GET/templatesTemplateReadList built-in agent/competency templates
GET/templates/{name}TemplateReadGet a single template by name
POST/backup/createBackupCreateCreate a backup archive (scope: "full" or "agents_only")
GET/backup/listBackupListList available backup archives
POST/backup/restoreBackupRestoreRestore 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

CodeMeaning
200Success
201Created (POST that creates a resource)
204No Content (successful DELETE, or a legacy-endpoint fallthrough)
400Bad Request (validation error)
403Forbidden (insufficient role, or an agent-level permission/trust-policy denial)
404Not Found
409Conflict (e.g. duplicate registry name)
410Gone (deprecated legacy endpoint — see the competency config endpoints in competencies.md)
415Unsupported Media Type (non-JSON Content-Type on a body request)
422Unprocessable Entity (malformed JSON body)
429Too Many Requests (rate limited)
502Bad Gateway (upstream provider or catalog/registry error)
503Service Unavailable (memory subsystem down, or auth not enabled for key-management calls)
500Internal Server Error