Skip to content

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-Key header — for daemons + service-to-service callers. The admin API key is written to ./secrets/admin_api_key on 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:

  1. 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 to 127.0.0.1:8585) and call http://hivemind-server:3000/api/v1/* directly. Forward-auth does not run on intra-network traffic.
  2. Configure a forward-auth bypass for X-API-Key. Add a Caddy / Traefik / nginx route rule that skips forward-auth when the X-API-Key header 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/json on writes; reads return JSON.
  • Errors: JSON of shape {"error": "<code>", "message": "<text>"} on 4xx / 5xx. The HTTP status carries the category; the error code 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 has total/limit/offset keys 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.

curl -fsS $BASE/api/v1/health
# {"status":"ok","version":"0.1.9"}

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):

HIVEMIND_CORS_ALLOWED_ORIGINS=https://seglamater.com,https://www.seglamater.com

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 prioritycritical 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 a v2 prefix; we do not silently change shapes.
  • The non-v1 routes (/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

curl -fsS -H "X-API-Key: $HM_KEY" $BASE/api/v1/cockpit/summary | jq .

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 statuses backlog + todo. .in_progress = in_progress + in_review. .done = done. Cancelled tasks are excluded — they're abandoned, not done.
  • agents.configured = alive: not in completed/failed/killed/stopped.
  • agents.active = status = 'running'.
  • agents.max_concurrent = sum of every project's max_concurrent_agents (theoretical parallelism ceiling).
  • activity.last_task_created and .last_task_completed are ISO-8601 UTC strings or null if the table is empty / no tasks have completed.
  • cost is always populated. cost.window is the literal string "30d". cost.usd is the 30-day rolling sum of agents.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