diff --git a/README.md b/README.md
index 0f9ee060..9fe1d45e 100644
--- a/README.md
+++ b/README.md
@@ -418,6 +418,8 @@ engram/
|-------|-------------|
| [docs/architecture.md](docs/architecture.md) | System design, data flows, Arweave integration |
| [docs/miner.md](docs/miner.md) | Miner setup, configuration, systemd, Docker |
+| [docs/openapi.json](docs/openapi.json) | OpenAPI 3.1 spec for miner HTTP endpoints |
+| [docs/miner-openapi.html](docs/miner-openapi.html) | Redoc viewer for the miner HTTP API |
| [docs/validator.md](docs/validator.md) | Validator setup and scoring loop |
| [docs/sdk.md](docs/sdk.md) | Python SDK full reference |
| [docs/cli.md](docs/cli.md) | CLI command reference |
diff --git a/docs/miner-openapi.html b/docs/miner-openapi.html
new file mode 100644
index 00000000..1c9d3cd5
--- /dev/null
+++ b/docs/miner-openapi.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Engram Miner HTTP API
+
+
+
+
+
+
diff --git a/docs/miner.md b/docs/miner.md
index 138f24b9..de8d2a52 100644
--- a/docs/miner.md
+++ b/docs/miner.md
@@ -160,6 +160,23 @@ curl http://localhost:8091/health
---
+## HTTP API Reference
+
+The miner HTTP API is documented as OpenAPI 3.1 in [`openapi.json`](openapi.json).
+Browse it locally through the Redoc page at [`miner-openapi.html`](miner-openapi.html).
+
+Regenerate the spec after changing miner routes:
+
+```bash
+python scripts/generate_openapi.py
+python scripts/generate_openapi.py --check
+```
+
+The OpenAPI generator reads the same route registry used by `neurons/miner.py`,
+so CI can catch stale docs when an endpoint is added, removed, or renamed.
+
+---
+
## systemd Service
```ini
diff --git a/docs/openapi.json b/docs/openapi.json
new file mode 100644
index 00000000..6b9702e1
--- /dev/null
+++ b/docs/openapi.json
@@ -0,0 +1,2687 @@
+{
+ "components": {
+ "schemas": {
+ "AttestNamespaceRequest": {
+ "properties": {
+ "namespace": {
+ "type": "string"
+ },
+ "owner_hotkey": {
+ "type": "string"
+ },
+ "signature": {
+ "type": "string"
+ },
+ "timestamp_ms": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "namespace",
+ "owner_hotkey",
+ "signature",
+ "timestamp_ms"
+ ],
+ "type": "object"
+ },
+ "AttestNamespaceResponse": {
+ "properties": {
+ "namespace": {
+ "type": "string"
+ },
+ "ok": {
+ "type": "boolean"
+ },
+ "stake_tao": {
+ "type": "number"
+ },
+ "trust_tier": {
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "AttestationResponse": {
+ "additionalProperties": true,
+ "type": "object"
+ },
+ "ChallengeRequest": {
+ "properties": {
+ "cid": {
+ "type": "string"
+ },
+ "expires_at": {
+ "type": "integer"
+ },
+ "hotkey": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "nonce": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "nonce_hex": {
+ "type": "string"
+ },
+ "signature": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "validator_hotkey_hex": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "cid",
+ "nonce_hex",
+ "expires_at"
+ ],
+ "type": "object"
+ },
+ "ChallengeResponse": {
+ "properties": {
+ "embedding_hash": {
+ "type": "string"
+ },
+ "proof": {
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "ChatHistoryResponse": {
+ "properties": {
+ "messages": {
+ "items": {
+ "additionalProperties": true,
+ "type": "object"
+ },
+ "type": "array"
+ }
+ },
+ "type": "object"
+ },
+ "ChatHistorySaveRequest": {
+ "properties": {
+ "conv_id": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "messages": {
+ "items": {
+ "additionalProperties": true,
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "user_id": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "user_id",
+ "messages"
+ ],
+ "type": "object"
+ },
+ "CommitmentResponse": {
+ "properties": {
+ "built_at": {
+ "type": "number"
+ },
+ "count": {
+ "type": "integer"
+ },
+ "hotkey": {
+ "type": "string"
+ },
+ "root_hex": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "ConversationCreateRequest": {
+ "properties": {
+ "conv_id": {
+ "type": "string"
+ },
+ "title": {
+ "type": "string"
+ },
+ "user_id": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "user_id",
+ "conv_id"
+ ],
+ "type": "object"
+ },
+ "ConversationRenameRequest": {
+ "properties": {
+ "title": {
+ "type": "string"
+ },
+ "user_id": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "user_id",
+ "title"
+ ],
+ "type": "object"
+ },
+ "ConversationsResponse": {
+ "properties": {
+ "conversations": {
+ "items": {
+ "additionalProperties": true,
+ "type": "object"
+ },
+ "type": "array"
+ }
+ },
+ "type": "object"
+ },
+ "DeleteRequest": {
+ "properties": {
+ "hotkey": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_hotkey": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_key": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_sig": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_timestamp_ms": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "nonce": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "signature": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "DeleteResponse": {
+ "properties": {
+ "cid": {
+ "type": "string"
+ },
+ "deleted": {
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ },
+ "ErrorResponse": {
+ "properties": {
+ "error": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "error"
+ ],
+ "type": "object"
+ },
+ "GenericObjectResponse": {
+ "additionalProperties": true,
+ "type": "object"
+ },
+ "HealthResponse": {
+ "properties": {
+ "status": {
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "IngestRequest": {
+ "properties": {
+ "hotkey": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "metadata": {
+ "additionalProperties": true,
+ "type": "object"
+ },
+ "model_version": {
+ "default": "v1",
+ "type": "string"
+ },
+ "namespace": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_hotkey": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_key": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_sig": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_timestamp_ms": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "nonce": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "raw_embedding": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "number"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "signature": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "text": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "IngestResponse": {
+ "properties": {
+ "cid": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "error": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "KeyShareRetrieveRequest": {
+ "properties": {
+ "namespace": {
+ "type": "string"
+ },
+ "namespace_hotkey": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_key": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_sig": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_timestamp_ms": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "namespace"
+ ],
+ "type": "object"
+ },
+ "KeyShareRetrieveResponse": {
+ "properties": {
+ "error": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "share_hex": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "share_index": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "threshold": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "total": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "KeyShareStoreRequest": {
+ "properties": {
+ "namespace": {
+ "type": "string"
+ },
+ "namespace_hotkey": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_key": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_sig": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_timestamp_ms": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "share_hex": {
+ "type": "string"
+ },
+ "share_index": {
+ "type": "integer"
+ },
+ "threshold": {
+ "type": "integer"
+ },
+ "total": {
+ "type": "integer"
+ }
+ },
+ "required": [
+ "namespace",
+ "share_index",
+ "share_hex",
+ "threshold",
+ "total"
+ ],
+ "type": "object"
+ },
+ "KeyShareStoreResponse": {
+ "properties": {
+ "stored": {
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ },
+ "ListRequest": {
+ "properties": {
+ "filter": {
+ "additionalProperties": true,
+ "type": "object"
+ },
+ "limit": {
+ "default": 50,
+ "maximum": 200,
+ "type": "integer"
+ },
+ "namespace": {
+ "default": "__public__",
+ "type": "string"
+ },
+ "namespace_hotkey": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_key": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_sig": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_timestamp_ms": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "offset": {
+ "default": 0,
+ "type": "integer"
+ }
+ },
+ "type": "object"
+ },
+ "ListResponse": {
+ "properties": {
+ "count": {
+ "type": "integer"
+ },
+ "limit": {
+ "type": "integer"
+ },
+ "offset": {
+ "type": "integer"
+ },
+ "records": {
+ "items": {
+ "additionalProperties": true,
+ "type": "object"
+ },
+ "type": "array"
+ }
+ },
+ "type": "object"
+ },
+ "MetagraphResponse": {
+ "properties": {
+ "block": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "neurons": {
+ "items": {
+ "additionalProperties": true,
+ "type": "object"
+ },
+ "type": "array"
+ }
+ },
+ "type": "object"
+ },
+ "NamespaceRequest": {
+ "properties": {
+ "action": {
+ "enum": [
+ "create",
+ "delete",
+ "rotate",
+ "list"
+ ],
+ "type": "string"
+ },
+ "key": {
+ "type": "string"
+ },
+ "namespace": {
+ "type": "string"
+ },
+ "new_key": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "action"
+ ],
+ "type": "object"
+ },
+ "NamespaceResponse": {
+ "additionalProperties": true,
+ "type": "object"
+ },
+ "OkResponse": {
+ "properties": {
+ "ok": {
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ },
+ "OkSavedResponse": {
+ "properties": {
+ "ok": {
+ "type": "boolean"
+ },
+ "saved": {
+ "type": "integer"
+ }
+ },
+ "type": "object"
+ },
+ "PrometheusMetricsResponse": {
+ "type": "string"
+ },
+ "ProveMemoryRequest": {
+ "properties": {
+ "cid": {
+ "type": "string"
+ },
+ "embedding_hash": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "cid",
+ "embedding_hash"
+ ],
+ "type": "object"
+ },
+ "ProveMemoryResponse": {
+ "properties": {
+ "cid": {
+ "type": "string"
+ },
+ "proof": {
+ "additionalProperties": true,
+ "type": "object"
+ },
+ "root_hex": {
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "QueryRequest": {
+ "properties": {
+ "filter": {
+ "additionalProperties": true,
+ "type": "object"
+ },
+ "hotkey": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_hotkey": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_key": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_sig": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "namespace_timestamp_ms": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "nonce": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "query_text": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "query_vector": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "number"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "signature": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "top_k": {
+ "default": 10,
+ "maximum": 100,
+ "minimum": 1,
+ "type": "integer"
+ }
+ },
+ "type": "object"
+ },
+ "QueryResponse": {
+ "properties": {
+ "error": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "latency_ms": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "results": {
+ "items": {
+ "$ref": "#/components/schemas/QueryResult"
+ },
+ "type": "array"
+ }
+ },
+ "type": "object"
+ },
+ "QueryResult": {
+ "properties": {
+ "cid": {
+ "type": "string"
+ },
+ "metadata": {
+ "additionalProperties": true,
+ "type": "object"
+ },
+ "score": {
+ "type": "number"
+ }
+ },
+ "required": [
+ "cid",
+ "score"
+ ],
+ "type": "object"
+ },
+ "RepairRequest": {
+ "properties": {
+ "cid": {
+ "type": "string"
+ },
+ "hotkey": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "nonce": {
+ "anyOf": [
+ {
+ "type": "integer"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "signature": {
+ "anyOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "cid"
+ ],
+ "type": "object"
+ },
+ "RepairResponse": {
+ "properties": {
+ "cid": {
+ "type": "string"
+ },
+ "embedding": {
+ "items": {
+ "type": "number"
+ },
+ "type": "array"
+ },
+ "metadata": {
+ "additionalProperties": true,
+ "type": "object"
+ }
+ },
+ "type": "object"
+ },
+ "RetrieveResponse": {
+ "properties": {
+ "cid": {
+ "type": "string"
+ },
+ "metadata": {
+ "additionalProperties": true,
+ "type": "object"
+ }
+ },
+ "type": "object"
+ },
+ "StatsResponse": {
+ "additionalProperties": true,
+ "type": "object"
+ }
+ },
+ "securitySchemes": {
+ "Sr25519SignedBody": {
+ "description": "Documentation marker for Engram signed-body auth. Clients send hotkey, nonce, and signature in the JSON body; the miner verifies sr25519 signatures with engram.miner.auth.verify_request.",
+ "in": "header",
+ "name": "X-Engram-Signature-Fields-In-Body",
+ "type": "apiKey"
+ }
+ }
+ },
+ "info": {
+ "description": "Machine-readable API contract for Engram miner endpoints. Signed requests use JSON body fields hotkey, nonce, and signature. The signature is sr25519 over '::', where payload_hash is the SHA-256 hash of the canonical JSON body excluding hotkey, nonce, and signature.",
+ "title": "Engram Miner HTTP API",
+ "version": "0.1.0"
+ },
+ "openapi": "3.1.0",
+ "paths": {
+ "/AttestNamespace": {
+ "post": {
+ "operationId": "attest",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/AttestNamespaceRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/AttestNamespaceResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Attest a namespace to a Bittensor hotkey.",
+ "tags": [
+ "Namespaces"
+ ]
+ }
+ },
+ "/ChallengeSynapse": {
+ "post": {
+ "operationId": "challenge",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ChallengeRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ChallengeResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Return a storage proof for a challenged CID.",
+ "tags": [
+ "Proofs"
+ ]
+ }
+ },
+ "/IngestSynapse": {
+ "post": {
+ "operationId": "ingest",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/IngestRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/IngestResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Store text or an embedding and return a CID.",
+ "tags": [
+ "Memory"
+ ]
+ }
+ },
+ "/KeyShareRetrieve": {
+ "post": {
+ "operationId": "key_share_retrieve",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/KeyShareRetrieveRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/KeyShareRetrieveResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Retrieve this miner's Shamir key share for a namespace.",
+ "tags": [
+ "Namespaces"
+ ]
+ }
+ },
+ "/KeyShareSynapse": {
+ "post": {
+ "operationId": "key_share_store",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/KeyShareStoreRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/KeyShareStoreResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Store a Shamir key share for a namespace.",
+ "tags": [
+ "Namespaces"
+ ]
+ }
+ },
+ "/QuerySynapse": {
+ "post": {
+ "operationId": "query",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/QueryRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/QueryResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Search stored vectors by text or embedding.",
+ "tags": [
+ "Memory"
+ ]
+ }
+ },
+ "/RepairSynapse": {
+ "post": {
+ "operationId": "repair_retrieve",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RepairRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RepairResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Return a public embedding for validator repair replication.",
+ "tags": [
+ "Repair"
+ ]
+ }
+ },
+ "/attestation/{namespace}": {
+ "get": {
+ "operationId": "attestation_get",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "namespace",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/AttestationResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Return attestation trust information for a namespace.",
+ "tags": [
+ "Namespaces"
+ ]
+ }
+ },
+ "/chat-history": {
+ "post": {
+ "operationId": "chat_history_post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ChatHistorySaveRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/OkSavedResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Save chat history for a user.",
+ "tags": [
+ "Chat"
+ ]
+ }
+ },
+ "/chat-history/{user_id}": {
+ "get": {
+ "operationId": "chat_history_get",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "user_id",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ChatHistoryResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Load chat history for a user.",
+ "tags": [
+ "Chat"
+ ]
+ }
+ },
+ "/commitment": {
+ "get": {
+ "operationId": "commitment",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/CommitmentResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Return the Merkle root for the miner's memory corpus.",
+ "tags": [
+ "Proofs"
+ ]
+ }
+ },
+ "/conversations": {
+ "post": {
+ "operationId": "conversations_post",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ConversationCreateRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/OkResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Create a conversation.",
+ "tags": [
+ "Chat"
+ ]
+ }
+ },
+ "/conversations/{conv_id}": {
+ "delete": {
+ "operationId": "conversations_delete",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "conv_id",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/OkResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Delete a conversation.",
+ "tags": [
+ "Chat"
+ ]
+ },
+ "patch": {
+ "operationId": "conversations_patch",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "conv_id",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ConversationRenameRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/OkResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Rename a conversation.",
+ "tags": [
+ "Chat"
+ ]
+ }
+ },
+ "/conversations/{user_id}": {
+ "get": {
+ "operationId": "conversations_get",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "user_id",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ConversationsResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "List conversations for a user.",
+ "tags": [
+ "Chat"
+ ]
+ }
+ },
+ "/health": {
+ "get": {
+ "operationId": "health",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/HealthResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Return a minimal liveness status.",
+ "tags": [
+ "Status"
+ ]
+ }
+ },
+ "/list": {
+ "post": {
+ "operationId": "list",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ListRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ListResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "List stored memories with pagination and optional metadata filtering.",
+ "tags": [
+ "Memory"
+ ]
+ }
+ },
+ "/metagraph": {
+ "get": {
+ "operationId": "metagraph",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/MetagraphResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Return a public metagraph snapshot.",
+ "tags": [
+ "Status"
+ ]
+ }
+ },
+ "/metrics": {
+ "get": {
+ "operationId": "metrics",
+ "responses": {
+ "200": {
+ "content": {
+ "text/plain": {
+ "schema": {
+ "type": "string"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Return Prometheus metrics.",
+ "tags": [
+ "Status"
+ ],
+ "x-engram-access": "Restricted to localhost or namespace owner depending on endpoint."
+ }
+ },
+ "/namespace": {
+ "post": {
+ "operationId": "namespace",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/NamespaceRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/NamespaceResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Manage local private namespaces.",
+ "tags": [
+ "Namespaces"
+ ],
+ "x-engram-access": "Restricted to localhost or namespace owner depending on endpoint."
+ }
+ },
+ "/prove-memory": {
+ "post": {
+ "operationId": "prove_memory",
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ProveMemoryRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ProveMemoryResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Return a Merkle inclusion proof for a CID.",
+ "tags": [
+ "Proofs"
+ ]
+ }
+ },
+ "/retrieve/{cid}": {
+ "delete": {
+ "operationId": "delete",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "cid",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DeleteRequest"
+ }
+ }
+ },
+ "required": true
+ },
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/DeleteResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Delete a stored memory by CID.",
+ "tags": [
+ "Memory"
+ ]
+ },
+ "get": {
+ "operationId": "retrieve",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "cid",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/RetrieveResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Retrieve public metadata for a CID.",
+ "tags": [
+ "Memory"
+ ]
+ }
+ },
+ "/stats": {
+ "get": {
+ "operationId": "stats",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/StatsResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Return public miner counters for dashboards.",
+ "tags": [
+ "Status"
+ ]
+ }
+ },
+ "/wallet-stats": {
+ "get": {
+ "operationId": "wallet_stats",
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/GenericObjectResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Return aggregate local wallet activity stats.",
+ "tags": [
+ "Status"
+ ],
+ "x-engram-access": "Restricted to localhost or namespace owner depending on endpoint."
+ }
+ },
+ "/wallet-stats/{hotkey}": {
+ "get": {
+ "operationId": "wallet_stats",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "hotkey",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/GenericObjectResponse"
+ }
+ }
+ },
+ "description": "Success"
+ },
+ "400": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Request failed validation."
+ },
+ "401": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Authentication failed."
+ },
+ "404": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ErrorResponse"
+ }
+ }
+ },
+ "description": "Resource not found."
+ }
+ },
+ "summary": "Return local activity stats for one hotkey.",
+ "tags": [
+ "Status"
+ ],
+ "x-engram-access": "Restricted to localhost or namespace owner depending on endpoint."
+ }
+ }
+ },
+ "servers": [
+ {
+ "description": "Local miner",
+ "url": "http://127.0.0.1:8091"
+ }
+ ],
+ "tags": [
+ {
+ "name": "Memory"
+ },
+ {
+ "name": "Proofs"
+ },
+ {
+ "name": "Namespaces"
+ },
+ {
+ "name": "Repair"
+ },
+ {
+ "name": "Chat"
+ },
+ {
+ "name": "Status"
+ }
+ ],
+ "x-engram-route-count": 26
+}
diff --git a/engram/miner/openapi.py b/engram/miner/openapi.py
new file mode 100644
index 00000000..cc8323cf
--- /dev/null
+++ b/engram/miner/openapi.py
@@ -0,0 +1,677 @@
+"""OpenAPI metadata for the Engram miner HTTP API.
+
+The miner runtime and the generated OpenAPI document both use
+``MINER_HTTP_ROUTES`` so route drift is caught by tests and by the generation
+script's ``--check`` mode.
+"""
+
+from __future__ import annotations
+
+from dataclasses import dataclass
+from typing import Any
+
+
+@dataclass(frozen=True)
+class MinerRoute:
+ method: str
+ path: str
+ handler: str
+ summary: str
+ tag: str
+ request_schema: str | None = None
+ response_schema: str = "ErrorResponse"
+ private: bool = False
+
+
+MINER_HTTP_ROUTES: tuple[MinerRoute, ...] = (
+ MinerRoute(
+ "post",
+ "/IngestSynapse",
+ "handle_ingest",
+ "Store text or an embedding and return a CID.",
+ "Memory",
+ "IngestRequest",
+ "IngestResponse",
+ ),
+ MinerRoute(
+ "post",
+ "/QuerySynapse",
+ "handle_query",
+ "Search stored vectors by text or embedding.",
+ "Memory",
+ "QueryRequest",
+ "QueryResponse",
+ ),
+ MinerRoute(
+ "post",
+ "/ChallengeSynapse",
+ "handle_challenge",
+ "Return a storage proof for a challenged CID.",
+ "Proofs",
+ "ChallengeRequest",
+ "ChallengeResponse",
+ ),
+ MinerRoute(
+ "post",
+ "/namespace",
+ "handle_namespace",
+ "Manage local private namespaces.",
+ "Namespaces",
+ "NamespaceRequest",
+ "NamespaceResponse",
+ private=True,
+ ),
+ MinerRoute(
+ "post",
+ "/AttestNamespace",
+ "handle_attest",
+ "Attest a namespace to a Bittensor hotkey.",
+ "Namespaces",
+ "AttestNamespaceRequest",
+ "AttestNamespaceResponse",
+ ),
+ MinerRoute(
+ "get",
+ "/attestation/{namespace}",
+ "handle_attestation_get",
+ "Return attestation trust information for a namespace.",
+ "Namespaces",
+ None,
+ "AttestationResponse",
+ ),
+ MinerRoute(
+ "get",
+ "/chat-history/{user_id}",
+ "handle_chat_history_get",
+ "Load chat history for a user.",
+ "Chat",
+ None,
+ "ChatHistoryResponse",
+ ),
+ MinerRoute(
+ "post",
+ "/chat-history",
+ "handle_chat_history_post",
+ "Save chat history for a user.",
+ "Chat",
+ "ChatHistorySaveRequest",
+ "OkSavedResponse",
+ ),
+ MinerRoute(
+ "get",
+ "/conversations/{user_id}",
+ "handle_conversations_get",
+ "List conversations for a user.",
+ "Chat",
+ None,
+ "ConversationsResponse",
+ ),
+ MinerRoute(
+ "post",
+ "/conversations",
+ "handle_conversations_post",
+ "Create a conversation.",
+ "Chat",
+ "ConversationCreateRequest",
+ "OkResponse",
+ ),
+ MinerRoute(
+ "patch",
+ "/conversations/{conv_id}",
+ "handle_conversations_patch",
+ "Rename a conversation.",
+ "Chat",
+ "ConversationRenameRequest",
+ "OkResponse",
+ ),
+ MinerRoute(
+ "delete",
+ "/conversations/{conv_id}",
+ "handle_conversations_delete",
+ "Delete a conversation.",
+ "Chat",
+ None,
+ "OkResponse",
+ ),
+ MinerRoute(
+ "get",
+ "/retrieve/{cid}",
+ "handle_retrieve",
+ "Retrieve public metadata for a CID.",
+ "Memory",
+ None,
+ "RetrieveResponse",
+ ),
+ MinerRoute(
+ "delete",
+ "/retrieve/{cid}",
+ "handle_delete",
+ "Delete a stored memory by CID.",
+ "Memory",
+ "DeleteRequest",
+ "DeleteResponse",
+ ),
+ MinerRoute(
+ "post",
+ "/RepairSynapse",
+ "handle_repair_retrieve",
+ "Return a public embedding for validator repair replication.",
+ "Repair",
+ "RepairRequest",
+ "RepairResponse",
+ ),
+ MinerRoute(
+ "post",
+ "/KeyShareSynapse",
+ "handle_key_share_store",
+ "Store a Shamir key share for a namespace.",
+ "Namespaces",
+ "KeyShareStoreRequest",
+ "KeyShareStoreResponse",
+ ),
+ MinerRoute(
+ "post",
+ "/KeyShareRetrieve",
+ "handle_key_share_retrieve",
+ "Retrieve this miner's Shamir key share for a namespace.",
+ "Namespaces",
+ "KeyShareRetrieveRequest",
+ "KeyShareRetrieveResponse",
+ ),
+ MinerRoute(
+ "post",
+ "/list",
+ "handle_list",
+ "List stored memories with pagination and optional metadata filtering.",
+ "Memory",
+ "ListRequest",
+ "ListResponse",
+ ),
+ MinerRoute(
+ "get",
+ "/health",
+ "handle_health",
+ "Return a minimal liveness status.",
+ "Status",
+ None,
+ "HealthResponse",
+ ),
+ MinerRoute(
+ "get",
+ "/stats",
+ "handle_stats",
+ "Return public miner counters for dashboards.",
+ "Status",
+ None,
+ "StatsResponse",
+ ),
+ MinerRoute(
+ "get",
+ "/metagraph",
+ "handle_metagraph",
+ "Return a public metagraph snapshot.",
+ "Status",
+ None,
+ "MetagraphResponse",
+ ),
+ MinerRoute(
+ "get",
+ "/metrics",
+ "handle_metrics",
+ "Return Prometheus metrics.",
+ "Status",
+ None,
+ "PrometheusMetricsResponse",
+ private=True,
+ ),
+ MinerRoute(
+ "get",
+ "/wallet-stats",
+ "handle_wallet_stats",
+ "Return aggregate local wallet activity stats.",
+ "Status",
+ None,
+ "GenericObjectResponse",
+ private=True,
+ ),
+ MinerRoute(
+ "get",
+ "/wallet-stats/{hotkey}",
+ "handle_wallet_stats",
+ "Return local activity stats for one hotkey.",
+ "Status",
+ None,
+ "GenericObjectResponse",
+ private=True,
+ ),
+ MinerRoute(
+ "get",
+ "/commitment",
+ "handle_commitment",
+ "Return the Merkle root for the miner's memory corpus.",
+ "Proofs",
+ None,
+ "CommitmentResponse",
+ ),
+ MinerRoute(
+ "post",
+ "/prove-memory",
+ "handle_prove_memory",
+ "Return a Merkle inclusion proof for a CID.",
+ "Proofs",
+ "ProveMemoryRequest",
+ "ProveMemoryResponse",
+ ),
+)
+
+
+def build_openapi_spec() -> dict[str, Any]:
+ """Return the OpenAPI 3.1 document for the miner HTTP API."""
+ paths: dict[str, dict[str, Any]] = {}
+ for route in MINER_HTTP_ROUTES:
+ operation: dict[str, Any] = {
+ "operationId": _operation_id(route),
+ "summary": route.summary,
+ "tags": [route.tag],
+ "responses": _responses(route.response_schema),
+ }
+ params = _path_parameters(route.path)
+ if params:
+ operation["parameters"] = params
+ if route.request_schema:
+ operation["requestBody"] = {
+ "required": True,
+ "content": {
+ "application/json": {
+ "schema": {"$ref": f"#/components/schemas/{route.request_schema}"}
+ }
+ },
+ }
+ if route.private:
+ operation["x-engram-access"] = (
+ "Restricted to localhost or namespace owner depending on endpoint."
+ )
+ paths.setdefault(route.path, {})[route.method] = operation
+
+ return {
+ "openapi": "3.1.0",
+ "info": {
+ "title": "Engram Miner HTTP API",
+ "version": "0.1.0",
+ "description": (
+ "Machine-readable API contract for Engram miner endpoints. "
+ "Signed requests use JSON body fields hotkey, nonce, and signature. "
+ "The signature is sr25519 over '::', "
+ "where payload_hash is the SHA-256 hash of the canonical JSON body "
+ "excluding hotkey, nonce, and signature."
+ ),
+ },
+ "servers": [{"url": "http://127.0.0.1:8091", "description": "Local miner"}],
+ "tags": [
+ {"name": "Memory"},
+ {"name": "Proofs"},
+ {"name": "Namespaces"},
+ {"name": "Repair"},
+ {"name": "Chat"},
+ {"name": "Status"},
+ ],
+ "paths": paths,
+ "components": {
+ "schemas": _schemas(),
+ "securitySchemes": {
+ "Sr25519SignedBody": {
+ "type": "apiKey",
+ "in": "header",
+ "name": "X-Engram-Signature-Fields-In-Body",
+ "description": (
+ "Documentation marker for Engram signed-body auth. "
+ "Clients send hotkey, nonce, and signature in the JSON body; "
+ "the miner verifies sr25519 signatures with "
+ "engram.miner.auth.verify_request."
+ ),
+ }
+ },
+ },
+ "x-engram-route-count": len(MINER_HTTP_ROUTES),
+ }
+
+
+def _operation_id(route: MinerRoute) -> str:
+ return route.handler.removeprefix("handle_")
+
+
+def _path_parameters(path: str) -> list[dict[str, Any]]:
+ params = []
+ for name in ("namespace", "user_id", "conv_id", "cid", "hotkey"):
+ if "{" + name + "}" in path:
+ params.append({
+ "name": name,
+ "in": "path",
+ "required": True,
+ "schema": {"type": "string"},
+ })
+ return params
+
+
+def _responses(schema_name: str) -> dict[str, Any]:
+ content_type = (
+ "text/plain" if schema_name == "PrometheusMetricsResponse" else "application/json"
+ )
+ schema: dict[str, Any]
+ if schema_name == "PrometheusMetricsResponse":
+ schema = {"type": "string"}
+ else:
+ schema = {"$ref": f"#/components/schemas/{schema_name}"}
+ return {
+ "200": {
+ "description": "Success",
+ "content": {content_type: {"schema": schema}},
+ },
+ "400": {
+ "description": "Request failed validation.",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/ErrorResponse"}
+ }
+ },
+ },
+ "401": {
+ "description": "Authentication failed.",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/ErrorResponse"}
+ }
+ },
+ },
+ "404": {
+ "description": "Resource not found.",
+ "content": {
+ "application/json": {
+ "schema": {"$ref": "#/components/schemas/ErrorResponse"}
+ }
+ },
+ },
+ }
+
+
+def _schemas() -> dict[str, Any]:
+ string_or_null = {"anyOf": [{"type": "string"}, {"type": "null"}]}
+ number_or_null = {"anyOf": [{"type": "number"}, {"type": "null"}]}
+ integer_or_null = {"anyOf": [{"type": "integer"}, {"type": "null"}]}
+ object_schema = {"type": "object", "additionalProperties": True}
+ signed_body = {
+ "hotkey": string_or_null,
+ "nonce": integer_or_null,
+ "signature": string_or_null,
+ }
+ namespace_sig = {
+ "namespace_hotkey": string_or_null,
+ "namespace_sig": string_or_null,
+ "namespace_timestamp_ms": integer_or_null,
+ "namespace_key": string_or_null,
+ }
+
+ return {
+ "ErrorResponse": {
+ "type": "object",
+ "properties": {"error": {"type": "string"}},
+ "required": ["error"],
+ },
+ "IngestRequest": {
+ "type": "object",
+ "properties": {
+ **signed_body,
+ **namespace_sig,
+ "text": string_or_null,
+ "raw_embedding": {
+ "anyOf": [
+ {"type": "array", "items": {"type": "number"}},
+ {"type": "null"},
+ ]
+ },
+ "metadata": object_schema,
+ "model_version": {"type": "string", "default": "v1"},
+ "namespace": string_or_null,
+ },
+ },
+ "IngestResponse": {
+ "type": "object",
+ "properties": {"cid": string_or_null, "error": string_or_null},
+ },
+ "QueryRequest": {
+ "type": "object",
+ "properties": {
+ **signed_body,
+ **namespace_sig,
+ "query_text": string_or_null,
+ "query_vector": {
+ "anyOf": [
+ {"type": "array", "items": {"type": "number"}},
+ {"type": "null"},
+ ]
+ },
+ "top_k": {"type": "integer", "minimum": 1, "maximum": 100, "default": 10},
+ "namespace": string_or_null,
+ "filter": object_schema,
+ },
+ },
+ "QueryResult": {
+ "type": "object",
+ "properties": {
+ "cid": {"type": "string"},
+ "score": {"type": "number"},
+ "metadata": object_schema,
+ },
+ "required": ["cid", "score"],
+ },
+ "QueryResponse": {
+ "type": "object",
+ "properties": {
+ "results": {
+ "type": "array",
+ "items": {"$ref": "#/components/schemas/QueryResult"},
+ },
+ "latency_ms": number_or_null,
+ "error": string_or_null,
+ },
+ },
+ "ChallengeRequest": {
+ "type": "object",
+ "properties": {
+ **signed_body,
+ "cid": {"type": "string"},
+ "nonce_hex": {"type": "string"},
+ "expires_at": {"type": "integer"},
+ "validator_hotkey_hex": string_or_null,
+ },
+ "required": ["cid", "nonce_hex", "expires_at"],
+ },
+ "ChallengeResponse": {
+ "type": "object",
+ "properties": {
+ "embedding_hash": {"type": "string"},
+ "proof": {"type": "string"},
+ },
+ },
+ "NamespaceRequest": {
+ "type": "object",
+ "properties": {
+ "action": {
+ "type": "string",
+ "enum": ["create", "delete", "rotate", "list"],
+ },
+ "namespace": {"type": "string"},
+ "key": {"type": "string"},
+ "new_key": string_or_null,
+ },
+ "required": ["action"],
+ },
+ "NamespaceResponse": {"type": "object", "additionalProperties": True},
+ "AttestNamespaceRequest": {
+ "type": "object",
+ "properties": {
+ "namespace": {"type": "string"},
+ "owner_hotkey": {"type": "string"},
+ "signature": {"type": "string"},
+ "timestamp_ms": {"type": "integer"},
+ },
+ "required": ["namespace", "owner_hotkey", "signature", "timestamp_ms"],
+ },
+ "AttestNamespaceResponse": {
+ "type": "object",
+ "properties": {
+ "ok": {"type": "boolean"},
+ "namespace": {"type": "string"},
+ "trust_tier": {"type": "string"},
+ "stake_tao": {"type": "number"},
+ },
+ },
+ "AttestationResponse": {"type": "object", "additionalProperties": True},
+ "ChatHistoryResponse": {
+ "type": "object",
+ "properties": {"messages": {"type": "array", "items": object_schema}},
+ },
+ "ChatHistorySaveRequest": {
+ "type": "object",
+ "properties": {
+ "user_id": {"type": "string"},
+ "conv_id": string_or_null,
+ "messages": {"type": "array", "items": object_schema},
+ },
+ "required": ["user_id", "messages"],
+ },
+ "OkSavedResponse": {
+ "type": "object",
+ "properties": {"ok": {"type": "boolean"}, "saved": {"type": "integer"}},
+ },
+ "ConversationsResponse": {
+ "type": "object",
+ "properties": {"conversations": {"type": "array", "items": object_schema}},
+ },
+ "ConversationCreateRequest": {
+ "type": "object",
+ "properties": {
+ "user_id": {"type": "string"},
+ "conv_id": {"type": "string"},
+ "title": {"type": "string"},
+ },
+ "required": ["user_id", "conv_id"],
+ },
+ "ConversationRenameRequest": {
+ "type": "object",
+ "properties": {"user_id": {"type": "string"}, "title": {"type": "string"}},
+ "required": ["user_id", "title"],
+ },
+ "OkResponse": {"type": "object", "properties": {"ok": {"type": "boolean"}}},
+ "RetrieveResponse": {
+ "type": "object",
+ "properties": {"cid": {"type": "string"}, "metadata": object_schema},
+ },
+ "DeleteRequest": {
+ "type": "object",
+ "properties": {**signed_body, **namespace_sig},
+ },
+ "DeleteResponse": {
+ "type": "object",
+ "properties": {"deleted": {"type": "boolean"}, "cid": {"type": "string"}},
+ },
+ "RepairRequest": {
+ "type": "object",
+ "properties": {**signed_body, "cid": {"type": "string"}},
+ "required": ["cid"],
+ },
+ "RepairResponse": {
+ "type": "object",
+ "properties": {
+ "cid": {"type": "string"},
+ "embedding": {"type": "array", "items": {"type": "number"}},
+ "metadata": object_schema,
+ },
+ },
+ "KeyShareStoreRequest": {
+ "type": "object",
+ "properties": {
+ **namespace_sig,
+ "namespace": {"type": "string"},
+ "share_index": {"type": "integer"},
+ "share_hex": {"type": "string"},
+ "threshold": {"type": "integer"},
+ "total": {"type": "integer"},
+ },
+ "required": ["namespace", "share_index", "share_hex", "threshold", "total"],
+ },
+ "KeyShareStoreResponse": {"type": "object", "properties": {"stored": {"type": "boolean"}}},
+ "KeyShareRetrieveRequest": {
+ "type": "object",
+ "properties": {**namespace_sig, "namespace": {"type": "string"}},
+ "required": ["namespace"],
+ },
+ "KeyShareRetrieveResponse": {
+ "type": "object",
+ "properties": {
+ "share_index": integer_or_null,
+ "share_hex": string_or_null,
+ "threshold": integer_or_null,
+ "total": integer_or_null,
+ "error": string_or_null,
+ },
+ },
+ "ListRequest": {
+ "type": "object",
+ "properties": {
+ **namespace_sig,
+ "filter": object_schema,
+ "limit": {"type": "integer", "default": 50, "maximum": 200},
+ "offset": {"type": "integer", "default": 0},
+ "namespace": {"type": "string", "default": "__public__"},
+ },
+ },
+ "ListResponse": {
+ "type": "object",
+ "properties": {
+ "records": {"type": "array", "items": object_schema},
+ "count": {"type": "integer"},
+ "offset": {"type": "integer"},
+ "limit": {"type": "integer"},
+ },
+ },
+ "HealthResponse": {
+ "type": "object",
+ "properties": {"status": {"type": "string"}},
+ },
+ "StatsResponse": {"type": "object", "additionalProperties": True},
+ "MetagraphResponse": {
+ "type": "object",
+ "properties": {
+ "neurons": {"type": "array", "items": object_schema},
+ "block": integer_or_null,
+ },
+ },
+ "PrometheusMetricsResponse": {"type": "string"},
+ "GenericObjectResponse": object_schema,
+ "CommitmentResponse": {
+ "type": "object",
+ "properties": {
+ "root_hex": string_or_null,
+ "count": {"type": "integer"},
+ "built_at": {"type": "number"},
+ "hotkey": {"type": "string"},
+ },
+ },
+ "ProveMemoryRequest": {
+ "type": "object",
+ "properties": {
+ "cid": {"type": "string"},
+ "embedding_hash": {"type": "string"},
+ },
+ "required": ["cid", "embedding_hash"],
+ },
+ "ProveMemoryResponse": {
+ "type": "object",
+ "properties": {
+ "root_hex": {"type": "string"},
+ "cid": {"type": "string"},
+ "proof": object_schema,
+ },
+ },
+ }
diff --git a/neurons/miner.py b/neurons/miner.py
index a60c660d..7393dcc2 100644
--- a/neurons/miner.py
+++ b/neurons/miner.py
@@ -45,6 +45,7 @@
from engram.miner.store import build_store
from engram.miner.http_synapses import ingest_synapse_from_body, query_synapse_from_body
from engram.miner.key_share_store import KeyShareStore
+from engram.miner.openapi import MINER_HTTP_ROUTES
from engram.protocol import IngestSynapse, QuerySynapse
from engram.storage.dht import DHTRouter, Peer
from engram.storage.replication import ReplicationManager
@@ -1276,32 +1277,35 @@ async def handle_metrics(req: web.Request) -> web.Response:
# Prevents OOM from oversized request bodies.
_MAX_BODY = int(os.getenv("MINER_MAX_BODY_BYTES", str(10 * 1024 * 1024)))
app = web.Application(client_max_size=_MAX_BODY)
- app.router.add_post("/IngestSynapse", handle_ingest)
- app.router.add_post("/QuerySynapse", handle_query)
- app.router.add_post("/ChallengeSynapse", handle_challenge)
- app.router.add_post("/namespace", handle_namespace)
- app.router.add_post("/AttestNamespace", handle_attest)
- app.router.add_get("/attestation/{namespace}", handle_attestation_get)
- app.router.add_get("/chat-history/{user_id}", handle_chat_history_get)
- app.router.add_post("/chat-history", handle_chat_history_post)
- app.router.add_get("/conversations/{user_id}", handle_conversations_get)
- app.router.add_post("/conversations", handle_conversations_post)
- app.router.add_patch("/conversations/{conv_id}", handle_conversations_patch)
- app.router.add_delete("/conversations/{conv_id}", handle_conversations_delete)
- app.router.add_get("/retrieve/{cid}", handle_retrieve)
- app.router.add_delete("/retrieve/{cid}", handle_delete)
- app.router.add_post("/RepairSynapse", handle_repair_retrieve)
- app.router.add_post("/KeyShareSynapse", handle_key_share_store)
- app.router.add_post("/KeyShareRetrieve", handle_key_share_retrieve)
- app.router.add_post("/list", handle_list)
- app.router.add_get("/health", handle_health)
- app.router.add_get("/stats", handle_stats)
- app.router.add_get("/metagraph", handle_metagraph)
- app.router.add_get("/metrics", handle_metrics)
- app.router.add_get("/wallet-stats", handle_wallet_stats)
- app.router.add_get("/wallet-stats/{hotkey}", handle_wallet_stats)
- app.router.add_get("/commitment", handle_commitment)
- app.router.add_post("/prove-memory", handle_prove_memory)
+ handlers = {
+ "handle_ingest": handle_ingest,
+ "handle_query": handle_query,
+ "handle_challenge": handle_challenge,
+ "handle_namespace": handle_namespace,
+ "handle_attest": handle_attest,
+ "handle_attestation_get": handle_attestation_get,
+ "handle_chat_history_get": handle_chat_history_get,
+ "handle_chat_history_post": handle_chat_history_post,
+ "handle_conversations_get": handle_conversations_get,
+ "handle_conversations_post": handle_conversations_post,
+ "handle_conversations_patch": handle_conversations_patch,
+ "handle_conversations_delete": handle_conversations_delete,
+ "handle_retrieve": handle_retrieve,
+ "handle_delete": handle_delete,
+ "handle_repair_retrieve": handle_repair_retrieve,
+ "handle_key_share_store": handle_key_share_store,
+ "handle_key_share_retrieve": handle_key_share_retrieve,
+ "handle_list": handle_list,
+ "handle_health": handle_health,
+ "handle_stats": handle_stats,
+ "handle_metagraph": handle_metagraph,
+ "handle_metrics": handle_metrics,
+ "handle_wallet_stats": handle_wallet_stats,
+ "handle_commitment": handle_commitment,
+ "handle_prove_memory": handle_prove_memory,
+ }
+ for route in MINER_HTTP_ROUTES:
+ getattr(app.router, f"add_{route.method}")(route.path, handlers[route.handler])
runner = web.AppRunner(app, keepalive_timeout=15)
await runner.setup()
diff --git a/scripts/generate_openapi.py b/scripts/generate_openapi.py
new file mode 100644
index 00000000..ff31bea1
--- /dev/null
+++ b/scripts/generate_openapi.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python3
+"""Generate and verify the Engram miner OpenAPI document."""
+
+from __future__ import annotations
+
+import argparse
+import json
+import sys
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[1]
+sys.path.insert(0, str(ROOT))
+
+from engram.miner.openapi import build_openapi_spec
+
+
+OPENAPI_PATH = ROOT / "docs" / "openapi.json"
+REDOC_PATH = ROOT / "docs" / "miner-openapi.html"
+
+
+def _render_json() -> str:
+ return json.dumps(build_openapi_spec(), indent=2, sort_keys=True) + "\n"
+
+
+def _render_redoc() -> str:
+ return """
+
+
+
+
+ Engram Miner HTTP API
+
+
+
+
+
+
+"""
+
+
+def write_files() -> None:
+ OPENAPI_PATH.parent.mkdir(parents=True, exist_ok=True)
+ OPENAPI_PATH.write_text(_render_json(), encoding="utf-8")
+ REDOC_PATH.write_text(_render_redoc(), encoding="utf-8")
+
+
+def check_files() -> list[str]:
+ expected = {
+ OPENAPI_PATH: _render_json(),
+ REDOC_PATH: _render_redoc(),
+ }
+ stale = []
+ for path, content in expected.items():
+ if not path.exists() or path.read_text(encoding="utf-8") != content:
+ stale.append(str(path.relative_to(ROOT)))
+ return stale
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--check", action="store_true", help="fail if generated files are stale")
+ args = parser.parse_args()
+
+ if args.check:
+ stale = check_files()
+ if stale:
+ print("OpenAPI files are stale:")
+ for path in stale:
+ print(f"- {path}")
+ print("Run: python scripts/generate_openapi.py")
+ return 1
+ print("OpenAPI files are up to date")
+ return 0
+
+ write_files()
+ print(f"Wrote {OPENAPI_PATH.relative_to(ROOT)}")
+ print(f"Wrote {REDOC_PATH.relative_to(ROOT)}")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/tests/test_openapi.py b/tests/test_openapi.py
new file mode 100644
index 00000000..a7c737ad
--- /dev/null
+++ b/tests/test_openapi.py
@@ -0,0 +1,52 @@
+"""Tests for generated miner OpenAPI metadata."""
+
+import json
+import subprocess
+import sys
+from pathlib import Path
+
+from engram.miner.openapi import MINER_HTTP_ROUTES, build_openapi_spec
+
+
+ROOT = Path(__file__).resolve().parents[1]
+
+
+def test_route_registry_has_unique_method_path_pairs():
+ pairs = [(route.method, route.path) for route in MINER_HTTP_ROUTES]
+ assert len(pairs) == len(set(pairs))
+
+
+def test_openapi_paths_match_route_registry():
+ spec = build_openapi_spec()
+ spec_pairs = {
+ (method, path)
+ for path, methods in spec["paths"].items()
+ for method in methods
+ }
+ route_pairs = {(route.method, route.path) for route in MINER_HTTP_ROUTES}
+ assert spec_pairs == route_pairs
+ assert spec["x-engram-route-count"] == len(MINER_HTTP_ROUTES)
+
+
+def test_openapi_documents_signed_body_auth():
+ spec = build_openapi_spec()
+ auth = spec["components"]["securitySchemes"]["Sr25519SignedBody"]
+ assert "sr25519" in auth["description"]
+ ingest_fields = spec["components"]["schemas"]["IngestRequest"]["properties"]
+ assert {"hotkey", "nonce", "signature"}.issubset(ingest_fields)
+
+
+def test_generated_openapi_file_is_current():
+ expected = json.dumps(build_openapi_spec(), indent=2, sort_keys=True) + "\n"
+ assert (ROOT / "docs" / "openapi.json").read_text(encoding="utf-8") == expected
+
+
+def test_generate_openapi_check_mode_passes():
+ result = subprocess.run(
+ [sys.executable, "scripts/generate_openapi.py", "--check"],
+ cwd=ROOT,
+ text=True,
+ capture_output=True,
+ check=False,
+ )
+ assert result.returncode == 0, result.stdout + result.stderr