API Reference
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 outconst BASE = "https://api.hivemind.militant.ai/hivemind/v1";
const HEADERS = {
"Authorization": `Bearer ${process.env.HIVEMIND_API_KEY}`,
"Content-Type": "application/json",
};
async function hm(path, body) {
const res = await fetch(BASE + path, { method: "POST", headers: HEADERS, body: JSON.stringify(body) });
const data = await res.json();
if (!data.ok) throw new Error(`${res.status}: ${JSON.stringify(data.error)}`);
return data;
}
async function hmGet(pathWithQuery) {
const res = await fetch(BASE + pathWithQuery, { headers: HEADERS });
const data = await res.json();
if (!data.ok) throw new Error(`${res.status}: ${JSON.stringify(data.error)}`);
return data;
}import os
import requests
BASE = "https://api.hivemind.militant.ai/hivemind/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['HIVEMIND_API_KEY']}"}
def hm(path, body):
response = requests.post(BASE + path, headers=HEADERS, json=body, timeout=120)
data = response.json()
if not data.get("ok"):
raise RuntimeError(f"{response.status_code}: {data.get('error')}")
return data
def hm_get(path_with_query):
response = requests.get(BASE + path_with_query, headers=HEADERS, timeout=120)
data = response.json()
if not data.get("ok"):
raise RuntimeError(f"{response.status_code}: {data.get('error')}")
return datapackage main
import (
"bytes"
"encoding/json"
"log"
"net/http"
"os"
)
const base = "https://api.hivemind.militant.ai/hivemind/v1"
func jsonBody(raw string) []byte { return []byte(raw) }
func do(req *http.Request) map[string]any {
req.Header.Set("Authorization", "Bearer "+os.Getenv("HIVEMIND_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer res.Body.Close()
var out map[string]any
json.NewDecoder(res.Body).Decode(&out)
if ok, _ := out["ok"].(bool); !ok { log.Fatalf("%d: %v", res.StatusCode, out["error"]) }
return out
}
func hm(path string, body []byte) map[string]any {
req, _ := http.NewRequest("POST", base+path, bytes.NewReader(body))
return do(req)
}
func hmGet(pathWithQuery string) map[string]any {
req, _ := http.NewRequest("GET", base+pathWithQuery, nil)
return do(req)
}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
| Field | Type | Required | Meaning |
|---|---|---|---|
user_input | string | yes | The initiating message of this exchange. Stored verbatim and used as the recall query. |
session_id | string | yes | Your 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_number | integer | yes | Monotonic per session; your client increments it each turn. |
budget_total | integer | yes | Token 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_id | string | yes | A fresh collision-resistant id per exchange; the store call must repeat it exactly. Convention: time-ordered UUIDv7. |
messages | array of objects | recommended | Your 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_briefing | string | no | Standing instructions compiled into the packet. Never stored; re-send it every turn. |
holds | array of objects | no | Pinned 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_context | string | no | One-shot context (a document excerpt, a tool result) compiled into this packet only. |
recall_top_k | integer | no | Forces a fixed recall pool. Omit it: the service sizes the pool from the budget, and your subscription caps the maximum. |
author | string | no | Who produced this call — identity metadata, orthogonal to role. Surfaces as the chat message name in compiles and recalls, and filters in metadata recall. |
policy | object | no | Compiler policy overrides. Omit unless you know you need it. |
config | object | no | Advanced 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"
}'const result = await hm("/turn", {
"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"
});
const packet = result.data.bundle.messages; // feed this to your modelresult = hm("/turn", {
"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"
})
packet = result["data"]["bundle"]["messages"] # feed this to your modelresult := hm("/turn", jsonBody(`{
"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"
}`))
// result["data"].bundle.messages feeds your modelResponse
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
| Field | Type | Required | Meaning |
|---|---|---|---|
content | string | yes | The message text. Empty content is rejected. |
session_id | string | yes | Same session as the turn. |
turn_number | integer | yes | Same turn number as the turn. |
role | string | yes | "assistant" for the reply half, "user" for a call half. Role is positional — which side of the exchange — not human versus machine. |
exchange_pair_id | string | yes | Exactly the id the turn used. This is the pairing. |
additional_metadata | object | no | Two 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"
}
}'const result = await hm("/conversation/store", {
"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"
}
});result = hm("/conversation/store", {
"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"
}
})result := hm("/conversation/store", jsonBody(`{
"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
| Field | Type | Required | Meaning |
|---|---|---|---|
query | string | yes | The search text. Matched against both sides of every exchange. |
session_id | string or null | no | Null searches across sessions; a value replays that session. |
top_k | integer | no | Result 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
}'const result = await hm("/conversation/recall", {
"query": "what was decided about the staging window",
"session_id": null,
"top_k": 10
});result = hm("/conversation/recall", {
"query": "what was decided about the staging window",
"session_id": null,
"top_k": 10
})result := hm("/conversation/recall", jsonBody(`{
"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
| Field | Type | Required | Meaning |
|---|---|---|---|
content | string | yes | The fact, stated so it stands alone. Empty content is rejected. |
metadata | object | no | Your 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"
}
}'const result = await hm("/create_memory", {
"content": "The staging database is rebuilt every Monday 03:00 UTC; do not schedule migrations near that window.",
"metadata": {
"project": "atlas",
"topic": "ops"
}
});result = hm("/create_memory", {
"content": "The staging database is rebuilt every Monday 03:00 UTC; do not schedule migrations near that window.",
"metadata": {
"project": "atlas",
"topic": "ops"
}
})result := hm("/create_memory", jsonBody(`{
"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
| Field | Type | Required | Meaning |
|---|---|---|---|
query | string | yes | The search text. |
top_k | integer | no | Result 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
}'const result = await hm("/recall_memory", {
"query": "when is it safe to run a staging migration?",
"top_k": 5
});result = hm("/recall_memory", {
"query": "when is it safe to run a staging migration?",
"top_k": 5
})result := hm("/recall_memory", jsonBody(`{
"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
| Field | Type | Required | Meaning |
|---|---|---|---|
query | string | yes | The search text. |
metadata | object | yes | Exact-match filters, all required to match. An empty object is rejected. |
top_k | integer | no | Result 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
}'const result = await hm("/recall_with_metadata", {
"query": "database maintenance",
"metadata": {
"project": "atlas"
},
"top_k": 10
});result = hm("/recall_with_metadata", {
"query": "database maintenance",
"metadata": {
"project": "atlas"
},
"top_k": 10
})result := hm("/recall_with_metadata", jsonBody(`{
"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
| Field | Type | Required | Meaning |
|---|---|---|---|
metadata | object | yes | Exact-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"
}
}'const result = await hm("/delete_by_metadata", {
"metadata": {
"project": "atlas",
"topic": "ops"
}
});result = hm("/delete_by_metadata", {
"metadata": {
"project": "atlas",
"topic": "ops"
}
})result := hm("/delete_by_metadata", jsonBody(`{
"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
| Field | Type | Required | Meaning |
|---|---|---|---|
older_than_days | integer | no | Minimum 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
}'const result = await hm("/purge_deleted_memories", {
"older_than_days": 30
});result = hm("/purge_deleted_memories", {
"older_than_days": 30
})result := hm("/purge_deleted_memories", jsonBody(`{
"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 '{}'const result = await hm("/prune_memories", {});result = hm("/prune_memories", {})result := hm("/prune_memories", jsonBody(`{}`))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 '{}'const result = await hm("/purge_memories", {});result = hm("/purge_memories", {})result := hm("/purge_memories", jsonBody(`{}`))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
| Field | Type | Required | Meaning |
|---|---|---|---|
limit | integer | no | 1 to 500, default 50. |
session_id | string | no | Only receipts from this session. |
operation | string | no | Only 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"const result = await hmGet("/receipts?session_id=session-1&limit=50");result = hm_get("/receipts?session_id=session-1&limit=50")result := hmGet("/receipts?session_id=session-1&limit=50")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"const result = await hmGet("/usage");result = hm_get("/usage")result := hmGet("/usage")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
| Field | Type | Required | Meaning |
|---|---|---|---|
cursor | string | no | The next_cursor from the previous page. Omit for the first page. |
limit | integer | no | 1 to 500 records per page, default 200. |
Example
curl -s "$BASE/export?limit=200" \
-H "Authorization: Bearer $HIVEMIND_API_KEY"const result = await hmGet("/export?limit=200");result = hm_get("/export?limit=200")result := hmGet("/export?limit=200")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
| Field | Type | Required | Meaning |
|---|---|---|---|
session_id | string | yes | Conversation identifier. |
messages | array of objects | yes | The history to compile, {role, content, timestamp}, oldest first, real timestamps. |
turn_number | integer | yes | Monotonic per session. |
budget_total | integer | yes | Packet budget; maximum 128,000. |
user_input | string | no | The current message, when not already in messages. |
operator_briefing | string | no | As on /turn. |
holds | array of objects | no | As on /turn. |
artifacts_context | string | no | As on /turn. |
recalled_conversation | array of objects | no | Your own recall results to include, in the shape /conversation/recall returns. |
recalled_memories | array of objects | no | Your own memory recall results, in the shape /recall_memory returns. |
policy | object | no | Compiler policy overrides. |
config | object | no | Advanced compiler configuration. |
event_id | string | no | Your own event identifier, echoed in the trace. |
trace_id | string | no | Your own trace identifier, echoed in the trace. |
timestamp | number | no | Unix 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
}'const result = await hm("/context/compile", {
"session_id": "session-1",
"messages": [
{
"role": "user",
"content": "Summarise where we are.",
"timestamp": 1787600300.0
}
],
"turn_number": 8,
"budget_total": 4096
});result = hm("/context/compile", {
"session_id": "session-1",
"messages": [
{
"role": "user",
"content": "Summarise where we are.",
"timestamp": 1787600300.0
}
],
"turn_number": 8,
"budget_total": 4096
})result := hm("/context/compile", jsonBody(`{
"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.