API reference

The Semurg HTTP API.

Two JSON surfaces, both hand-rolled on the perimeter gateway. The /v1 data API puts your own bytes into a licensed instance and reads them back. The /api benchmark API runs the same live queries the demos run, so any number here is reproducible on your own hardware. Every endpoint on this page was enumerated from the router and controllers and confirmed live against one.semurg.io. Performance figures shown in the example payloads below are the canonical stored snapshot — run 2026-08-18, build f279dfd, quiet box — the same source served at /semurg-metrics.js and rendered on Benchmarks. Example bodies show the response shape; the authoritative numbers are that one snapshot.

Honest scope. Only endpoints that actually exist and answer are listed. Where a capability is not yet a JSON endpoint (vector search, general graph traversal, SQL/OLAP over HTTP) it is marked roadmap and pointed at the interactive face that does serve it today. Nothing here is aspirational unless it says so.
Surfaces

Base URL and content types.

On the public reference box the API is served at https://one.semurg.io behind Caddy, which proxies the JSON paths to the gateway on port 4000. A self-hosted instance answers on http://localhost:4000 directly. There is no API versioning beyond the /v1 prefix.

SurfacePrefixPurposeReadsWrites
Data API/v1put your bytes in, read them outopen (auth optional)licence write cap
Benchmark API/apireproduce the live numbersopenblocked on public
Download gate/dissued-token bundle fetchtoken onlyn/a

/v1 negotiates application/json for the JSON ops and application/octet-stream ("bin") for the binary /v1/fetch_batch fast path. /api accepts JSON only. Both surfaces are batch-first: one request carries a batch and costs exactly one crossing into the engine. There is deliberately no per-item write endpoint, because a per-item turnstile is the one shape that cannot reach the engine's throughput.

Auth and back-pressure

Three gates, all fail-open by construction.

Optional caller auth (bearer token)

The /v1 surface has an optional built-in authentication gate. With no token provisioned it fails open: every /v1 request is served, which is exactly the public read-only reference box. Once a token is set (env SEMURG_API_TOKEN, an app-env :api_token, or an api_token= licence capability) the gate fails closed: every /v1 request, read and write alike, must present the token or get 401. The token is compared in constant time.

curl https://one.semurg.io/v1/status -H 'Authorization: Bearer <token>'

Either header works: Authorization: Bearer <token> or x-api-key: <token>. Multiple comma-separated tokens are accepted for rotation. /v1/status reports "auth_required":true once a token is set, without revealing it.

Write capability (licence gate)

/v1/ingest is the only mutating action, and it is gated on the instance licence carrying the write capability. The public build ships no licence module at all, so it is read-only by construction and /v1/ingest returns 403. Reads are never licence-gated.

Rate limiting and load shedding

A hand-rolled guard runs on both surfaces, parameterised by one deployment policy resolved once at boot:

ModePer-IP token bucketLoadavg shed/api write-blockConcurrency cap
public (this box)on: 60 cap, refill 3/s, heavy op = 6 tokenson: heavy benchmarks get 503on: 40332 in flight
onprem (default self-host)offoffoff (writes allowed)cores × 8
offoffoffoffnone

When the bucket or concurrency cap trips you get 429 with a Retry-After header. When the box is loaded, heavy benchmarks are shed with 503 + Retry-After while cheap metadata still serves. The guard never 500s a request itself: any doubt fails open. The self-hosted default (onprem) keeps only the concurrency cap so an operator benchmarking their own box is never throttled.

/v1 data API live

Put your bytes in, read them out.

Customer bytes land in this instance's own store, never in the shipped demo store. Everything crossing the boundary is opaque: uniform containers, packed ids, or base64 of the same. See Extension API for how to build the containers you ingest.

GET /v1/status

Instance identity, licence, writability, auth state, and store counts. An anonymous caller on an open box gets a reduced, non-identifying view (a store basename hint, no absolute path, no licensee name); once auth is on, the authenticated caller sees the full operator detail.

curl https://one.semurg.io/v1/status
{"ok":true,
 "api":"semurg /v1 data api",
 "writable":false,
 "auth_required":false,
 "licence":{"state":"unlicensed build (read-only)"},
 "store":{"state":"open","name":"user.bin","shards":null}}

Response captured live on the public box (read-only). A licensed instance reports "writable":true and its licence id, expiry and caps.

POST /v1/ingest write cap required

Append one batch of packed uniform containers. Body is JSON with containers_b64, the base64 of the packed containers. One call, one append, one crossing, no fsync in the path.

FieldTypeRules
containers_b64stringbase64 of a whole number of containers; max 65,536 containers (4 MB) per request; send successive batches for more
curl -X POST https://one.semurg.io/v1/ingest -H 'content-type: application/json' -d '{"containers_b64":"<base64 of packed containers>"}'
{"ok":true,"containers":65536,"bytes":4194304,"elapsed_us":<elapsed>,"containers_per_s":<measured on your box>}

On the public read-only box this returns 403 {"error":"this instance is read-only", ...}. Errors: 422 for a missing or non-base64 body or a wrong-length body, 413 for a batch over 65,536 containers, 500 if the append itself fails.

POST /v1/query

Batch read. The op field selects one of four read primitives. It is a POST only because it carries a JSON body; it is a read, so it is never licence-gated.

opparamsdoesresult
fetchids (list, ≤ 100,000)batch point read, one crossingbase64 of the containers, request order, zeros for absent
resolveid (int)logical id to live byte offsetinteger offset, or null if absent/tombstoned
searchneedle (string)pattern scan over raw container bytes[matches, containers_scanned, bytes_scanned]
histogramfield_offset (0..63, default 8), buckets (1 to 4,096, default 16)group-by fold over a qword field[count_0, ..., count_(buckets-1)]
curl -X POST https://one.semurg.io/v1/query -H 'content-type: application/json' -d '{"op":"fetch","ids":[0,1,2]}'
{"ok":true,"op":"fetch",
 "result":{"encoding":"base64","bytes":192,"containers":3,"data":"AAAA...AAAA"},
 "params":{"requested":3},"elapsed_us":<elapsed>}
curl -X POST https://one.semurg.io/v1/query -H 'content-type: application/json' -d '{"op":"histogram","field_offset":8,"buckets":4}'
{"ok":true,"op":"histogram","result":[0,0,0,0],"params":{"field_offset":8,"buckets":4},"elapsed_us":<elapsed>}

Responses captured live on the public box (its /v1 store is intentionally empty, so counts read as zero). A missing op or an unsupported one returns 422 with a hint; an empty ids list is 422; over 100,000 ids is 413.

POST /v1/fetch_batch binary

The binary twin of /v1/query op=fetch: same primitive, same single crossing, but no JSON or base64 wrap on the container bytes. Request body is application/octet-stream, a packed array of little-endian u64 ids (8 bytes each). Response is application/octet-stream, exactly one fixed-size container per id in request order, an all-zero container for an absent id. Cap is 1,048,576 ids (8 MiB) per request; a body that is not a whole number of ids is rejected 400 (plain text, since the route otherwise returns raw bytes).

printf '\x00\x00\x00\x00\x00\x00\x00\x00' | curl -X POST https://one.semurg.io/v1/fetch_batch -H 'content-type: application/octet-stream' --data-binary @- | xxd | head

Verified live: one id in returns exactly one container out.

/v1 gaps

What is not a JSON endpoint yet.

The engine does more than the four /v1/query ops expose. These capabilities run today on the interactive faces (in-process, on the same one store) but are not yet a JSON /v1 endpoint. They are honestly on the roadmap.

No vector op on /v1/query yet. roadmap There is no {"op":"vector"} (or any JSON vector search) on /v1. Vector code containers can be ingested and fetched like any other container, but binary approximate-nearest-neighbour search is only exercised through the interactive vector face in-process, not over HTTP. Passing op=vector to /v1/query returns 422 today (verified live).
CapabilityStatus over HTTPWhere it runs today
Vector / ANN searchroadmap no JSON op/demo/vector, in-process
General graph traversal (k-hop, neighbours, shortest path)roadmap no general JSON opthe /demo/g* faces and /api/bench/graph_teps (fixed BFS benchmark)
SQL / OLAP over HTTProadmap no JSON opthe /demo/sql* and /demo/olap faces, in-process
Value-flow forensic traceroadmap no JSON opthe collapse and dataset faces, in-process

The building blocks for all of these are the extension API on the Develop page: an extension in-process has the full engine surface (scan, histogram, traversal, vector codes) that the JSON API only partly re-exports.

/api benchmark API live

Reproduce the numbers.

Every figure the demos show is measured live on this box. The benchmark API lets you run the same queries programmatically and get the same numbers, then download the public dataset and re-run to verify. Params come from the query string (GET) or a JSON body (POST); both work.

GET /api/health

Liveness, which datasets are ready, the live engine state, and the local model-serving status. The engine block asserts the native engine is the engaged read path (not the OS page cache).

curl https://one.semurg.io/api/health
{"ok":true,"service":"semurg benchmark api",
 "engine":{"mode":"native","arms":{"shard":"engaged","chain":"engaged","snn":"engaged"}},
 "datasets":{"binance":"warming","btc-chain":"warming"},
 "intelligence":{"backend":"local_e4b","model":"gemma4:e4b","serving":false,"detail":"native_module_absent"}}

Captured live. The datasets map and the intelligence block are instance-dependent and reflect what is staged and warmed on the box at the moment of the call.

GET /api/benchmarks

The catalog: every runnable benchmark key, what it measures, which datasets it accepts, and its params.

curl https://one.semurg.io/api/benchmarks
keymeasuresdatasetsclass
graph_tepsdeep k-hop BFS, traversed edges/secondgraph datasets (pokec, web-google, ...)heavy
traversalswhole-graph BFS, cold + warm TEPSgraph datasetsheavy
readshot (L3) + warm (DRAM) reads/secondgraph datasetsheavy
scansresident scan throughput (GB/s)graph datasetsheavy
saopsraw SNN kernel synaptic-ops/secondsynthetic bit slabscheap
writespure-append containers/secondsynthetic fresh handlewrite (blocked on public)

GET POST /api/bench/:key

Run one benchmark. Read-only keys only on the public box; the write keys (writes and friends) are refused 403 under the public policy. An unknown key returns 404 with the available list; a warming or absent dataset returns 503 (retry shortly); malformed params return 422.

curl 'https://one.semurg.io/api/bench/saops?iters=50000'
{"benchmark":"saops",
 "result":{"saops":<measured on your box>,"synops":1638400000,"saops_human":"<your figure>"},
 "params":{"iters":50000},
 "elapsed_s":<elapsed>,
 "provenance":{"native_engaged":true,"honesty_band":"(c) measured on native silicon"}}

Response captured live. graph_teps takes dataset, hops (1..8), seed and span; it returns 503 while its dataset is warming.

GET /api/dashboard heavy

One-call headline snapshot: it re-runs the full heavy battery (multi-million-container writes plus BFS traversals) and is memoized for 5 seconds so a burst cannot re-run it per request. Classed heavy, so it is shed under load on the public policy.

curl https://one.semurg.io/api/dashboard
{"measured_live_on":"<hostname>",
 "writes":{"result":{"containers":1000000,"writes_per_s":<measured on your box>}, ...},
 "writes_16shard":{...},"reads":{...},"traversals":{...},"scans":{...},
 "saops":{"result":{"saops":<measured on your box>,"saops_human":"<your figure>"}, ...}}

Captured live; keys whose dataset is still warming report a {"status":"warming"} stanza rather than a number, which is the honest state on a freshly booted box.

/d download gate live

Issued-token bundle delivery.

Two token-gated routes deliver an evaluator their own installer bundle and licence key: GET /d/:token and GET /d/:token/license.key. A bad or absent token is refused 403 (verified live). This is Semurg's own gated delivery, not part of the data API, and it is pruned from a customer instance build: a customer box has nothing to hand out, so these routes do not exist there.

Error codes

One table for both surfaces.

CodeWhereMeaning
200allOK. JSON body, or raw bytes for /v1/fetch_batch.
400/v1/fetch_batchbody not a whole number of 8-byte ids, or over the id cap. Plain-text body.
401/v1/*token required and not presented. Only when a token is provisioned; the public box never returns this.
403/v1/ingest, /api/bench/<write>, /dread-only instance (no write cap), or public write-block, or bad download token.
404/api/bench/:keyunknown benchmark key; body lists the available keys.
413/v1/query, /v1/ingesttoo many ids (> 100,000) or too large a batch (> 65,536 containers).
422/v1/query, /v1/ingestmissing or malformed op, empty ids/needle, out-of-range histogram params, non-base64 body.
429all (public)per-IP rate limit or global concurrency cap. Carries Retry-After.
500/v1/ingestthe engine append failed.
503/api/*dataset warming/absent, or heavy benchmark shed under load. Carries Retry-After where applicable.
Reproduce it. Everything on this page runs on your own instance too. See the Docs for install, and Develop for the in-process function API an extension calls directly.