diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 00000000..aa1b431b
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,16 @@
+
+
+
+ Engram Miner API Documentation
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/openapi.yaml b/docs/openapi.yaml
new file mode 100644
index 00000000..16c2c568
--- /dev/null
+++ b/docs/openapi.yaml
@@ -0,0 +1,1256 @@
+openapi: "3.0.3"
+info:
+ title: Engram Miner HTTP API
+ version: "1.0.0"
+ description: |
+ Machine-readable API specification for the Engram subnet miner HTTP endpoints.
+
+ The miner exposes a REST-like JSON API for memory ingestion, semantic search,
+ storage-proof challenges, key-share management, chat history, namespace
+ management, and operational metrics.
+
+ ## Authentication
+
+ Private namespace operations require **sr25519 signature-based auth**:
+ - `namespace_hotkey`: Bittensor SS58 hotkey that owns the namespace
+ - `namespace_sig`: sr25519 hex signature over `engram-ns:{namespace}:{namespace_timestamp_ms}`
+ - `namespace_timestamp_ms`: Unix ms timestamp (±60s replay window)
+
+ Legacy `namespace_key` auth is deprecated but still supported for backward compatibility.
+
+ ## Localhost-Only Endpoints
+
+ Some admin endpoints (`/namespace`, `/wallet-stats`) are restricted to loopback
+ connections (127.0.0.1) for security.
+ license:
+ name: MIT
+ url: https://opensource.org/licenses/MIT
+ contact:
+ name: Engram Subnet
+ url: https://github.com/cxmplex/Engram
+
+servers:
+ - url: http://localhost:{port}
+ description: Local miner instance
+ variables:
+ port:
+ default: "8091"
+ description: Miner HTTP port (MINER_PORT env var)
+
+tags:
+ - name: Synapse
+ description: Bittensor synapse endpoints (ingest, query, challenge, repair)
+ - name: Memory
+ description: Direct memory retrieval, listing, and deletion
+ - name: Namespace
+ description: Namespace management, attestation, and trust tiers
+ - name: KeyShare
+ description: Shamir secret key-share storage and retrieval
+ - name: Chat
+ description: Chat history and conversation management
+ - name: Operational
+ description: Health, stats, metrics, metagraph, commitment, and proofs
+
+components:
+ schemas:
+ NamespaceAuth:
+ type: object
+ description: sr25519 signature-based namespace authentication fields
+ properties:
+ namespace:
+ type: string
+ description: Private collection name
+ namespace_hotkey:
+ type: string
+ description: Bittensor SS58 hotkey that owns this namespace
+ namespace_sig:
+ type: string
+ description: "sr25519 hex signature over 'engram-ns:{namespace}:{namespace_timestamp_ms}'"
+ namespace_timestamp_ms:
+ type: integer
+ format: int64
+ description: Unix ms timestamp for replay prevention (±60s window)
+
+ IngestRequest:
+ type: object
+ description: Ingest a memory into the miner's vector store
+ properties:
+ text:
+ type: string
+ nullable: true
+ description: Raw text to embed and store. Mutually exclusive with raw_embedding.
+ raw_embedding:
+ type: array
+ items:
+ type: number
+ format: float
+ nullable: true
+ description: Pre-computed embedding vector. Skips embedding on the miner.
+ metadata:
+ type: object
+ additionalProperties: true
+ description: Arbitrary key-value metadata stored alongside the vector
+ model_version:
+ type: string
+ default: v1
+ description: Subnet model epoch version for CID generation
+ namespace:
+ type: string
+ nullable: true
+ namespace_hotkey:
+ type: string
+ nullable: true
+ namespace_sig:
+ type: string
+ nullable: true
+ namespace_timestamp_ms:
+ type: integer
+ format: int64
+ nullable: true
+ namespace_key:
+ type: string
+ nullable: true
+ deprecated: true
+ description: "[Deprecated] Use namespace_sig instead"
+
+ IngestResponse:
+ type: object
+ properties:
+ cid:
+ type: string
+ description: Content identifier for the stored memory
+ error:
+ type: string
+ nullable: true
+
+ QueryRequest:
+ type: object
+ description: Semantic search over stored memories
+ properties:
+ query_text:
+ type: string
+ nullable: true
+ description: Natural language query (will be embedded by miner)
+ query_vector:
+ type: array
+ items:
+ type: number
+ format: float
+ nullable: true
+ description: Pre-computed query embedding vector
+ top_k:
+ type: integer
+ default: 10
+ minimum: 1
+ maximum: 100
+ description: Number of results to return
+ namespace:
+ type: string
+ nullable: true
+ namespace_hotkey:
+ type: string
+ nullable: true
+ namespace_sig:
+ type: string
+ nullable: true
+ namespace_timestamp_ms:
+ type: integer
+ format: int64
+ nullable: true
+ namespace_key:
+ type: string
+ nullable: true
+ deprecated: true
+
+ QueryResult:
+ type: object
+ properties:
+ cid:
+ type: string
+ score:
+ type: number
+ format: float
+ metadata:
+ type: object
+ additionalProperties: true
+
+ QueryResponse:
+ type: object
+ properties:
+ results:
+ type: array
+ items:
+ $ref: "#/components/schemas/QueryResult"
+ latency_ms:
+ type: number
+ format: float
+ nullable: true
+ error:
+ type: string
+ nullable: true
+
+ ChallengeRequest:
+ type: object
+ required: [cid, nonce_hex, expires_at]
+ properties:
+ cid:
+ type: string
+ description: CID the miner is challenged to prove storage of
+ nonce_hex:
+ type: string
+ description: 32-byte random nonce as hex string
+ expires_at:
+ type: integer
+ format: int64
+ description: Unix timestamp after which the proof is invalid
+
+ ChallengeResponse:
+ type: object
+ properties:
+ embedding_hash:
+ type: string
+ nullable: true
+ description: SHA-256 of the stored embedding bytes (hex)
+ proof:
+ type: string
+ nullable: true
+ description: HMAC-SHA256(nonce || embedding_hash) proving possession
+ error:
+ type: string
+ nullable: true
+
+ RepairRequest:
+ type: object
+ required: [cid]
+ description: Request full embedding for replication/repair (network auth required)
+ properties:
+ cid:
+ type: string
+ description: CID to retrieve full embedding for
+
+ RepairResponse:
+ type: object
+ properties:
+ cid:
+ type: string
+ embedding:
+ type: array
+ items:
+ type: number
+ format: float
+ metadata:
+ type: object
+ additionalProperties: true
+ error:
+ type: string
+ nullable: true
+
+ KeyShareDepositRequest:
+ type: object
+ required: [namespace, share_index, share_hex, threshold, total]
+ properties:
+ namespace:
+ type: string
+ share_index:
+ type: integer
+ minimum: 1
+ description: 1-based share index
+ share_hex:
+ type: string
+ description: Hex-encoded share bytes
+ threshold:
+ type: integer
+ minimum: 1
+ description: Minimum shares needed to reconstruct (k)
+ total:
+ type: integer
+ minimum: 1
+ description: Total shares created (n)
+ namespace_hotkey:
+ type: string
+ nullable: true
+ namespace_sig:
+ type: string
+ nullable: true
+ namespace_timestamp_ms:
+ type: integer
+ format: int64
+ nullable: true
+
+ KeyShareDepositResponse:
+ type: object
+ properties:
+ stored:
+ type: boolean
+ error:
+ type: string
+ nullable: true
+
+ KeyShareRetrieveRequest:
+ type: object
+ required: [namespace]
+ properties:
+ namespace:
+ type: string
+ namespace_hotkey:
+ type: string
+ nullable: true
+ namespace_sig:
+ type: string
+ nullable: true
+ namespace_timestamp_ms:
+ type: integer
+ format: int64
+ nullable: true
+
+ KeyShareRetrieveResponse:
+ type: object
+ properties:
+ share_index:
+ type: integer
+ nullable: true
+ share_hex:
+ type: string
+ nullable: true
+ threshold:
+ type: integer
+ nullable: true
+ total:
+ type: integer
+ nullable: true
+ error:
+ type: string
+ nullable: true
+
+ NamespaceManageRequest:
+ type: object
+ required: [action, namespace]
+ description: "Localhost-only namespace management (create/delete/rotate)"
+ properties:
+ action:
+ type: string
+ enum: [create, delete, rotate]
+ description: "Action to perform"
+ namespace:
+ type: string
+ key:
+ type: string
+ description: Current key (for delete/rotate)
+ new_key:
+ type: string
+ nullable: true
+ description: New key (for rotate action only)
+
+ AttestNamespaceRequest:
+ type: object
+ required: [namespace, owner_hotkey, signature, timestamp_ms]
+ properties:
+ namespace:
+ type: string
+ owner_hotkey:
+ type: string
+ description: Bittensor SS58 hotkey
+ signature:
+ type: string
+ description: sr25519 hex signature
+ timestamp_ms:
+ type: integer
+ format: int64
+
+ AttestNamespaceResponse:
+ type: object
+ properties:
+ attested:
+ type: boolean
+ namespace:
+ type: string
+ trust_tier:
+ type: string
+ enum: [anonymous, registered, staked, validator]
+ stake_tao:
+ type: number
+ format: float
+ error:
+ type: string
+ nullable: true
+
+ AttestationInfo:
+ type: object
+ properties:
+ namespace:
+ type: string
+ owner_hotkey:
+ type: string
+ trust_tier:
+ type: string
+ enum: [anonymous, registered, staked, validator]
+ stake_tao:
+ type: number
+ format: float
+ attested_at:
+ type: string
+ format: date-time
+ attested:
+ type: boolean
+
+ ChatMessage:
+ type: object
+ properties:
+ role:
+ type: string
+ enum: [user, assistant, system]
+ content:
+ type: string
+ timestamp:
+ type: string
+ format: date-time
+
+ ListMemoriesRequest:
+ type: object
+ properties:
+ filter:
+ type: object
+ additionalProperties: true
+ description: Metadata key/value pairs (AND match)
+ limit:
+ type: integer
+ default: 50
+ minimum: 1
+ maximum: 200
+ offset:
+ type: integer
+ default: 0
+ minimum: 0
+ namespace:
+ type: string
+ nullable: true
+ namespace_hotkey:
+ type: string
+ nullable: true
+ namespace_sig:
+ type: string
+ nullable: true
+ namespace_timestamp_ms:
+ type: integer
+ format: int64
+ nullable: true
+
+ CommitmentResponse:
+ type: object
+ properties:
+ root_hex:
+ type: string
+ description: Merkle root hex of the full memory corpus
+ count:
+ type: integer
+ description: Number of memories in the tree
+ built_at:
+ type: string
+ format: date-time
+ hotkey:
+ type: string
+ description: Miner SS58 hotkey
+
+ ProveMemoryRequest:
+ type: object
+ required: [cid, embedding_hash]
+ properties:
+ cid:
+ type: string
+ embedding_hash:
+ type: string
+ description: 64-char hex SHA-256 of the embedding
+
+ ProveMemoryResponse:
+ type: object
+ properties:
+ proof:
+ type: object
+ description: Merkle inclusion proof (verifiable offline with engram_core)
+ root_hex:
+ type: string
+ verified:
+ type: boolean
+ error:
+ type: string
+ nullable: true
+
+ StatsResponse:
+ type: object
+ properties:
+ total_memories:
+ type: integer
+ total_queries:
+ type: integer
+ p50_latency_ms:
+ type: number
+ format: float
+ nullable: true
+ proof_rate:
+ type: number
+ format: float
+ nullable: true
+ uptime_pct:
+ type: number
+ format: float
+ block:
+ type: integer
+ nullable: true
+
+ MetagraphNeuron:
+ type: object
+ properties:
+ uid:
+ type: integer
+ hotkey:
+ type: string
+ ip:
+ type: string
+ port:
+ type: integer
+ incentive:
+ type: number
+ format: float
+
+ HealthResponse:
+ type: object
+ properties:
+ status:
+ type: string
+ example: ok
+ version:
+ type: string
+ uptime_seconds:
+ type: number
+ format: float
+
+ ErrorResponse:
+ type: object
+ properties:
+ error:
+ type: string
+
+paths:
+ # ─── Synapse Endpoints ─────────────────────────────────────────────────────
+ /IngestSynapse:
+ post:
+ tags: [Synapse]
+ summary: Ingest a memory (Bittensor synapse)
+ description: |
+ Store text or a pre-computed embedding in the miner's vector database.
+ Returns a content identifier (CID) on success.
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/IngestRequest"
+ responses:
+ "200":
+ description: Memory stored successfully
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/IngestResponse"
+ "400":
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ErrorResponse"
+
+ /QuerySynapse:
+ post:
+ tags: [Synapse]
+ summary: Semantic search (Bittensor synapse)
+ description: |
+ Perform approximate nearest-neighbor search over stored memories.
+ Returns top-K results ranked by cosine similarity.
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/QueryRequest"
+ responses:
+ "200":
+ description: Search results
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/QueryResponse"
+ "400":
+ description: Invalid request
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ErrorResponse"
+
+ /ChallengeSynapse:
+ post:
+ tags: [Synapse]
+ summary: Storage proof challenge (Bittensor synapse)
+ description: |
+ Validator issues a challenge to prove the miner holds a specific CID.
+ Miner returns HMAC proof over the stored embedding hash.
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ChallengeRequest"
+ responses:
+ "200":
+ description: Proof response
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ChallengeResponse"
+ "404":
+ description: CID not found
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ErrorResponse"
+
+ /RepairSynapse:
+ post:
+ tags: [Synapse]
+ summary: Repair/replication data retrieval
+ description: |
+ Returns full embedding for a CID so the validator can copy it to
+ under-replicated miners. Requires network auth (validator signature).
+ Only returns public namespace memories.
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/RepairRequest"
+ responses:
+ "200":
+ description: Full embedding data for replication
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/RepairResponse"
+ "401":
+ description: Network auth failed
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ErrorResponse"
+ "404":
+ description: CID not found or not in public namespace
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ErrorResponse"
+
+ # ─── Namespace Management ──────────────────────────────────────────────────
+ /namespace:
+ post:
+ tags: [Namespace]
+ summary: Manage namespaces (localhost only)
+ description: |
+ Create, delete, or rotate keys for namespaces.
+ **Restricted to loopback connections (127.0.0.1) only.**
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/NamespaceManageRequest"
+ responses:
+ "200":
+ description: Action completed
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ ok:
+ type: boolean
+ namespace:
+ type: string
+ "403":
+ description: Forbidden (non-loopback connection)
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ErrorResponse"
+
+ /AttestNamespace:
+ post:
+ tags: [Namespace]
+ summary: Attest a namespace to a Bittensor hotkey
+ description: |
+ Bind a namespace to a Bittensor hotkey with sr25519 signature proof.
+ The on-chain stake of the hotkey determines the trust tier.
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/AttestNamespaceRequest"
+ responses:
+ "200":
+ description: Attestation recorded
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/AttestNamespaceResponse"
+ "400":
+ description: Invalid signature or missing fields
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ErrorResponse"
+
+ /attestation/{namespace}:
+ get:
+ tags: [Namespace]
+ summary: Get namespace attestation info
+ description: |
+ Returns the trust tier and attestation details for a namespace.
+ Returns trust_tier "anonymous" if not attested.
+ parameters:
+ - name: namespace
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ description: Attestation info
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/AttestationInfo"
+
+ # ─── Memory CRUD ───────────────────────────────────────────────────────────
+ /retrieve/{cid}:
+ get:
+ tags: [Memory]
+ summary: Retrieve a memory by CID
+ description: Returns the stored memory record for a given content identifier.
+ parameters:
+ - name: cid
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ description: Memory record
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ cid:
+ type: string
+ metadata:
+ type: object
+ additionalProperties: true
+ "404":
+ description: CID not found
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ErrorResponse"
+ delete:
+ tags: [Memory]
+ summary: Delete a memory by CID
+ description: |
+ Delete a stored memory. For private namespaces, requires namespace
+ ownership proof via request body.
+ parameters:
+ - name: cid
+ in: path
+ required: true
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ namespace:
+ type: string
+ nullable: true
+ namespace_hotkey:
+ type: string
+ nullable: true
+ namespace_sig:
+ type: string
+ nullable: true
+ namespace_timestamp_ms:
+ type: integer
+ format: int64
+ nullable: true
+ responses:
+ "200":
+ description: Memory deleted
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ deleted:
+ type: boolean
+ cid:
+ type: string
+ "403":
+ description: Namespace auth failed
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ErrorResponse"
+ "404":
+ description: CID not found
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ErrorResponse"
+
+ /list:
+ post:
+ tags: [Memory]
+ summary: List memories with optional filters
+ description: |
+ List stored memories with pagination and metadata filtering.
+ Supports namespace scoping with auth for private namespaces.
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ListMemoriesRequest"
+ responses:
+ "200":
+ description: Paginated list of memories
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ records:
+ type: array
+ items:
+ type: object
+ properties:
+ cid:
+ type: string
+ metadata:
+ type: object
+ additionalProperties: true
+ count:
+ type: integer
+ offset:
+ type: integer
+ limit:
+ type: integer
+
+ # ─── KeyShare ──────────────────────────────────────────────────────────────
+ /KeyShareSynapse:
+ post:
+ tags: [KeyShare]
+ summary: Deposit a Shamir key share
+ description: |
+ Deposit one Shamir secret share with the miner for threshold decryption.
+ Requires sr25519 namespace ownership proof.
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/KeyShareDepositRequest"
+ responses:
+ "200":
+ description: Share stored
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/KeyShareDepositResponse"
+ "403":
+ description: Namespace auth failed
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ErrorResponse"
+
+ /KeyShareRetrieve:
+ post:
+ tags: [KeyShare]
+ summary: Retrieve a Shamir key share
+ description: |
+ Retrieve the stored key share for a namespace.
+ Requires namespace ownership verification.
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/KeyShareRetrieveRequest"
+ responses:
+ "200":
+ description: Share retrieved
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/KeyShareRetrieveResponse"
+ "403":
+ description: Namespace auth failed
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ErrorResponse"
+ "404":
+ description: No share found for namespace
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ErrorResponse"
+
+ # ─── Chat ──────────────────────────────────────────────────────────────────
+ /chat-history/{user_id}:
+ get:
+ tags: [Chat]
+ summary: Get chat history for a user
+ description: Load a user's chat history, optionally filtered by conversation ID.
+ parameters:
+ - name: user_id
+ in: path
+ required: true
+ schema:
+ type: string
+ - name: conv_id
+ in: query
+ required: false
+ schema:
+ type: string
+ description: Optional conversation ID filter
+ responses:
+ "200":
+ description: Chat messages
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ messages:
+ type: array
+ items:
+ $ref: "#/components/schemas/ChatMessage"
+
+ /chat-history:
+ post:
+ tags: [Chat]
+ summary: Save chat messages
+ description: Append chat messages to a user's conversation history.
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required: [user_id, messages]
+ properties:
+ user_id:
+ type: string
+ conv_id:
+ type: string
+ nullable: true
+ messages:
+ type: array
+ items:
+ $ref: "#/components/schemas/ChatMessage"
+ responses:
+ "200":
+ description: Messages saved
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ type: string
+ example: ok
+
+ /conversations/{user_id}:
+ get:
+ tags: [Chat]
+ summary: List conversations for a user
+ parameters:
+ - name: user_id
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ description: Conversation list
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ conversations:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: string
+ title:
+ type: string
+ created_at:
+ type: string
+ format: date-time
+
+ /conversations:
+ post:
+ tags: [Chat]
+ summary: Create a new conversation
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ required: [user_id]
+ properties:
+ user_id:
+ type: string
+ title:
+ type: string
+ responses:
+ "200":
+ description: Conversation created
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ ok:
+ type: boolean
+ conv_id:
+ type: string
+
+ /conversations/{conv_id}:
+ patch:
+ tags: [Chat]
+ summary: Update a conversation (rename)
+ parameters:
+ - name: conv_id
+ in: path
+ required: true
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ title:
+ type: string
+ responses:
+ "200":
+ description: Conversation updated
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ ok:
+ type: boolean
+ delete:
+ tags: [Chat]
+ summary: Delete a conversation
+ parameters:
+ - name: conv_id
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ description: Conversation deleted
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ ok:
+ type: boolean
+
+ # ─── Operational ───────────────────────────────────────────────────────────
+ /health:
+ get:
+ tags: [Operational]
+ summary: Health check
+ description: Returns miner health status, version, and uptime.
+ responses:
+ "200":
+ description: Healthy
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/HealthResponse"
+
+ /stats:
+ get:
+ tags: [Operational]
+ summary: Public statistics
+ description: |
+ Rich counters for the dashboard: total memories, queries, latency,
+ proof rate, uptime, and current block height.
+ responses:
+ "200":
+ description: Miner statistics
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/StatsResponse"
+
+ /metagraph:
+ get:
+ tags: [Operational]
+ summary: Metagraph snapshot
+ description: Returns all registered neurons for the subnet leaderboard.
+ responses:
+ "200":
+ description: Neuron list
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ neurons:
+ type: array
+ items:
+ $ref: "#/components/schemas/MetagraphNeuron"
+ block:
+ type: integer
+
+ /metrics:
+ get:
+ tags: [Operational]
+ summary: Prometheus metrics
+ description: Returns Prometheus-formatted metrics for monitoring.
+ responses:
+ "200":
+ description: Prometheus metrics
+ content:
+ text/plain:
+ schema:
+ type: string
+
+ /wallet-stats:
+ get:
+ tags: [Operational]
+ summary: Wallet statistics summary (localhost only)
+ description: |
+ Returns aggregated wallet activity data. Restricted to loopback only.
+ Without a hotkey parameter, returns summary of all wallets.
+ responses:
+ "200":
+ description: Wallet stats summary
+ content:
+ application/json:
+ schema:
+ type: object
+ additionalProperties: true
+ "403":
+ description: Forbidden (non-loopback)
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ErrorResponse"
+
+ /wallet-stats/{hotkey}:
+ get:
+ tags: [Operational]
+ summary: Wallet statistics for a specific hotkey (localhost only)
+ description: Returns memory count and namespace list for a given hotkey.
+ parameters:
+ - name: hotkey
+ in: path
+ required: true
+ schema:
+ type: string
+ description: Bittensor SS58 hotkey
+ responses:
+ "200":
+ description: Wallet statistics
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ hotkey:
+ type: string
+ total_memories:
+ type: integer
+ namespaces:
+ type: array
+ items:
+ type: string
+ "403":
+ description: Forbidden (non-loopback)
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ErrorResponse"
+
+ /commitment:
+ get:
+ tags: [Operational]
+ summary: Merkle commitment root
+ description: |
+ Returns the Merkle root of this miner's full memory corpus.
+ AI agents and validators can use this root to verify that a specific
+ memory is genuinely stored without downloading the full index.
+ responses:
+ "200":
+ description: Commitment data
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CommitmentResponse"
+
+ /prove-memory:
+ post:
+ tags: [Operational]
+ summary: Merkle inclusion proof for a CID
+ description: |
+ Returns a Merkle inclusion proof for one CID. AI agents use this
+ to verify a specific memory is intact without fetching the entire store.
+ Proof is verifiable offline with `engram_core.verify_inclusion()`.
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ProveMemoryRequest"
+ responses:
+ "200":
+ description: Inclusion proof
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ProveMemoryResponse"
+ "400":
+ description: Missing cid or embedding_hash
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ErrorResponse"
+ "404":
+ description: CID not found in commitment tree
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ErrorResponse"
diff --git a/scripts/check_openapi_sync.py b/scripts/check_openapi_sync.py
new file mode 100755
index 00000000..72e9b7c5
--- /dev/null
+++ b/scripts/check_openapi_sync.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+"""
+CI check: verify that docs/openapi.yaml covers all HTTP routes defined in neurons/miner.py.
+
+This script parses the miner source to extract route paths registered via
+`app.router.add_*()` calls, then checks that each path exists in the OpenAPI spec.
+
+Usage:
+ python scripts/check_openapi_sync.py
+
+Exit codes:
+ 0 — all routes are documented
+ 1 — missing routes detected
+"""
+
+from __future__ import annotations
+
+import re
+import sys
+from pathlib import Path
+
+try:
+ import yaml
+except ImportError:
+ # Fallback: just check file exists
+ print("⚠️ PyYAML not installed — skipping deep validation")
+ spec_path = Path(__file__).resolve().parent.parent / "docs" / "openapi.yaml"
+ if spec_path.exists():
+ print(f"✅ OpenAPI spec exists: {spec_path}")
+ sys.exit(0)
+ else:
+ print(f"❌ OpenAPI spec not found: {spec_path}")
+ sys.exit(1)
+
+ROOT = Path(__file__).resolve().parent.parent
+MINER_PATH = ROOT / "neurons" / "miner.py"
+SPEC_PATH = ROOT / "docs" / "openapi.yaml"
+
+
+def extract_routes_from_miner(source: str) -> set[str]:
+ """Extract route paths from app.router.add_* calls."""
+ # Matches patterns like: app.router.add_post("/IngestSynapse", ...)
+ # and: app.router.add_get("/health", ...)
+ pattern = r'app\.router\.add_(?:get|post|put|delete|patch)\(\s*"([^"]+)"'
+ routes = set(re.findall(pattern, source))
+ return routes
+
+
+def extract_paths_from_spec(spec: dict) -> set[str]:
+ """Extract all paths from the OpenAPI spec, normalizing path params."""
+ paths = set()
+ for path in spec.get("paths", {}):
+ # Normalize OpenAPI path params {param} for comparison
+ # e.g., /retrieve/{cid} → /retrieve/{cid}
+ paths.add(path)
+ return paths
+
+
+def normalize_route(route: str) -> str:
+ """Normalize a miner route for comparison with OpenAPI paths.
+
+ Converts aiohttp-style path params like /{cid} to OpenAPI style.
+ aiohttp uses {name} already, so mostly a pass-through.
+ """
+ return route
+
+
+def main() -> int:
+ if not MINER_PATH.exists():
+ print(f"❌ Miner source not found: {MINER_PATH}")
+ return 1
+
+ if not SPEC_PATH.exists():
+ print(f"❌ OpenAPI spec not found: {SPEC_PATH}")
+ return 1
+
+ miner_source = MINER_PATH.read_text()
+ miner_routes = extract_routes_from_miner(miner_source)
+
+ with open(SPEC_PATH) as f:
+ spec = yaml.safe_load(f)
+
+ spec_paths = extract_paths_from_spec(spec)
+
+ # Normalize miner routes for comparison
+ normalized_miner = {normalize_route(r) for r in miner_routes}
+
+ # Check coverage
+ missing = normalized_miner - spec_paths
+ extra = spec_paths - normalized_miner
+
+ print(f"📋 Miner routes found: {len(miner_routes)}")
+ print(f"📋 OpenAPI paths found: {len(spec_paths)}")
+ print()
+
+ if missing:
+ print("❌ Routes in miner.py NOT in OpenAPI spec:")
+ for route in sorted(missing):
+ print(f" - {route}")
+ print()
+
+ if extra:
+ print("ℹ️ Paths in OpenAPI spec not found in miner.py (may be aliases):")
+ for path in sorted(extra):
+ print(f" - {path}")
+ print()
+
+ if not missing:
+ print("✅ All miner routes are documented in the OpenAPI spec!")
+ return 0
+ else:
+ print(f"❌ {len(missing)} route(s) missing from OpenAPI spec")
+ return 1
+
+
+if __name__ == "__main__":
+ sys.exit(main())