API reference — the customer surface
This is the API surface a Hivemind install exposes. It is not the full
internal route map of hivemind-server — it is the set of endpoints a
customer-side daemon, automation tool, or integration would realistically
use. The authoritative source is the router assembly in
crates/hivemind-server/src/app.rs; this doc is kept in sync with it
by hand.
Hivemind's API is HTTP + JSON. Authenticated endpoints accept either:
- An
X-API-Keyheader — for daemons + service-to-service callers. The admin API key is written to./secrets/admin_api_keyon install and shown in the admin UI on first login. - A session cookie — for browsers, set by
POST /auth/login.
The public chatbot endpoint (POST /api/v1/public/agents/chat/{slug})
is intentionally unauthenticated and rate-limited per IP-hash for
unsigned users; this is the surface you'd embed in a marketing-site
chat widget.
Service-to-service access through a reverse proxy
If you've put Hivemind behind a reverse proxy with SSO forward-auth
(Authentik / Keycloak / etc., per sso-setup.md),
note that forward-auth intercepts every request before it reaches the
API. A valid X-API-Key is not enough on its own — forward-auth
runs first and bounces the call to the IdP login flow before the
server ever sees the header.
Two supported patterns for service-to-service callers:
- Call from inside the proxy network. Bind your integrator (the
other Seglamater service, your daemon) onto the same Docker network
as
hivemind-server(or onto the host with access to127.0.0.1:8585) and callhttp://hivemind-server:3000/api/v1/*directly. Forward-auth does not run on intra-network traffic. - Configure a forward-auth bypass for
X-API-Key. Add a Caddy / Traefik / nginx route rule that skips forward-auth when theX-API-Keyheader is present. A Caddy snippet:
@api_key header X-API-Key *
handle @api_key {
reverse_proxy hivemind-server:3000
}
handle {
forward_auth authentik:9000 {
# ... your existing forward_auth config ...
}
reverse_proxy hivemind-server:3000
}
The server still validates the key (so a bypass isn't a bypass of auth — just of the forward-auth indirection); without a key, the request goes through the SSO flow as before.
The unauthenticated public endpoints (/api/v1/health,
/api/v1/public/agents/chat/{slug}) are not gated by forward-auth in
the shipped Caddy config; they're routed at the proxy before the
forward-auth handler.
Base URL is whatever your reverse proxy forwards /api/v1/* to. In the
examples below, $BASE is e.g. https://hivemind.example.com. On the
host directly, it is http://localhost:8585.
Conventions
- Content-Type:
application/jsonon writes; reads return JSON. - Errors: JSON of shape
{"error": "<code>", "message": "<text>"}on 4xx / 5xx. The HTTP status carries the category; theerrorcode is stable across versions for programmatic handling. - Pagination (where present): query params
?limit=<n>&offset=<n>, default limit varies per endpoint, no link headers — the response body hastotal/limit/offsetkeys when paginated. - Timestamps: RFC-3339 UTC throughout (e.g.
2026-05-28T13:14:15Z). - IDs: UUIDs unless otherwise noted.
Health + version
GET /api/v1/health
Unauthenticated. Liveness probe. Returns ok if the server process is serving requests; does not check the database.
GET /api/v1/health/db
Unauthenticated. Database reachability + migration state.
curl -fsS $BASE/api/v1/health/db
# {"status":"ok","migrations_applied":34,"latest_migration":"20260528000001_chatalot_integration_verify_tls"}
The combined ok shape is what the managed-update healthcheck waits for
before committing an update; if either returns non-200 within the
grace window, the updater rolls back. See upgrade.md.
Auth
POST /auth/register
Local-account registration. Only allowed while the users table is
empty — the first POST creates the admin account, subsequent
attempts return 409. Closed entirely with SSO forward-auth.
curl -fsS -X POST -H 'Content-Type: application/json' \
-d '{"email":"you@example.com","password":"<strong password>"}' \
$BASE/auth/register
# {"id":"...","email":"you@example.com","role":"admin","created_at":"..."}
POST /auth/login
Local-account login. Returns a session cookie (SET-COOKIE: hm_sess=…)
to keep across subsequent browser calls. Daemon callers should use
X-API-Key instead — /auth/login is for the web UI.
curl -fsS -X POST -H 'Content-Type: application/json' \
-d '{"email":"you@example.com","password":"<password>"}' \
-c cookies.txt \
$BASE/auth/login
# {"ok":true,"user":{"id":"...","email":"you@example.com","role":"admin"}}
GET /api/v1/me
The currently authenticated user. Useful for daemons to confirm the API key resolves to the expected service account.
curl -fsS -H "X-API-Key: $ADMIN_API_KEY" $BASE/api/v1/me
# {"id":"...","email":"...","role":"admin","tier":"...","created_at":"..."}
Public chatbot
POST /api/v1/public/agents/chat/{slug}
Unauthenticated. The customer-facing public chat surface — embed this in a marketing site, a help widget, etc. Only profiles at tier 1 ("public") can be invoked through this endpoint. Hivemind applies a full security pipeline to every request: per-IP-hash rate limiting, input length cap, prompt-injection regex, PII scrubbing on input, cost-cap check, then the LLM call, then leak detection + PII scrub on the response.
curl -fsS -X POST -H 'Content-Type: application/json' \
-d '{"message":"What is Hivemind?","conversation_id":null}' \
$BASE/api/v1/public/agents/chat/support
# {
# "conversation_id": "0199ae…",
# "message": "Hivemind is a self-hosted multi-agent orchestration platform …",
# "tokens": {"input": 42, "output": 156, "cache_read": 0},
# "cost_cents": 0.42
# }
Embedders must thread conversation_id back on every turn to keep
conversational context — without it, every call lands as a fresh
conversation and the agent has no memory of prior turns (HIVE-49 was a
client-side bug in an embedder that did exactly this — the Hivemind
server hydrates up to the last 10 messages from the agent_messages
table when a valid conversation_id is supplied, but an embedder that
doesn't echo it back gets a stateless surface). The conversation has a
TTL (configurable per profile) and is garbage-collected after that.
Multi-turn example (the second turn must pass conversation_id from
the first turn's response):
# Turn 1: start fresh.
CONV=$(curl -fsS -X POST -H 'Content-Type: application/json' \
-d '{"message":"What is your support email?","conversation_id":null}' \
$BASE/api/v1/public/agents/chat/support | jq -r .conversation_id)
# Turn 2: thread the same conversation_id back.
curl -fsS -X POST -H 'Content-Type: application/json' \
-d "{\"message\":\"And what was that email again?\",\"conversation_id\":\"$CONV\"}" \
$BASE/api/v1/public/agents/chat/support
# {"conversation_id":"<same as turn 1>","message":"As I mentioned, our support email is …", …}
The server validates that the conversation_id belongs to the same
profile + visitor; cross-profile or cross-visitor IDs are silently
ignored (the server starts a fresh conversation), which prevents
history-injection attacks via a guessed UUID.
CORS
The public chat endpoint is the one route a browser on a different
origin would call directly (e.g. a marketing site at
https://example.com embedding a chat widget that hits
https://hivemind.example.com/api/v1/public/agents/chat/…). To enable
cross-origin browser calls, set the env var
HIVEMIND_CORS_ALLOWED_ORIGINS to a comma-separated list of exact
origins (no wildcards by design — explicit allowlist):
With the env set, the public chat endpoint responds with
Access-Control-Allow-Origin headers matching the request's Origin
when it's on the list, plus the preflight handling browsers need
(OPTIONS requests, Access-Control-Allow-Methods: POST, OPTIONS,
Access-Control-Allow-Headers: content-type). When the env var is
unset or empty, no CORS layer is installed and the behavior is the
pre-HIVE-35 default (same-origin only). This is opt-in by design —
the operator decides which origins they trust.
Scope: CORS is only attached to the public chat router. Admin and
integration routes are server-to-server and don't need (or get) the
CORS headers. The HIVE-35 implementation lives at
crates/hivemind-server/src/cors.rs.
Status codes:
- 200 — response generated.
- 400 — input failed validation (length, JSON shape).
- 429 — rate limited (per-IP-hash). The body's retry_after is in seconds.
- 503 — LLM provider unreachable or the configured agent's tier-cap is hit.
Private chat (authed)
POST /api/v1/agents/{slug}/chat
Authed. Same shape as the public chat endpoint, but invokable for any profile tier (not just tier 1). Used by service-to-service callers and the admin UI's internal chat console.
curl -fsS -X POST -H "X-API-Key: $ADMIN_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"message":"summarize today\\'s audit log","conversation_id":null}' \
$BASE/api/v1/agents/ops/chat
Agent profiles
Customer admins manage which AI personas (profiles) the install serves. Each profile pins: tier (public / semi-public / internal), default model, system prompt, cost cap, rate limit, and which security filters apply.
GET /api/v1/agent-chat/profiles
List all profiles.
curl -fsS -H "X-API-Key: $ADMIN_API_KEY" $BASE/api/v1/agent-chat/profiles
# {"profiles":[{"slug":"support","name":"Support","tier":1,"model":"claude-haiku-4-5-20251001","cost_cap_usd":5.0,"rate_per_min":10}, …]}
POST /api/v1/agent-chat/profiles
Create a profile. slug is unique + URL-safe.
curl -fsS -X POST -H "X-API-Key: $ADMIN_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"slug":"qa-helper",
"name":"QA Helper",
"tier":2,
"model":"claude-haiku-4-5-20251001",
"system_prompt":"You are a QA assistant…",
"cost_cap_usd":20.0,
"rate_per_min":30
}' \
$BASE/api/v1/agent-chat/profiles
PUT /api/v1/agent-chat/profiles/{slug}
Update an existing profile (partial — only the fields you send are changed).
DELETE /api/v1/agent-chat/profiles/{slug}
Delete a profile. Returns 409 if there are active conversations.
Cost + usage reporting
GET /api/v1/agent-chat/usage/summary
Today's spend across all profiles. Use this to wire a billing dashboard or a budget-warning alert.
curl -fsS -H "X-API-Key: $ADMIN_API_KEY" $BASE/api/v1/agent-chat/usage/summary
# {
# "date":"2026-05-28",
# "totals":{"requests":1240,"tokens_in":89421,"tokens_out":31204,"cost_usd":3.42},
# "by_profile":[
# {"slug":"support","requests":1100,"cost_usd":2.10,"cap_pct":42.0},
# {"slug":"marketing","requests":140,"cost_usd":1.32,"cap_pct":6.6}
# ]
# }
Per-request raw rows are available at /api/v1/agent-chat/costs (same
auth) for finer-grained billing — same shape but one row per call.
Audit log
GET /api/v1/agent-chat/audit?limit=100&since=2026-05-28T00:00:00Z
Security-relevant events: detected prompt injections, rate-limit hits, leak-detector triggers, cost-cap blocks. One row per event.
curl -fsS -H "X-API-Key: $ADMIN_API_KEY" \
"$BASE/api/v1/agent-chat/audit?limit=20&since=2026-05-28T00:00:00Z"
The audit log is append-only in v1 (no delete endpoint). Retention is governed by the install's database — back it up if you need it kept beyond your operational retention.
Conversations
GET /api/v1/agent-chat/conversations
List recent conversations across profiles. Useful for browsing chat history in admin UIs.
GET /api/v1/agent-chat/conversations/{id}/messages
Full message history for one conversation. Order is oldest-first.
Tasks
Tasks are the unit of work hivemind agents pick up. Service integrators typically create tasks via the API + watch their status transition.
Enums
| Enum | Wire values |
|---|---|
TaskStatus |
backlog, todo, in_progress, in_review, done, cancelled |
TaskPriority |
low, medium, high, critical |
There is no urgent priority — critical is the highest. Closed
tasks are done (not closed or complete). A guess at any other
value returns 400.
POST /api/v1/tasks
Authed. Create a task. created_by references the agent (not the
user) that's creating the task — pass an agent UUID, not a user UUID.
For service-to-service calls without an agent identity, leave
created_by null.
curl -fsS -X POST -H "X-API-Key: $ADMIN_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"project_id":"019e6632-…",
"title":"Investigate the chatalot connector regression",
"description":"AIWF reports… (full context)",
"priority":"high"
}' \
$BASE/api/v1/tasks
Fields:
- project_id (required, UUID): the project the task belongs to.
- title (required, string, 1-500 chars).
- description (optional, string).
- priority (required, TaskPriority enum).
- directive_id (optional, UUID): link to a parent directive.
- parent_task_id (optional, UUID): link to a parent task.
- branch_name (optional, string): the git branch the work happens on.
- created_by (optional, UUID): the agent that created this task.
- metadata (optional, JSON object): freeform.
On FK violation (e.g. created_by referencing a missing agent), the
server now returns 400 with a specific message naming the column +
referent — e.g. created_by must reference an existing agent (not a
user). Hard-to-debug opaque constraint-name errors are gone (HIVE-50).
GET /api/v1/tasks?project_id=<uuid>&status=todo
Authed. List tasks with optional filters.
PATCH /api/v1/tasks/{id}
Authed. Update a task. Status transitions to in_progress are blocked
if the task has incomplete dependencies — the response message names
the blockers.
DELETE /api/v1/tasks/{id}
Authed.
Schedules (HIVE-3)
Schedules pair a 5-field cron expression with a directive template. A
60-second tick loop in the server sweeps the table each minute; when a
schedule's next_run_at is in the past and enabled is true, the loop
materializes a fresh directive from the template, advances next_run_at
to the cron's next firing, and stamps last_run_at + last_directive_id
on the row.
Use this to run autonomous ops sweeps, nightly backup verifications, weekly bug triage, etc. without an external cron / scheduler. The coordinator pipeline runs as normal against the fired directive.
Cron format
5-field standard Unix cron (m h dom mon dow), UTC only in v1.
Examples:
- 0 2 * * * — 02:00 UTC daily.
- 30 6 * * 1 — 06:30 UTC every Monday.
- */15 * * * * — every 15 minutes.
- 0 0 1 * * — midnight UTC on the 1st of every month.
The server validates the expression at create / update time; an invalid
cron returns 400 with a message naming the field count or parse error.
Backfill posture
Missed windows are not backfilled. If the server is down for a day
and a daily schedule's next_run_at passes, the next sweep after
restart fires the schedule once, then sets next_run_at to the
next cron firing after NOW(). A weekly-down-then-restart does not
unleash seven scheduled directives.
Disable globally
Set HIVEMIND_SCHEDULES_ENABLED=false to disable the tick loop without
deleting any rows. The next start with the env unset / true resumes.
POST /api/v1/schedules
Authed. Create a schedule.
curl -fsS -X POST -H "X-API-Key: $ADMIN_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"name": "Morning ops sweep",
"cron_expression": "0 13 * * *",
"project_id": "019e6632-…",
"directive_template": {
"title": "Morning ops sweep",
"description": "Run infra health + Plane triage + flag CRITICAL findings",
"priority": "high",
"metadata": {"report_via": "telegram"}
},
"enabled": true
}' \
$BASE/api/v1/schedules
# { "id": "0199ae…", "next_run_at": "2026-05-29T13:00:00Z", "enabled": true, … }
Fields:
- name (required, 1-200 chars).
- cron_expression (required, 5-field Unix cron).
- directive_template.title (required), plus optional description,
priority (low|medium|high|critical), metadata (JSON object).
- project_id (optional, UUID): the project directives fire into.
- enabled (optional, default true).
The materialized directive's metadata is the template's metadata
merged with a scheduled_by: {schedule_id, schedule_name, cron_expression}
provenance block — so a directives-table consumer can tell at a glance
which entries were cron-fired and which were human-created.
GET /api/v1/schedules?project_id=…&enabled=true
Authed. List schedules, ordered by next_run_at ascending.
GET /api/v1/schedules/{id}
Authed. Get one schedule.
PATCH /api/v1/schedules/{id}
Authed. Partial update. Sending a new cron_expression triggers
re-validation + recomputation of next_run_at; sending only
enabled: false leaves next_run_at alone (so a temporarily-disabled
schedule resumes its original timing when re-enabled).
curl -fsS -X PATCH -H "X-API-Key: $ADMIN_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"enabled": false}' \
$BASE/api/v1/schedules/$SCHEDULE_ID
DELETE /api/v1/schedules/{id}
Authed. Returns 204 on success, 404 if not found.
Integrations: chatalot
Hivemind's chatalot integration is per-agent: each agent (a stored
identity in your agents table) gets its own chatalot bot token,
provisioned on first connect.
GET /api/v1/agents/{agent_id}/chatalot
Authed. Get the connection status of an agent's chatalot integration.
curl -fsS -H "X-API-Key: $ADMIN_API_KEY" \
$BASE/api/v1/agents/$AGENT_ID/chatalot
# {"connected":true,"instance_url":"https://chat.example.com","bot_user_id":"…","verify_tls":true,"last_connected_at":"…"}
POST /api/v1/agents/{agent_id}/chatalot/connect
Authed. Connect this agent to a chatalot instance. Provisions a bot
token + persists it encrypted at rest under the install's
integration_encryption_key.
curl -fsS -X POST -H "X-API-Key: $ADMIN_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"instance_url":"https://chat.example.com","admin_token":"<chatalot-admin-bot-token>","verify_tls":true}' \
$BASE/api/v1/agents/$AGENT_ID/chatalot/connect
verify_tls defaults to true (recommended). Set to false for
self-signed / private-CA instances if you cannot supply an additive
CA bundle via HIVEMIND_CHATALOT_CA_BUNDLE. See
integrations/chatalot.md for the
full pattern.
DELETE /api/v1/agents/{agent_id}/chatalot
Authed. Disconnect + revoke the agent's bot token.
Updates (admin)
GET /api/v1/admin/updates/check
Authed (admin). Force-poll the updates manifest for new releases on the install's channel. Returns the same info the admin UI shows.
curl -fsS -X POST -H "X-API-Key: $ADMIN_API_KEY" \
$BASE/api/v1/admin/updates/check
# {"current":"0.1.9","available":"0.1.10","channel":"stable","cosign_verified":true,"manifest_url":"…"}
POST /api/v1/admin/updates/apply
Authed (admin). Trigger an apply of the latest available release. This is the API equivalent of the Apply button in the admin UI.
curl -fsS -X POST -H "X-API-Key: $ADMIN_API_KEY" \
$BASE/api/v1/admin/updates/apply
# {"apply_id":"…","status":"pulling"}
Poll GET /api/v1/admin/updates/apply/{apply_id} for status.
GET /api/v1/admin/updates/applies
Authed (admin). Historical apply attempts (success + rollback).
WebSocket
GET /ws
Authed. The live event stream — agent lifecycle events, message
relay, status changes. Subscribe by sending a {"subscribe":["agents","messages"]}
payload after connecting. Heartbeat ping every 30 s; clients should
reply pong within 60 s or be disconnected.
GET /ws/live
Unauthenticated. Public-tier WebSocket for status surfaces only (no
message bodies, no user data). Used by status pages + the
public-facing health UI.
Stability + versioning
/api/v1/*is the customer-stable surface — semver-compatible across minor versions of Hivemind. Breaking changes get av2prefix; we do not silently change shapes.- The non-
v1routes (/api/agents,/api/tasks, etc.) are internal compatibility aliases for older daemons; they may be removed in a future major release. Use/api/v1/*in new code. /auth/*is stable too — registration + login URLs do not move across minor versions.
Bot knowledge
Per-bot retrieval-augmented content. The chat handler full-text
searches this corpus at message time and injects the top-N
relevance-ranked snippets into the system prompt. Authed via
X-API-Key (or session cookie).
Snippets are scoped to a single agent profile (the bot) — there is
no global / shared knowledge base. The bot profile's
knowledge_search_enabled flag flips to true automatically on the
first insert.
GET /api/v1/bots/{profile_id}/knowledge
List all knowledge snippets for a bot, newest first. profile_id is
the bot's UUID (visible on the bot detail page; not the slug).
Optional query param ?title_contains=<text> filters case-insensitively.
curl -fsS -H "X-API-Key: $HM_KEY" \
$BASE/api/v1/bots/d88aeede-c79a-4e42-a6f0-930dc701fc5f/knowledge | jq .
POST /api/v1/bots/{profile_id}/knowledge
Create a snippet. Body: {title, content, source?, metadata?}.
| Field | Type | Limit | Notes |
|---|---|---|---|
title |
string | 1–200 chars | Admin-side label |
content |
string | 1–4000 chars | Sent to LLM verbatim |
source |
string | optional | Free-text provenance hint |
metadata |
object | optional | Arbitrary JSON for client-side use |
Returns 201 + the created row including its id. Flips
knowledge_search_enabled = true on the parent bot profile if it was
false.
GET /api/v1/bots/{profile_id}/knowledge/{id}
Fetch a single snippet. Returns 404 if the snippet belongs to a different bot — never leaks cross-bot existence.
PATCH /api/v1/bots/{profile_id}/knowledge/{id}
Partial update. Body: any subset of {title, content, source, metadata}.
Omitted fields are unchanged.
DELETE /api/v1/bots/{profile_id}/knowledge/{id}
Remove a snippet. Returns 204. Same cross-bot 404 protection as GET.
GET /api/v1/bots/{profile_id}/knowledge/search?q=<text>&limit=<n>
Preview what the chat handler would retrieve for a given query. Returns
ranked hits ordered by relevance (ts_rank descending). Default
limit=5, clamped to [1, 50]. The chat handler itself uses this
same SQL but does not call this endpoint.
curl -fsS -H "X-API-Key: $HM_KEY" \
"$BASE/api/v1/bots/$BOT_ID/knowledge/search?q=support+hours&limit=3" | jq .
Bot templates
Read-mostly catalog of well-tuned agent profile starting points. The admin UI's + New bot modal consumes these; you can also instantiate directly via the API.
GET /api/v1/templates
List the bundled templates.
curl -fsS -H "X-API-Key: $HM_KEY" $BASE/api/v1/templates | jq '.[].slug'
# "customer-support"
# "sales-sdr"
# "internal-it-faq"
# "hr-faq"
# "product-docs"
GET /api/v1/templates/{slug}
Template detail including its starter knowledge memories. Used by the UI to preview before instantiating.
POST /api/v1/templates/{slug}/instantiate
Mint a new bot from a template. Body:
{
"slug": "acme-support",
"name": "Acme Support",
"description": "Optional one-line override",
"system_prompt_override": "Optional; falls back to the template default",
"seed_memories": true
}
The new bot's slug follows the same shape rules as direct profile
creation ([a-z0-9-]+, 1–60 chars, no leading/trailing hyphen).
Duplicate slugs return 409.
When seed_memories: true (the default), the template's starter
knowledge snippets are copied into the new bot's bot_knowledge
table and knowledge_search_enabled flips to true. The whole
operation is one transaction — a partial failure leaves no
half-built bot.
Returns 201 with {agent_profile_id, slug, seeded_memory_count}.
Vantage cockpit summary
A single read endpoint that returns a contract-shaped summary of the Hivemind instance for external dashboards (the Seglamater Vantage cockpit is the primary consumer, but the shape is usable by any operator dashboard).
GET /api/v1/cockpit/summary
Returns:
{
"version": "0.1.10",
"projects": [
{"name": "Acme Ops", "tasks": {"open": 3, "in_progress": 0, "done": 12}}
],
"agents": {"configured": 4, "active": 0, "max_concurrent": 0},
"activity": {
"last_task_created": "2026-05-28T22:42:05Z",
"last_task_completed": "2026-05-27T18:10:00Z"
},
"cost": {"window": "30d", "usd": 12.34}
}
Field semantics (consumers should rely on these — they are part of the public contract, locked alongside the endpoint):
version— the server's own version string. Matches/api/v1/health.projects[].tasks.open= task statusesbacklog + todo..in_progress=in_progress + in_review..done=done. Cancelled tasks are excluded — they're abandoned, not done.agents.configured= alive: not incompleted/failed/killed/stopped.agents.active=status = 'running'.agents.max_concurrent= sum of every project'smax_concurrent_agents(theoretical parallelism ceiling).activity.last_task_createdand.last_task_completedare ISO-8601 UTC strings ornullif the table is empty / no tasks have completed.costis always populated.cost.windowis the literal string"30d".cost.usdis the 30-day rolling sum ofagents.total_cost_usd.
The endpoint is cheap (8 small aggregation queries). Reasonable polling cadence is 30s – 5min depending on how live you need the dashboard.
See also
architecture.md— what each component is, where they sit on your host.self-hosting/install.md— the install path that brings up the API.integrations/chatalot.md— the chatalot integration walkthrough, deeper than the API ref above.