API Reference

Every endpoint, every field, every language  //  base: https://api.hivemind.militant.ai/hivemind/v1

This is the complete wire-level reference. Authentication is a bearer key on every request: Authorization: Bearer <key>, created in the dashboard. Every response arrives in one envelope: {"ok": true, "data": …, "request_id": "…"} on success, and on failure {"ok": false, "error": {"code", "message", "details"}, "request_id"} with a matching HTTP status — quote the request_id when reporting a problem. Requests select a project with the optional X-Hivemind-Project header. The conceptual documentation — the context window model, recall and ranking, budget mechanics — lives in the main documentation. GET /health on the host root answers {"status": "ok"} without authentication — use it for liveness checks. Routes under /operator/, /metrics, and /diagnostic/ are service-internal and refuse customer keys.

Client setup

Every example on this page uses two helpers — one POST, one GET — defined once per language. They carry the base URL, the bearer key, and JSON encoding; every endpoint call is then a single short expression. The base URL is the host root: clients append /hivemind/v1 exactly once. To target a specific project, add the X-Hivemind-Project header to both helpers; without it your default project serves.

BASE="https://api.hivemind.militant.ai/hivemind/v1"
export HIVEMIND_API_KEY="sk_your_key"
# every call: curl with the Authorization header, JSON in, JSON out

Client implementation guide

A correct client holds three pieces of state and follows six rules. Everything else on this page is per-endpoint detail.

The state

A turn counter, incremented once per exchange within a session. The open exchange: after a /turn succeeds, remember its exchange_pair_id and turn number until the reply is stored or the generation is abandoned. A rolling history buffer: the last few dozen messages of the session (forty is a sensible default), each as {role, content, timestamp} with the real time it was said. The buffer is sent as messages on every /turn; it is the “recent conversation” the compiler combines with recalled memory, and the compiler orders it by timestamp, so fabricated or missing times scramble reading order.

The lifecycle of one exchange

Increment the turn counter. Generate a fresh exchange_pair_id — a time-ordered UUIDv7 by convention. Append the user message to the history buffer with its timestamp. Call /turn with the buffer. Run your model on data.bundle.messages. Stream or return the reply to your user. Then call /conversation/store with the reply, the same exchange pair id and turn number, role "assistant", and the two metadata conventions: semantic_content set to the initiating message text, and author if you attribute agents. Append the reply to the history buffer. The exchange is complete.

Streaming hosts

/turn performs a store, two semantic recalls, and a full compile — at large budgets that is a multi-second operation. Establish your output stream first (send headers or a keep-alive frame), then call /turn, then stream the model. Otherwise streaming clients treat the absence of bytes before the compile finishes as a failed connection.

Restarts

The history buffer is held in your process and is lost when that process stops. Rebuild it after a restart from your own message store — nothing is re-sent to the service; the buffer is only ever what you pass as messages. Keep your conversation id mapped to session_id so the restarted host resumes the same session, and when a user clears a thread, start a new session id rather than rebuilding the old one.

Aborted generation

If the user cancels mid-stream or sends a new message before the reply finished, make no Hivemind call. Discard the open exchange. The user message was already stored by /turn and stands as an unanswered call; the next message is a new turn. Never store empty content, and never store a partial reply as cleanup — the only legitimate partial is a host that deliberately keeps what was shown on screen as what was said, decided after the stream has ended.

Retries and backoff

Never re-send a /turn or /conversation/store that timed out: the request may have executed, and replaying it stores the message twice. Retry only requests that failed before any response began (connection refused, DNS failure). On a 429, sleep exactly the number of seconds in the Retry-After header and retry — that is the service asking your client to slow down. On a 402 storage cap, writes are blocked but recall and compile keep working, so degrade to read-only memory instead of failing the chat. If /conversation/store fails after a successful turn, the chat already succeeded — queue the store and retry it out of band. On a 5xx or a timeout, decide once whether your host fails open (proceed with local context only) or fails closed, and document the choice.

Content rules

Text is stored and returned verbatim. Recalled text re-enters a model’s context on later turns, so strip or alter model control tokens (markers of the <|…|> kind) before storing. Size budget_total as your model window minus whatever you wrap around the packet, and read your context meter from the response’s token_count against budget_total — local history length undercounts, and your model provider counts a different tokenizer after your additions.

POST /turn

The compile side of one exchange, in a single request. The service performs three steps in order: it stores user_input as the call half of a new exchange (receipted), it runs semantic recall over deliberate memories and past conversation using the input as the query, and it compiles your local history, the recalled records, active holds, and the operator briefing into one token-budgeted prompt packet. Your model runs on the packet; POST /conversation/store then records the reply, completing the exchange. Degraded recall or a failed store surface in warnings — the turn still succeeds.

Request fields

FieldTypeRequiredMeaning
user_inputstringyesThe initiating message of this exchange. Stored verbatim and used as the recall query.
session_idstringyesYour conversation identifier. Map your own thread id to it so a restarted host resumes the same session; start a new id when a user clears a thread.
turn_numberintegeryesMonotonic per session; your client increments it each turn.
budget_totalintegeryesToken budget for the compiled packet. Maximum 128,000; larger values are rejected with a 400 naming the limit. The budget bounds what is surfaced per turn, not what is remembered.
exchange_pair_idstringyesA fresh collision-resistant id per exchange; the store call must repeat it exactly. Convention: time-ordered UUIDv7.
messagesarray of objectsrecommendedYour rolling local history plus the current message, oldest first. Each entry is {role, content, timestamp} with a real unix timestamp — the compiler orders conversation by timestamp. Around forty messages is a sensible buffer.
operator_briefingstringnoStanding instructions compiled into the packet. Never stored; re-send it every turn.
holdsarray of objectsnoPinned operational state, each {id, title, content}. Compiled into every packet you send them with; never stored, never recalled, never receipted. Your client keeps the list and drops entries when the condition clears.
artifacts_contextstringnoOne-shot context (a document excerpt, a tool result) compiled into this packet only.
recall_top_kintegernoForces a fixed recall pool. Omit it: the service sizes the pool from the budget, and your subscription caps the maximum.
authorstringnoWho produced this call — identity metadata, orthogonal to role. Surfaces as the chat message name in compiles and recalls, and filters in metadata recall.
policyobjectnoCompiler policy overrides. Omit unless you know you need it.
configobjectnoAdvanced compiler configuration. Omit unless you know you need it.

Example

curl -s -X POST "$BASE/turn" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "user_input": "Can we ship the migration tonight?",
  "session_id": "session-1",
  "turn_number": 7,
  "budget_total": 8192,
  "exchange_pair_id": "xp-01a03987ae5a7c4392f1b06d2e9d51aa",
  "messages": [
    {
      "role": "user",
      "content": "What did we decide about the staging window?",
      "timestamp": 1787600120.0
    },
    {
      "role": "assistant",
      "content": "The staging database is rebuilt Mondays 03:00 UTC; migrations stay clear of it.",
      "timestamp": 1787600131.5
    },
    {
      "role": "user",
      "content": "Can we ship the migration tonight?",
      "timestamp": 1787600180.2
    }
  ],
  "operator_briefing": "You are the deployments assistant. Be direct.",
  "holds": [
    {
      "id": "stop-order",
      "title": "stop-order",
      "content": "No production deploys until the incident review closes."
    }
  ],
  "author": "operator-7"
}'

Response

bundle.messages is a contiguous, internally ordered block — use it as the whole prompt or wrap it above and below, but never insert into or reorder inside it. token_count against budget_total is the authoritative context meter. stored confirms the call half was written; timings is the server-side phase breakdown in seconds.

{
  "ok": true,
  "data": {
    "bundle": {
      "messages": [
        {
          "role": "system",
          "content": "…compiled packet: briefing, holds, recalled memory, conversation…"
        },
        {
          "role": "user",
          "content": "Can we ship the migration tonight?",
          "name": "operator-7"
        }
      ],
      "policy": null,
      "token_count": 8192,
      "source_counts": {
        "conversation": 6,
        "semantic": 4,
        "holds": 1,
        "briefing": 1
      },
      "budget_total": 8192,
      "budget_available": 8192,
      "conversation_tokens": 2413,
      "hold_tokens": 118,
      "trace": {
        "trace_id": "…",
        "event_id": "…",
        "receipt_id": "…",
        "parent_receipt_id": null
      }
    },
    "receipt": {
      "receipt_id": "01a03987-b1c2-7e4d-9f10-2a6b8c4d5e6f",
      "operation": "context.compile",
      "status": "success",
      "created_at": "2026-08-26T01:20:11.412331+00:00",
      "tenant_id": "hm_tenant_9f2c1a7b3d4e5f60",
      "inputs_ref": {
        "session_id": "session-1",
        "turn_number": 7,
        "budget_total": 8192,
        "recalled_conversation_count": 6,
        "recalled_memory_count": 4
      },
      "outputs_ref": {
        "token_count": 8192,
        "item_ids": [
          "…"
        ],
        "source_counts": {
          "conversation": 6,
          "semantic": 4
        }
      }
    },
    "stored": {
      "id": "01a03987-ae5a-7c43-92f1-b06d2e9d51aa",
      "receipt_id": "01a03987-aefe-7a11-8c2d-3e4f5a6b7c8d"
    },
    "recalled_memories": 4,
    "recalled_conversation": 6,
    "warnings": [],
    "timings": {
      "embed_s": 0.025,
      "store_and_recall_s": 1.703,
      "completion_s": 0.317,
      "compile_s": 0.005,
      "receipts_s": 0.162,
      "total_s": 2.212
    }
  },
  "request_id": "01a03987-ad00-7000-8000-000000000000"
}

Errors

400 VALIDATION_ERROR for an empty field or a budget above 128,000 (details.field names it); 400 "No storage connected" when no cluster is bound; 429 RATE_LIMITED with Retry-After; 401 for a bad key. A degraded sub-operation (one recall search failing) is a warnings entry, not an error.

POST /conversation/store

Stores one half of an exchange. In the standard loop it records the assistant reply, completing the exchange the last /turn opened; it is also how a custom loop stores either half itself. Recall returns whole exchanges, so completing pairs is what keeps answers attached to the questions that produced them.

Request fields

FieldTypeRequiredMeaning
contentstringyesThe message text. Empty content is rejected.
session_idstringyesSame session as the turn.
turn_numberintegeryesSame turn number as the turn.
rolestringyes"assistant" for the reply half, "user" for a call half. Role is positional — which side of the exchange — not human versus machine.
exchange_pair_idstringyesExactly the id the turn used. This is the pairing.
additional_metadataobjectnoTwo conventions matter: semantic_content on the reply half carries the initiating message text, so a reply recalled alone renders with its question; author attributes the half to a named agent or person.

Example

curl -s -X POST "$BASE/conversation/store" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "content": "Not tonight — the stop-order is active until the incident review closes.",
  "session_id": "session-1",
  "turn_number": 7,
  "role": "assistant",
  "exchange_pair_id": "xp-01a03987ae5a7c4392f1b06d2e9d51aa",
  "additional_metadata": {
    "semantic_content": "Can we ship the migration tonight?",
    "author": "deploy-assistant"
  }
}'

Response

{
  "ok": true,
  "data": {
    "message": "Conversation exchange stored successfully",
    "id": "01a03987-c777-7d21-8e9f-0a1b2c3d4e5f",
    "receipt_id": "01a03987-c790-7aa1-9b2c-3d4e5f6a7b8c"
  },
  "request_id": "…"
}

Errors

400 VALIDATION_ERROR for empty content, session id, or exchange pair id; 402 STORAGE_LIMIT_EXCEEDED where a storage allowance applies (writes only — recall and compile keep working); 429; 401. If this call fails after a successful turn, the chat already succeeded — retry it out of band rather than surfacing an error to the user.

POST /conversation/recall

Explicit episode recall over conversation history. Two modes: with session_id omitted or null it searches semantically across all sessions and returns one result per exchange; with a session_id it returns that session’s exchanges in full, both halves, newest first — the replay path. The loop already does cross-session conversation recall internally; this endpoint is for doing it deliberately.

Request fields

FieldTypeRequiredMeaning
querystringyesThe search text. Matched against both sides of every exchange.
session_idstring or nullnoNull searches across sessions; a value replays that session.
top_kintegernoResult count, default 10, capped by your subscription.

Example

curl -s -X POST "$BASE/conversation/recall" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "query": "what was decided about the staging window",
  "session_id": null,
  "top_k": 10
}'

Response

{
  "ok": true,
  "data": {
    "memories": [
      {
        "id": "…",
        "content": "The staging database is rebuilt Mondays 03:00 UTC…",
        "metadata": {
          "session_id": "session-1",
          "turn_number": 4,
          "role": "assistant",
          "exchange_pair_id": "xp-…",
          "semantic_content": "When is staging rebuilt?",
          "author": "deploy-assistant",
          "timestamp": 1787344210.7
        }
      }
    ]
  },
  "request_id": "…"
}

Errors

404 MEMORY_NOT_FOUND when nothing matches — normal on a new workspace, proceed; 400 for an empty query; 429; 401.

POST /create_memory

Deliberate durable memory: decisions and their rationale, lessons learned, durable facts, outcomes. Stored forever, recallable from any session, deletable by its metadata. Attach consistent metadata keys — they are what make filtered recall and surgical deletion possible later.

Request fields

FieldTypeRequiredMeaning
contentstringyesThe fact, stated so it stands alone. Empty content is rejected.
metadataobjectnoYour keys and values. tenant_id is overwritten server-side; everything else is yours. tags, project, topic, and author are useful conventions.

Example

curl -s -X POST "$BASE/create_memory" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "content": "The staging database is rebuilt every Monday 03:00 UTC; do not schedule migrations near that window.",
  "metadata": {
    "project": "atlas",
    "topic": "ops"
  }
}'

Response

{
  "ok": true,
  "data": {
    "message": "Memory created successfully",
    "id": "01a03990-1111-7abc-8def-0123456789ab",
    "receipt_id": "01a03990-1120-7bcd-9ef0-123456789abc"
  },
  "request_id": "…"
}

Errors

400 empty content; 402 storage cap where one applies; 429; 401.

POST /recall_memory

Semantic recall over deliberate memories only — conversation history is a separate record class with its own endpoint. Results are relevance-ranked.

Request fields

FieldTypeRequiredMeaning
querystringyesThe search text.
top_kintegernoResult count, default 5, capped by your subscription.

Example

curl -s -X POST "$BASE/recall_memory" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "query": "when is it safe to run a staging migration?",
  "top_k": 5
}'

Response

{
  "ok": true,
  "data": {
    "memories": [
      {
        "id": "01a03990-1111-7abc-8def-0123456789ab",
        "content": "The staging database is rebuilt every Monday 03:00 UTC; do not schedule migrations near that window.",
        "metadata": {
          "project": "atlas",
          "topic": "ops",
          "tenant_id": "hm_tenant_9f2c1a7b3d4e5f60"
        }
      }
    ]
  },
  "request_id": "…"
}

Errors

404 MEMORY_NOT_FOUND when nothing relevant is stored yet — treat it as an empty result, not a failure; 400 empty query; 429; 401.

POST /recall_with_metadata

Semantic recall scoped to exact metadata matches. Every given key must match; fewer keys match more records. This is also how authorship filters: {"author": "scout-agent"} returns only that agent’s records.

Request fields

FieldTypeRequiredMeaning
querystringyesThe search text.
metadataobjectyesExact-match filters, all required to match. An empty object is rejected.
top_kintegernoResult count, default 10, capped by your subscription.

Example

curl -s -X POST "$BASE/recall_with_metadata" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "query": "database maintenance",
  "metadata": {
    "project": "atlas"
  },
  "top_k": 10
}'

Response

{
  "ok": true,
  "data": {
    "memories": [
      {
        "id": "…",
        "content": "…",
        "metadata": {
          "project": "atlas",
          "topic": "ops"
        }
      }
    ],
    "receipt_id": "…"
  },
  "request_id": "…"
}

Errors

404 when nothing matches; 400 for an empty query or empty metadata; 429; 401.

POST /delete_by_metadata

Soft-deletes every record matching all given metadata keys. Deleted records leave recall immediately and remain recoverable until purged; every deletion is receipted. Your API key is the authorization (included with your subscription) — what your agents are allowed to delete is your harness’s policy to enforce. Mind the breadth of the filter: fewer keys match more records.

Request fields

FieldTypeRequiredMeaning
metadataobjectyesExact-match filters; everything matching all of them is soft-deleted.

Example

curl -s -X POST "$BASE/delete_by_metadata" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "metadata": {
    "project": "atlas",
    "topic": "ops"
  }
}'

Response

{
  "ok": true,
  "data": {
    "message": "Memory deletion by metadata completed successfully"
  },
  "request_id": "…"
}

Errors

401 when the plan does not enable destructive operations; 429.

POST /purge_deleted_memories

Permanently removes soft-deleted records older than the given age. Purged records cannot be recovered; until this runs, soft-deleted records are recoverable.

Request fields

FieldTypeRequiredMeaning
older_than_daysintegernoMinimum age of soft-deleted records to purge. Default 30.

Example

curl -s -X POST "$BASE/purge_deleted_memories" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "older_than_days": 30
}'

Response

{
  "ok": true,
  "data": {
    "message": "Soft-deleted memories purged successfully",
    "deleted_count": 12
  },
  "request_id": "…"
}

Errors

401 when the plan does not enable destructive operations; 429.

POST /prune_memories

Relevance-based maintenance: prunes low-relevance records, applied only once a collection has grown very large (on the order of a million records); below that it is a no-op. Most workspaces never need to call it. Plan-gated as a destructive operation and receipted like every deletion.

Request fields

The request body is an empty JSON object: {}.

Example

curl -s -X POST "$BASE/prune_memories" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

Response

{
  "ok": true,
  "data": {
    "message": "Memory pruning completed successfully"
  },
  "request_id": "…"
}

Errors

401 when the plan does not enable destructive operations; 429.

POST /purge_memories

Deletes every record the workspace holds — memories and conversation alike. It is immediate and irreversible. Your cluster itself is untouched as infrastructure; its Hivemind records are removed. Plan-gated as a destructive operation and receipted.

Request fields

No request body.

Example

curl -s -X POST "$BASE/purge_memories" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

Response

{
  "ok": true,
  "data": {
    "message": "Tenant memories have been purged successfully"
  },
  "request_id": "…"
}

Errors

401 when the plan does not enable destructive operations; 429.

GET /receipts

The audit trail, newest first. Every operation — store, recall, compile, delete — emits a persisted receipt recording what ran, what it consumed, and what it produced, with lineage back to the request that caused it.

Query parameters

FieldTypeRequiredMeaning
limitintegerno1 to 500, default 50.
session_idstringnoOnly receipts from this session.
operationstringnoOnly this operation, for example context.compile or conversation.store.

Example

curl -s "$BASE/receipts?session_id=session-1&limit=50" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY"

Response

{
  "ok": true,
  "data": {
    "receipts": [
      {
        "receipt_id": "…",
        "operation": "context.compile",
        "status": "success",
        "created_at": "2026-08-26T01:20:11.412331+00:00",
        "tenant_id": "hm_tenant_…",
        "inputs_ref": {
          "session_id": "session-1",
          "turn_number": 7,
          "budget_total": 8192
        },
        "outputs_ref": {
          "token_count": 8192
        }
      }
    ],
    "count": 1
  },
  "request_id": "…"
}

GET /usage

The authenticated workspace’s own usage: storage measured against any allowance, record counts, and the limits your subscription carries (the wire field is named plan). Watch this instead of discovering a storage cap through a 402 mid-write. On a bring-your-own cluster, storage is yours and carries no allowance from us; if your cluster is unreachable the response reports storage.error = "cluster_unreachable" rather than failing — that outage is on the cluster, not the service.

Example

curl -s "$BASE/usage" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY"

Response

On a bring-your-own cluster the limit fields are null — storage is yours and carries no allowance from us. counts.memories counts every memory point, conversation included; counts.receipts counts the audit trail.

{
  "ok": true,
  "data": {
    "plan": {
      "name": "hive",
      "max_top_k": 50,
      "rate_limit": 120,
      "receipts_enabled": true
    },
    "storage": {
      "mode": "byo",
      "used_bytes": 18734231,
      "used_mb": 17.9,
      "limit_bytes": null,
      "limit_mb": null,
      "percent_used": null
    },
    "counts": {
      "memories": 1504,
      "receipts": 4310
    },
    "tenant_id": "hm_tenant_9f2c1a7b3d4e5f60"
  },
  "request_id": "…"
}

GET /export

Paginated export of everything the workspace owns: memories and conversation, then receipts. Content and metadata; vectors are derivable and not exported. Follow next_cursor until done is true. This is the portability contract — your data leaves whenever you ask.

Query parameters

FieldTypeRequiredMeaning
cursorstringnoThe next_cursor from the previous page. Omit for the first page.
limitintegerno1 to 500 records per page, default 200.

Example

curl -s "$BASE/export?limit=200" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY"

Response

The first page also writes a data.export receipt into your audit trail, so every export is itself on the record.

{
  "ok": true,
  "data": {
    "records": [
      {
        "id": "…",
        "content": "…",
        "metadata": {}
      }
    ],
    "count": 200,
    "phase": "memories",
    "next_cursor": "bWVtb3JpZXM6MjAw",
    "done": false
  },
  "request_id": "…"
}

POST /context/compile

The compiler alone: no store, no recall. For custom loops that run their own recall (or none) and want the packet built from exactly what they pass. /turn is this endpoint composed with the store and both recall searches; everything said about the packet there applies here.

Request fields

FieldTypeRequiredMeaning
session_idstringyesConversation identifier.
messagesarray of objectsyesThe history to compile, {role, content, timestamp}, oldest first, real timestamps.
turn_numberintegeryesMonotonic per session.
budget_totalintegeryesPacket budget; maximum 128,000.
user_inputstringnoThe current message, when not already in messages.
operator_briefingstringnoAs on /turn.
holdsarray of objectsnoAs on /turn.
artifacts_contextstringnoAs on /turn.
recalled_conversationarray of objectsnoYour own recall results to include, in the shape /conversation/recall returns.
recalled_memoriesarray of objectsnoYour own memory recall results, in the shape /recall_memory returns.
policyobjectnoCompiler policy overrides.
configobjectnoAdvanced compiler configuration.
event_idstringnoYour own event identifier, echoed in the trace.
trace_idstringnoYour own trace identifier, echoed in the trace.
timestampnumbernoUnix time of the compile, when you need to pin it.

Example

curl -s -X POST "$BASE/context/compile" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "session_id": "session-1",
  "messages": [
    {
      "role": "user",
      "content": "Summarise where we are.",
      "timestamp": 1787600300.0
    }
  ],
  "turn_number": 8,
  "budget_total": 4096
}'

Response

{
  "ok": true,
  "data": {
    "bundle": {
      "messages": [
        "…"
      ],
      "token_count": 4096,
      "source_counts": {},
      "budget_total": 4096,
      "budget_available": 4096,
      "conversation_tokens": 31,
      "hold_tokens": 0,
      "trace": {}
    },
    "receipt": {
      "operation": "context.compile"
    }
  },
  "request_id": "…"
}

Errors

400 for an empty session id or a budget above 128,000; 429; 401.