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. Every request names its project in the X-Hivemind-Project header, and a request without it is refused with 400: there is no default project for a key. A key reaches every project of the workspace, or the projects that list its role, and a request naming a project the key does not reach is refused with 403. A key also carries what it may remove — nothing, the recoverable deletes, or those plus the permanent purges — and a new key starts able to read and write only; a route the key's level does not allow answers 403. 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 three helpers — one POST, one GET, one DELETE — defined once per language. They carry the base URL, the bearer key, the project, 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. The project is sent as the X-Hivemind-Project header, which every request must carry: the id of the project to work in, shown beside each project in the dashboard.

BASE="https://api.hivemind.militant.ai/hivemind/v1"
export HIVEMIND_API_KEY="sk_your_key"
export HIVEMIND_PROJECT="proj_your_project"
# every call: curl with the Authorization and X-Hivemind-Project headers, JSON in, JSON out

Client implementation guide

A correct client holds two 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. No conversation history: the service stores every message and reads the session’s recent conversation back on every /turn, newest first, as much as budget_total can hold, in reading order.

The lifecycle of one exchange

Increment the turn counter. Generate a fresh exchange_pair_id — a time-ordered UUIDv7 by convention. Call /turn with the user message as user_input. 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 author in additional_metadata if you attribute agents. 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

Nothing to rebuild: the conversation lives in the service. 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 that timed out: the request may have executed, and replaying it stores the message twice. For a /conversation/store that timed out, verify first: replay the session with /conversation/recall and look for the reply under its exchange_pair_id, then re-send only if it is not there. 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 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 — counting your own messages 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 stores user_input as the call half of a new exchange (receipted), runs semantic recall over deliberate memories and past conversation using the input as the query, reads the session’s recent conversation back from what it stored (newest first, as much as budget_total can hold), and compiles them with active holds and the operator briefing into one token-budgeted prompt packet, user_input last. Recalled conversation that is already in the recent conversation is left out. 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 objectsnoOnly what the service does not store and later compiles should see — tool results — never the conversation itself, which the service reads back. ONE of two shapes — never mixed in one request. Chat form: {role, content, timestamp, metadata?}, role user | assistant | system | tool, with a real unix timestamp — the compiler orders conversation by timestamp. A role: "tool" entry carries a tool result; put {"tool_call_id", "tool_name"} in its metadata and the packet renders them as the message's tool_call_id and name. Event form (detected when the first entry has type and payload): each entry {type, payload, id?, ts?} with types user.input (payload.text), assistant.message.final (payload.content), and tool.call.finished (payload.call_id, ok, result | error) — tool events land in the dedicated tool admission lane governed by max_tool_items/include_tools.
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, applied as an overlay: fields you omit keep their budget-derived values (window from budget_total, balanced admission, budget-scaled caps). Omit entirely unless you need a cap or flag.
configobjectnoAdvanced compiler configuration. Omit unless you know you need it.

Example

curl -s -X POST "$BASE/turn" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "X-Hivemind-Project: $HIVEMIND_PROJECT" \
  -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",
  "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. stored confirms the call half was written; session_history counts the stored messages of this session read back as the recent conversation; timings is the server-side phase breakdown in seconds.

{
  "ok": true,
  "data": {
    "bundle": {
      "messages": [
        {
          "id": "…",
          "role": "system",
          "content": "…compiled packet: briefing, holds, recalled memory, conversation…"
        },
        {
          "id": "01a047c7-4c0c-73a0-80d9-885d20403920",
          "role": "user",
          "content": "Can we ship the migration tonight?",
          "name": "operator-7"
        }
      ],
      "policy": {
        "max_context_tokens": 8192, "max_items": 64, "max_memory_items": 16,
        "max_tool_items": 10, "max_hold_items": null, "include_memory": true,
        "include_tools": true, "include_system": true, "include_directives": true,
        "include_holds": true, "admission_strategy": "balanced"
      },
      "token_count": 3210,
      "source_counts": {
        "conversation": 13,
        "hivemind": 4,
        "recalled_conversation": 2,
        "directive": 1,
        "system": 3
      },
      "budget_total": 8192,
      "budget_available": 4982,
      "conversation_tokens": 310,
      "hold_tokens": 84,
      "trace": {
        "trace_id": "…",
        "event_id": "…",
        "receipt_id": "…",
        "parent_receipt_id": null
      }
    },
    "receipt": {
      "receipt_id": "01a0a64b-28b3-7e9b-a152-70ec1f4b6a29",
      "operation": "context.compile",
      "status": "ok",
      "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": 3210,
        "item_ids": [
          "…"
        ],
        "source_counts": {
          "conversation": 13,
          "hivemind": 4,
          "recalled_conversation": 2,
          "directive": 1,
          "system": 3
        }
      },
      "source_receipt_ids": [],
      "parent_receipt_id": null,
      "request_id": "01a069b3-bbcb-7a6c-a9f5-ff21413e878a",
      "idempotency_key": "01a069b3-bbcb-7a6c-a9f5-ff21413e878a"
    },
    "stored": {
      "id": "01a047c7-4c0c-73a0-80d9-885d20403920",
      "receipt_id": "01a0a7dc-f73a-745a-ab41-2a57f72bec14"
    },
    "recalled_memories": 4,
    "recalled_conversation": 6,
    "session_history": 12,
    "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": "01a056a5-6217-7a7c-8d3f-aa08b063b90a"
}

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. If your message cannot be stored the turn fails with 500 INTERNAL_ERROR and nothing is compiled: a message with no stored row would be missing from the conversation from then on.

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_metadataobjectnoOne convention matters: author attributes the half to a named agent or person. The question needs nothing here: /turn already stored it, and recall pairs the two by exchange_pair_id. Limits: at most 64 keys; a key up to 128 characters; a string value up to 8,000 characters; a list value up to 64 items. Anything beyond is rejected with 400 VALIDATION_ERROR, and the message says which limit was broken.

Example

curl -s -X POST "$BASE/conversation/store" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "X-Hivemind-Project: $HIVEMIND_PROJECT" \
  -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": {
    "author": "deploy-assistant"
  }
}'

Response

{
  "ok": true,
  "data": {
    "message": "Conversation exchange stored successfully",
    "id": "01a0afb5-e514-780d-bee5-6f85ce258536",
    "receipt_id": "01a03163-2e2c-74f8-8a54-1125c38b68dc"
  },
  "request_id": "…"
}

Errors

400 VALIDATION_ERROR for empty content, session id, or exchange pair id; 429; 401. If this call fails after a successful turn, the chat already succeeded — do not surface an error to the user. On a 5xx, or a failure before any response began, retry it out of band with a backoff that ends. A 4xx other than 429 is permanent for that request: the same request will be refused again, so fix it or set it aside, and never let it block the stores queued behind it. A timed-out store may have landed, and the service does not dedupe a re-sent reply half: verify first: replay the session with /conversation/recall and look for the reply under its exchange_pair_id, then re-send only if it is not there.

POST /conversation/recall

Explicit episode recall over conversation history. Two modes: with session_id omitted or null it searches semantically across all sessions, one slot per exchange, and returns each exchange whole: the half that matched, plus its other half (so up to two records per slot); with a session_id it returns that session’s exchanges in full, both halves, newest first by the time each was stored — the replay path. A session made by /conversation/fork replays its own messages, then its parent’s from before the fork. 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 "X-Hivemind-Project: $HIVEMIND_PROJECT" \
  -H "Content-Type: application/json" \
  -d '{
  "query": "production snapshot rebuild schedule Mondays 03:00 UTC",
  "session_id": null,
  "top_k": 3
}'

Response

{
  "ok": true,
  "data": {
    "memories": [
      {
        "id": "01a0a3db-b5a0-77ed-bc51-147f51059aa4",
        "content": "When is the staging database rebuilt?",
        "metadata": {
          "author": "ops-lead",
          "type": "conversation_exchange",
          "session_id": "docs-example-6df0986250",
          "turn_number": 4,
          "role": "user",
          "exchange_pair_id": "xp-docs-6df0986250",
          "timestamp": 1789780724.6888163,
          …
        }
      },
      {
        "id": "01a05826-8904-7357-b371-2d2fdbd0fb56",
        "content": "The staging database is rebuilt Mondays 03:00 UTC from the previous night's production snapshot.",
        "metadata": {
          "author": "deploy-assistant",
          "type": "conversation_exchange",
          "session_id": "docs-example-6df0986250",
          "turn_number": 4,
          "role": "assistant",
          "exchange_pair_id": "xp-docs-6df0986250",
          "timestamp": 1789780727.848919,
          "semantic_relativity": 0.75301,
          "resonance_score": 0.75301,
          …
        }
      }
    ]
  },
  "request_id": "01a06ba7-26d0-739c-8c70-9b0900ba555e"
}

A real response to a query worded like the reply. The reply is the half that matched, so it carries the match scores; the question was placed before it, so the exchange arrives whole. Metadata is abridged here (…): every record also carries the service’s own bookkeeping fields.

Errors

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

POST /conversation/fork

Starts a new session as a fork of an existing one, cut at the moment of the call. The new session’s /turn and /conversation/recall read the parent’s conversation up to the fork, then only what the new session stores. Messages the parent stores later are not the fork’s, and the parent never sees the fork’s. Nothing is copied: one marker record is stored in the new session, and it never comes back from any recall or search. Deleting a parent message removes it from the fork too. A fork can itself be forked. Use it to clone an agent that continues from the same context: run the clone on new_session_id.

Request fields

FieldTypeRequiredMeaning
session_idstringyesThe session to fork. At most 256 characters.
new_session_idstringyesThe new session. Must hold no records yet and must differ from session_id. At most 256 characters.
authorstringnoWho forked it; stored on the marker. At most 256 characters.

Example

curl -s -X POST "$BASE/conversation/fork" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "X-Hivemind-Project: $HIVEMIND_PROJECT" \
  -H "Content-Type: application/json" \
  -d '{
  "session_id": "planner-main",
  "new_session_id": "planner-clone-2",
  "author": "planner"
}'

Response

{
  "ok": true,
  "data": {
    "session_id": "planner-clone-2",
    "forked_from": "planner-main",
    "fork_time": 1790581204.5316944,
    "marker_id": "01a1c7e2-4f0b-7a91-9c3d-5e8b21f6a0d4",
    "receipt_id": "01a1c7e2-5a17-7c02-8e4f-b3d90a6c1e72"
  },
  "request_id": "01a1c7e2-3e88-79f5-a2b6-0c4d8f13e957"
}

fork_time is the cut, in the service’s epoch seconds: the parent’s messages stored before it are the new session’s history. marker_id is the marker record; receipt_id is the conversation.fork receipt.

Errors

404 MEMORY_NOT_FOUND when session_id holds no conversation; 409 CONFLICT when new_session_id already holds records — pick a new id; 400 VALIDATION_ERROR when either id is empty or both are the same, with details.field naming the field; 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 id or 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; at most 200,000 characters.
metadataobjectnoYour keys and values. tenant_id is overwritten server-side; everything else is yours. tags, project, topic, and author are useful conventions. Metadata is for labels and small facts; long text belongs in content. Limits: at most 64 keys; a key up to 128 characters; a string value up to 8,000 characters; a list value up to 64 items. Anything beyond is rejected with 400 VALIDATION_ERROR, and the message says which limit was broken.

Example

curl -s -X POST "$BASE/create_memory" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "X-Hivemind-Project: $HIVEMIND_PROJECT" \
  -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": "01a05f7c-7b0a-70be-b939-427cd2ea8b63",
    "receipt_id": "01a043aa-e42e-7b78-b802-2c8d8d246178"
  },
  "request_id": "…"
}

Errors

400 empty content; 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 "X-Hivemind-Project: $HIVEMIND_PROJECT" \
  -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": "01a05f7c-7b0a-70be-b939-427cd2ea8b63",
        "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. The same size limits apply as on stored metadata: 64 keys, 128-character keys, 8,000-character string values, 64-item lists.
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 "X-Hivemind-Project: $HIVEMIND_PROJECT" \
  -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. The same size limits apply as on stored metadata: 64 keys, 128-character keys, 8,000-character string values, 64-item lists.

Example

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

Response

{
  "ok": true,
  "data": {
    "message": "Memory deletion by metadata completed successfully",
    "deleted_records": 3,
    "deleted_exchanges": 0,
    "deleted_points": 4
  },
  "request_id": "01a020d4-5a2f-73d6-8409-448f16583231"
}

A real response: three notes matched, one of them long enough to be stored as two chunks. The three numbers count the same deletion in three units, and they differ on purpose:

FieldCounts
deleted_recordsRecords, as a recall shows them: one memory is one record however many chunks it is stored as. This is the number to show a person.
deleted_exchangesDistinct conversation exchanges among those records. A metadata recall gives an exchange one result — its question and its reply together — so three exchanges are six records.
deleted_pointsStored rows marked deleted. A long record is several rows, so this is never smaller than deleted_records. It is the unit storage usage and /purge_deleted_memories work in.

A filter that matches nothing is a 200 with all three counts at zero, not an error — so is repeating a delete that already ran.

Every call writes one memory.delete_by_metadata receipt, readable through GET /receipts, including a call that matched nothing. Its outputs_ref carries deleted_count (stored rows, the same number as deleted_points), deleted_records and deleted_exchanges.

Errors

401 when the plan does not enable destructive operations; 403 when the key's level is below Can delete, which this route needs; 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 "X-Hivemind-Project: $HIVEMIND_PROJECT" \
  -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; 403 when the key's level is below Can delete and purge, which this route needs; 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. The response’s deleted_count is how many stored pieces were removed — the same number the memory.purge_tenant receipt records. The workspace’s earlier receipts are removed with the records; that one receipt is written afterwards and is what remains.

Request fields

No request body.

Example

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

Response

{
  "ok": true,
  "data": {
    "message": "Tenant memories have been purged successfully",
    "deleted_count": 3
  },
  "request_id": "01a09cb8-cf73-724a-842b-0af6a50d48fa"
}

Errors

401 when the plan does not enable destructive operations; 403 when the key's level is below Can delete and purge, which this route needs; 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" \
  -H "X-Hivemind-Project: $HIVEMIND_PROJECT"

Response

{
  "ok": true,
  "data": {
    "receipts": [
      {
        "receipt_id": "…",
        "operation": "context.compile",
        "status": "ok",
        "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": 3210
        }
      }
    ],
    "count": 1
  },
  "request_id": "…"
}

GET /memory/{memory_id}

Exact lookup of one record by the id that create_memory returned or that any recall hit carried. Scoped to your workspace: an id from another workspace is a 404, not a leak. A soft-deleted record answers 404 until it is purged. The shape is the same as a recall hit — id, content, metadata exactly as stored, service-stamped fields included.

Path parameters

FieldTypeRequiredMeaning
memory_idstringyesThe record id. 1 to 256 characters.

Example

curl -s "$BASE/memory/01a0f6ad-acdc-7d3c-a986-cecaf06a6d3c" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "X-Hivemind-Project: $HIVEMIND_PROJECT"

Response

{
  "ok": true,
  "data": {
    "memory": {
      "id": "01a0f6ad-acdc-7d3c-a986-cecaf06a6d3c",
      "content": "Decision: the lookup route returns exactly one record by its id.",
      "metadata": {
        "content_type": "decision",
        "topic": "api",
        "tenant_id": "hm_tenant_107eb8ef49751bd9",
        "written_by_key": "api_key_011T2CW29YDCFKWD3KHNNS0VMW",
        "kind": "memory",
        "recall_mode": "semantic",
        "storage_mode": "inline",
        "content_length_bytes": 64,
        "embedding_model": "all-MiniLM-L6-v2",
        "timestamp": 1789814850.9363692,
        "recall_count": 0,
        "memetic_similarity": 1.0,
        "semantic_relativity": 1.0,
        "gravitational_pull": 1.0000000632769623,
        "spacetime_coordinate": 0.9999371247296064
      }
    }
  },
  "request_id": "01a011aa-77db-7253-b1b7-e9b68189d8dd"
}

An unknown id, another workspace’s id, or a soft-deleted record answers 404 MEMORY_NOT_FOUND with details.field = "memory_id".

DELETE /memory/{memory_id}

Soft-deletes exactly one record, by the id that create_memory returned, that a recall hit carried, or that GET /memory/{id} answered with. It deletes what that GET would fetch and nothing else: a long record stored in chunks goes whole, the other half of a conversation exchange stays, and no other record is touched however similar its metadata. The record leaves recall at once and stays recoverable until purged. Use it when you hold the id; use /delete_by_metadata to remove everything matching a filter. Receipts are your audit trail and cannot be deleted this way.

Path parameters

FieldTypeRequiredMeaning
memory_idstringyesThe record id. 1 to 256 characters. No request body.

Example

curl -s -X DELETE "$BASE/memory/01a009c2-b0e3-774a-916c-fefb744fd6e1" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "X-Hivemind-Project: $HIVEMIND_PROJECT"

Response

{
  "ok": true,
  "data": {
    "message": "Memory deleted",
    "id": "01a009c2-b0e3-774a-916c-fefb744fd6e1",
    "deleted": true,
    "deleted_records": 1,
    "deleted_exchanges": 0,
    "deleted_points": 1
  },
  "request_id": "01a0350b-83fc-77a5-a0ec-fad4aaf36fc5"
}

An unknown id, another workspace’s id, an id that is not a valid record id, or a record already deleted answers 404 MEMORY_NOT_FOUND — so deleting the same id twice answers 404 the second time. Nothing is written and no receipt is created.

The three counts are the same units /delete_by_metadata reports: deleted_records is always 1, deleted_exchanges is always 0 because the other half of an exchange is never removed, and deleted_points is the stored rows marked — more than 1 for a long record stored in chunks. A deletion that removed something writes one memory.delete receipt, readable through GET /receipts, whose outputs_ref carries deleted: true and points, the same number as deleted_points.

Errors

404 MEMORY_NOT_FOUND as above; 401 for a missing or invalid key, or when your subscription does not enable destructive operations; 403 when the key belongs to another project, or when its level is below Can delete; 429.

GET /receipts/{receipt_id}

Exact lookup of one receipt by the receipt_id that any write, recall, or turn returned. Scoped to your workspace. The shape matches one entry of the receipts listing.

Path parameters

FieldTypeRequiredMeaning
receipt_idstringyesThe receipt id. 1 to 256 characters.

Example

curl -s "$BASE/receipts/01a06373-1673-78d3-a307-21b8d31b7276" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "X-Hivemind-Project: $HIVEMIND_PROJECT"

Response

{
  "ok": true,
  "data": {
    "receipt": {
      "receipt_id": "01a06373-1673-78d3-a307-21b8d31b7276",
      "operation": "memory.create",
      "status": "ok",
      "created_at": "2026-09-11T07:30:47.410880+00:00",
      "tenant_id": "hm_tenant_107eb8ef49751bd9",
      "principal_id": "hm_tenant_107eb8ef49751bd9",
      "agent_id": null,
      "session_id": null,
      "turn_id": null,
      "source_event_id": null,
      "trace_id": null,
      "parent_receipt_id": null,
      "request_id": "01a08f85-8645-75cd-a24b-385f380c5db1",
      "idempotency_key": "01a08f85-8645-75cd-a24b-385f380c5db1",
      "inputs_ref": {
        "content_preview": "Decision: the lookup route returns exactly one record by its id.",
        "metadata": {
          "content_type": "decision",
          "tenant_id": "hm_tenant_107eb8ef49751bd9",
          "topic": "api"
        }
      },
      "outputs_ref": {
        "memory_id": "01a050eb-fe23-778c-8664-394df14040e0"
      },
      "metadata": {},
      "source_receipt_ids": []
    }
  },
  "request_id": "01a0d86e-fd5f-7f15-a42d-3a84b275e985"
}

Not found

Unknown id or another workspace's receipt: HTTP 404 with MEMORY_NOT_FOUND and details.field = receipt_id.

GET /usage

The authenticated workspace’s own usage: its record counts, and the limits your subscription carries (the wire field is named plan). Storage is your own cluster and carries no allowance from us — this reports what is there, and nothing is refused on it. It is not your bill: billing is metered on compiled tokens. It also reports the calling key, what that key may remove, and which projects it can reach. The key’s part answers even before any cluster is connected: storage.error is then "no_cluster" and counts is null. Like every route it needs X-Hivemind-Project, so a client needs one project id before its first call; once it has one, projects lists the rest the key can reach. If your cluster is unreachable the response reports storage.error = "cluster_unreachable" rather than failing.

Example

curl -s "$BASE/usage" \
  -H "Authorization: Bearer $HIVEMIND_API_KEY" \
  -H "X-Hivemind-Project: $HIVEMIND_PROJECT"

Response

storage reports the mode. plan.rate_limit of 0 means no requests-per-minute limit. key is the calling key’s own settings: its id, its delete level in destructive ("none", "delete" or "purge"), and projects, the projects it reaches, or null when it reaches every project. destructive is never null here: a key with no level set is refused as invalid, 401, before it reaches this route. plan.allow_destructive is the workspace’s own switch, which applies on top of the key’s level. destructive_access is the service’s verdict after applying both — read destructive_access.delete and destructive_access.purge rather than working it out from the other fields. projects lists every project the key can reach, each with its id, name, whether a cluster is connected, and whether it is the workspace default; a key with a list of projects lists those that still exist and no others, as in the example below. A project’s cluster address and credential are never included. Reading this writes nothing: no receipt, and no refusal. 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": 0,
      "allow_destructive": true,
      "receipts_enabled": true
    },
    "storage": {
      "mode": "byo"
    },
    "counts": {
      "memories": 1840,
      "receipts": 2615
    },
    "tenant_id": "hm_tenant_107eb8ef49751bd9",
    "key": {
      "id": "api_key_01SETB4MGGY5WKSZE2MC4X8R0Y",
      "destructive": "none",
      "projects": [
        "proj_30e37f2dee65"
      ]
    },
    "destructive_access": {
      "delete": false,
      "purge": false
    },
    "projects": [
      {
        "id": "proj_30e37f2dee65",
        "name": "Research",
        "connected": true,
        "default": false
      }
    ]
  },
  "request_id": "01a03f00-6ad4-7422-8780-4f19c58d2e90"
}

GET /export

Paginated export of everything the workspace owns: memories and conversation, then receipts. Content and metadata; vectors are 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" \
  -H "X-Hivemind-Project: $HIVEMIND_PROJECT"

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, oldest first, real timestamps — same two shapes as /turn's messages (chat form incl. role: "tool" with metadata.tool_call_id/tool_name, or interface events incl. tool.call.finished; one shape per request, never mixed).
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, applied as an overlay: omitted fields keep their budget-derived values.
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 "X-Hivemind-Project: $HIVEMIND_PROJECT" \
  -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": 78,
      "source_counts": { "conversation": 1, "system": 1 },
      "budget_total": 4096,
      "budget_available": 4018,
      "conversation_tokens": 31,
      "hold_tokens": 0,
      "trace": { "session_id": "session-1", "turn_number": 8,
                 "budget_used": 78, "budget_available": 4018 }
    },
    "receipt": {
      "operation": "context.compile"
    }
  },
  "request_id": "…"
}

Errors

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