Synapse API Reference
Synapse is the CMN Federated Indexer and Resilience Layer. It crawls sovereign domains, aggregates metadata, and serves as a content fallback when origin servers are offline.
For the protocol specification — pulse ingestion, verification rules, error codes, and Nostr integration — see the Synapse Protocol Specification.
Response Structure
All responses follow a consistent three-field structure:
{
"code": "ok",
"trace": { ... },
"result": {
"query": { ... },
"<content>": { ... }
}
}| Field | Description |
|---|---|
code | "ok" on success, or a specific error code (e.g. "not_found", "signature_invalid") — always present |
trace | Server-side operational state — always present (empty {} when no metadata) |
result | Response payload (success) or absent (error) |
error | Human-readable error message (error) or absent (success) |
Within result:
queryechoes the request parameters for context- Content key varies by endpoint (
spore,mycelium,myceliums,spores,bonds)
The trace field carries operational metadata such as max_depth_reached, source (graph vs storage), or pulse info. The action field (indexed/duplicate/updated/replicated) is in result for pulse responses. The access log middleware merges this with request metadata (method, path, status_code, latency_ms) for structured logging.
Spore API
GET /synapse/spore/:hash
Retrieve a spore by content hash (content-addressable lookup).
Path Parameters:
hash(required): BLAKE3 hash with prefix, e.g.,b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2
Response (200):
{
"code": "ok",
"trace": {},
"result": {
"query": { "hash": "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2" },
"spore": { ... },
"replicates": ["mirror.net"]
}
}
Notes:
sporeis the full signed manifest (passthrough)replicateslists mirror domains for this content hash (origin excluded, string array)- Replicates are detected by manifest semantics (
capsule.core.domain != URI domain) rather than a replicate-prefixed URI form - Useful for finding mirrors when the original domain is offline
GET /synapse/spore/:hash/bonds
Traverse bonds of a spore in a given direction.
Query Parameters:
direction(required):"inbound"(who points to me) or"outbound"(who do I point to)relation(optional): Filter by relation type (e.g.,"spawned_from","follows")max_depth(optional): Maximum traversal depth. Default: 1
Response (200):
{
"code": "ok",
"trace": {
"max_depth_reached": false
},
"result": {
"query": { "hash": "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2", "direction": "inbound", "max_depth": 1 },
"bonds": [
{
"uri": "cmn://fork.org/b3.8cQnH4xPmZ2vLkJdRt7wNbA9sF3eYgU1hK6pXq5",
"domain": "fork.org",
"name": "my-fork",
"relation": "spawned_from"
}
]
}
}
Error (400): Missing or invalid direction parameter.
Mycelium API
GET /synapse/myceliums
List all indexed myceliums.
Response (200):
{
"code": "ok",
"trace": {},
"result": {
"query": {},
"myceliums": [
{
"domain": "cmn.dev",
"hash": "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2",
"updated_at_epoch_ms": 1769787347698
}
]
}
}GET /synapse/mycelium/:domain
Retrieve indexed mycelium for a domain. Returns the full signed manifest (passthrough), enriched with cmn (capsule entry from cmn.json, best-effort) and replicates (known mirror domains, origin excluded).
Response (200):
{
"code": "ok",
"trace": {},
"result": {
"query": { "domain": "cmn.dev" },
"cmn": { "uri": "cmn://cmn.dev", "public_key": "ed25519.5Xmk...", "mycelium_hash": "b3.abc...", "endpoints": { ... } },
"replicates": [],
"mycelium": {
"$schema": "https://cmn.dev/schemas/synapse/mycelium.json",
"capsule": {
"uri": "cmn://cmn.dev",
"core": {
"name": "cmn.dev",
"domain": "cmn.dev",
"synopsis": "",
"updated_at_epoch_ms": 1769787347698,
"spores": [
{
"id": "cmn-spec",
"hash": "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2",
"name": "CMN Protocol Specification",
"synopsis": "Code Mycelial Network - A sovereign-first protocol for code distribution"
},
{
"id": "cmn-tools",
"hash": "b3.8cQnH4xPmZ2vLkJdRt7wNbA9sF3eYgU1hK6pXq5",
"name": "CMN Tools",
"synopsis": "Command-line tools for CMN protocol (hypha, synapse, substrate)"
}
]
},
"core_signature": "ed25519.5XmkQ9vZP8nL3xJdFtR7wNcA6sY2bKgU1eH9pXb4..."
},
"capsule_signature": "ed25519.5XmkQ9vZP8nL3xJdFtR7wNcA6sY2bKgU1eH9pXb4..."
}
}
}GET /synapse/mycelium/:domain/spores
List all spore hashes published by a domain.
Response (200):
{
"code": "ok",
"trace": {},
"result": {
"query": { "domain": "cmn.dev" },
"spores": [
"b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2",
"b3.8cQnH4xPmZ2vLkJdRt7wNbA9sF3eYgU1hK6pXq5"
]
}
}GET /synapse/mycelium/:domain/spore/:hash
Retrieve a specific spore from a domain. Returns the full signed manifest if the spore belongs to the requested domain.
Response (200):
{
"code": "ok",
"trace": {},
"result": {
"query": { "domain": "cmn.dev", "hash": "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2" },
"spore": { ... }
}
}Taste API
GET /synapse/spore/:hash/tastes
Retrieve taste reports submitted by other domains for a spore. Returns aggregated verdict counts plus a flattened list of recent reports. Taste values and processing rules are defined in 03-spore §2.5.
Path Parameters:
hash(required): BLAKE3 hash with prefix, e.g.,b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2
Response (200):
{
"code": "ok",
"trace": {},
"result": {
"query": { "hash": "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2" },
"verdicts": {
"sweet": 0,
"safe": 1,
"fresh": 0,
"rotten": 1,
"toxic": 0
},
"tastes": [
{
"target_uri": "cmn://cmn.dev/b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2",
"domain": "alice.dev",
"verdict": "safe",
"notes": [],
"tasted_at_epoch_ms": 1769787400000,
"uri": "cmn://alice.dev/taste/b3.7tRkW2xPqL9nH4mYeZcFjA5sD8vBwKgU6pXb3",
"replicates": ["mirror.net"]
},
{
"target_uri": "cmn://cmn.dev/b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2",
"domain": "bob.dev",
"verdict": "rotten",
"notes": ["Tests fail on ARM architecture"],
"tasted_at_epoch_ms": 1769787500000,
"uri": "cmn://bob.dev/taste/b3.8hVnR2xQmL5yK7p",
"replicates": ["mirror.net"]
}
]
}
}GET /synapse/mycelium/:domain/tastes
Retrieve taste reports for a domain target and its currently indexed mycelium target.
Path Parameters:
domain(required): domain name, e.g.,cmn.dev
Response (200):
{
"code": "ok",
"trace": {},
"result": {
"query": {
"domain": "cmn.dev"
},
"cmn": {
"target": "cmn://cmn.dev",
"verdicts": {
"sweet": 0,
"fresh": 0,
"safe": 1,
"rotten": 0,
"toxic": 0
},
"tastes": [
{
"target_uri": "cmn://cmn.dev",
"domain": "alice.dev",
"verdict": "safe",
"notes": [],
"tasted_at_epoch_ms": 1769787400000,
"uri": "cmn://alice.dev/taste/b3.7tRkW2xPqL9nH4mYeZcFjA5sD8vBwKgU6pXb3",
"replicates": []
}
]
},
"mycelium": {
"hash": "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2",
"targets": [
"cmn://cmn.dev/mycelium/b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2",
"cmn://mirror.net/mycelium/b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2"
],
"verdicts": {
"sweet": 0,
"fresh": 0,
"safe": 1,
"rotten": 0,
"toxic": 1
},
"tastes": [
{
"target_uri": "cmn://mirror.net/mycelium/b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2",
"domain": "bob.dev",
"verdict": "toxic",
"notes": ["Manifest points to unsafe mirror endpoint"],
"tasted_at_epoch_ms": 1769787500000,
"uri": "cmn://bob.dev/taste/b3.8hVnR2xQmL5yK7p",
"replicates": ["mirror.net"]
}
]
}
}
}
Notes:
- Reports are submitted via
POST /synapse/pulseusing thetaste.jsonschema - Synapse verifies
core_signatureandcapsule_signatureagainst the domain’s key - Only domains with valid Ed25519 keys (published in cmn.json) can submit reports
- Visitors without domains cannot submit taste reports — the trust anchor is the domain’s key
- Visitors can independently verify each report’s signatures against the taster’s public key
tastes[].replicatescarries mirror domains for that report’s target scope (origin excluded)- Reports are advisory — each visitor decides which domains to trust
Pulse API
POST /synapse/pulse
Receive and index a signed spore or mycelium manifest (and taste reports). See Synapse Protocol §2 for the full specification including verification rules and error codes.
Search API
Requires the ruvector Cargo feature and search.enabled: true in config.
GET /synapse/search
Semantic search over spore metadata using vector similarity.
Query Parameters:
q(required): Search query textdomain(optional): Filter by domainlicense(optional): Filter by licensebonds(optional): Filter by bond relationship. Format:relation:uri. Comma-separated for AND logic (all must match). Example:spawned_from:cmn://cmn.dev/b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2limit(optional): Max results. Default: 20, max: 100
Response (200):
{
"code": "ok",
"trace": {},
"result": {
"query": { "text": "HTTP client", "domain": null, "license": null, "bonds": null, "limit": 20 },
"spores": [
{ "uri": "cmn://cmn.dev/b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2", "domain": "cmn.dev", "name": "reqwest-lite", "relevance": 0.92 },
{ "uri": "cmn://cmn.dev/b3.8cQnH4xPmZ2vLkJdRt7wNbA9sF3eYgU1hK6pXq5", "domain": "cmn.dev", "name": "hyper-client", "relevance": 0.87 }
]
}
}
Error (503): Search engine not configured.
Graph API
Requires the ruvector Cargo feature and search.graph.enabled: true in config.
GET /synapse/graph/search
Semantic search over relationship edges. Requires search.graph.embed_relationships: true.
Query Parameters:
q(required): Search query text (e.g., “forked for security fix”)limit(optional): Max results. Default: 10, max: 100
Response (200):
{
"code": "ok",
"trace": {},
"result": {
"query": { "text": "forked for security", "limit": 10 },
"results": [
{
"edge_id": "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2->b3.8cQnH4xPmZ2vLkJdRt7wNbA9sF3eYgU1hK6pXq5:spawned_from",
"relation": "spawned_from",
"child_hash": "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2",
"parent_hashes": ["b3.8cQnH4xPmZ2vLkJdRt7wNbA9sF3eYgU1hK6pXq5"],
"score": 0.85
}
]
}
}GET /synapse/graph/temporal
Query evolution events within a time range.
Query Parameters:
start(required): Start timestamp (Unix epoch seconds)end(required): End timestamp (Unix epoch seconds)
Response (200):
{
"code": "ok",
"trace": {},
"result": {
"query": { "start": 1700000000, "end": 1710000000 },
"edges": [
{
"edge_id": "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2->b3.8cQnH4xPmZ2vLkJdRt7wNbA9sF3eYgU1hK6pXq5:spawned_from",
"relation": "spawned_from",
"child_hash": "b3.3yMR7vZQ9hL2xKJdFtN8wPcB6sY1mXgU4eH5pTa2",
"parent_hashes": ["b3.8cQnH4xPmZ2vLkJdRt7wNbA9sF3eYgU1hK6pXq5"],
"score": 1.0
}
]
}
}GET /synapse/graph/stats
Return graph statistics.
Response (200):
{
"code": "ok",
"trace": {},
"result": {
"graph": {
"total_nodes": 12847,
"total_edges": 9231,
"avg_degree": 1.44
}
}
}
Error (503): Graph engine not configured.
Nostr Relay API
GET /synapse/nostr
WebSocket NIP-01 relay (CMN events only). See Synapse Protocol §4 for event format and relay details.
Error Codes
Pulse Error Codes
See Synapse Protocol §6 for the standardized pulse error codes that all Synapse implementations must support.
Query Error Codes
code | HTTP | Description |
|---|---|---|
not_found | 404 | Spore or mycelium not in index |
invalid_hash | 400 | Hash format is invalid |
invalid_domain | 400 | Domain format is invalid |
search_unavailable | 503 | Search engine not configured |
search_failed | 500 | Search query failed |
graph_unavailable | 503 | Graph engine not configured |
graph_search_failed | 500 | Graph relationship search failed |
graph_query_failed | 500 | Graph temporal query failed |
graph_stats_failed | 500 | Graph stats query failed |
field_missing | 400 | Required field is missing |
Error Response Format:
Error type is carried in code directly — trace is only for operational metadata.
{
"code": "not_found",
"error": "Spore not found"HTTP Status Codes
| Status | Description |
|---|---|
| 200 | Request succeeded |
| 400 | Invalid request format or unknown schema |
| 401 | Signature verification failed |
| 404 | Resource not found |
| 422 | Invalid URI format |
| 500 | Internal error |
| 503 | Feature not configured (search/graph) |
Configuration
Storage Options
| Option | Description |
|---|---|
storage.type | "redb" (embedded) or "postgres" (production) |
storage.url | PostgreSQL connection string |
Each backend requires its Cargo feature to be compiled in (postgres or redb). Both are enabled by default. See Synapse Storage for details.
HTTP/HTTPS Options
| Option | Description | Default |
|---|---|---|
http.enabled | Enable HTTP server | true |
http.port | HTTP port | 3000 |
https.enabled | Enable HTTPS server | false |
https.port | HTTPS port | 443 |
https.acme.enabled | Use Let’s Encrypt | false |
https.acme.renewal_before_days | Renew certs this many days before expiry | 30 |
https.self_signed_validity_days | Self-signed certificate validity (days) | 365 |
Cache Options
| Option | Description | Default |
|---|---|---|
cache.cmn_ttl | cmn.json cache TTL in seconds | 300 |
Pulse Options
| Option | Description | Default |
|---|---|---|
pulse.max_clock_skew | Maximum allowed clock skew in seconds | 300 |
Search Options
Requires the ruvector Cargo feature (enabled by default).
| Option | Description | Default |
|---|---|---|
search.enabled | Enable semantic search | false |
search.index_path | Path for vector index data | "synapse-search" |
search.embedding.provider | Embedding provider: "ollama" or "openai" | — |
search.embedding.url | Embedding API base URL | — |
search.embedding.model | Model name (e.g., "bge-large") | — |
search.embedding.api_key | API key (required for OpenAI) | null |
search.embedding.dimensions | Embedding vector dimensions | — |
search.graph.enabled | Enable in-memory evolution graph | false |
search.graph.rebuild_on_startup | Rebuild graph from storage on boot | true |
search.graph.embed_relationships | Embed edge descriptions (costs API calls) | false |
search.graph.rebuild_batch_size | Batch size for startup rebuild | 100 |
When search.graph.enabled is true, bond queries are accelerated via in-memory graph traversal (falling back to storage on miss). The /synapse/graph/* endpoints are only available when the graph is enabled. Relationship search (/synapse/graph/search) additionally requires embed_relationships: true.
Nostr Options
Requires the nostr Cargo feature (enabled by default).
| Option | Description | Default |
|---|---|---|
nostr.enabled | Enable Nostr integration | false |
nostr.relays | External relay URLs | [] |
nostr.key_store | Key storage: "storage" (database) or "file:<path>" | "storage" |
nostr.subscribe | Listen for CMN events on external relays | false |
nostr.forward_pulse | Forward HTTP pulses to Nostr relays | false |
When nostr.enabled is true, the /synapse/nostr WebSocket endpoint is activated. A secp256k1 identity key is auto-generated on first run and persisted according to key_store.
CLI Flags
–rebuild-search
Rebuild the search index from storage on startup. Use this after schema changes, embedding model updates, or when the search index has become corrupted.
Prerequisites:
search.enabled: truein configurationruvectorCargo feature compiled in- A running embedding provider (Ollama or OpenAI) if re-embedding is needed
Usage:
synapse --rebuild-search
This triggers a full re-index: reads all spores from storage, recomputes embeddings, and rebuilds the vector index. When search.graph.enabled is also true, the in-memory graph is rebuilt simultaneously.
When to use:
- After changing
search.embedding.modelorsearch.embedding.dimensions - After database migration or restoration from backup
- When search results appear stale or incorrect
Hash Format
Hashes use dot as separator everywhere: b3.<base58>. This format is used consistently across URIs, JSON fields, filenames, and API paths. No conversion is needed. Base58 uses the alphabet 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz (no 0, O, I, l). BLAKE3 hashes encode to ~44 base58 characters.