Kurz auf Deutsch. Der API-Spine ist der eine getypte Kern hinter den Fassaden der Plattform: vierzehn Operationen, die REST-Spezifikation aus dem Code erzeugt, der Client aus der Spezifikation erzeugt, jede Operation genau einer Zeile eines Fähigkeitsregisters zugeordnet. Lesend über dem Verzeichnis, dem Siegelregister und dem Werkzeug-Inventar; dazu ein Passkey-Login. Anleitung unten auf Englisch.
api-spine-oh is the API of the association OpenHelvetia: one typed core behind the platform's facades. The REST specification is emitted from the code (dropshot), the client is generated from the specification (progenitor), and the MCP tool surface of the gateway binds to the same functions — never a second implementation of the same semantics. Fourteen operations, each mapped to exactly one row of a capability register that lives in code; a test proves that register and specification cover each other exactly.
- Before you start
- Get it running in five minutes
- The operations
- Your first calls
- How the pieces fit
- What is in this repository
- How it is verified
- When something does not work
- Where this repository comes from
- Contributing, security, licence
| Need | Why | How to get it |
|---|---|---|
| Rust, stable (rustc and cargo) | everything here is built from source | https://rustup.rs — one command, then open a new terminal and run cargo --version |
| Git | to clone this repository | macOS: xcode-select --install; Linux: your package manager; Windows: https://git-scm.com |
The tests need no network. Linux and macOS are what the association builds on; Windows works in principle, use WSL if in doubt.
Clone
git clone https://github.com/OpenHelvetia/api-spine-oh.git
cd api-spine-ohRun the tests (offline; the end-to-end tests start the server on a free local port)
cargo test --locked --manifest-path registry/api/spine/Cargo.toml
cargo test --locked --manifest-path registry/api/client/Cargo.toml
cargo test --locked --manifest-path registry/api/facts/Cargo.toml
cargo test --locked --manifest-path profile/auth/login/Cargo.tomlServe the API locally, read-only, over the entries in this repository:
cargo run --locked --manifest-path registry/api/spine/Cargo.toml -- serve --root . --port 8710What you should see: oh-api serving on 127.0.0.1:8710. Then, in a second terminal:
curl -sS http://127.0.0.1:8710/capabilities
curl -sS http://127.0.0.1:8710/directoryPrint the specification exactly as the server emits it:
cargo run --locked --manifest-path registry/api/spine/Cargo.toml -- emit-openapi| Method | Path | What it answers |
|---|---|---|
| GET | /capabilities |
the capability register: every operation with its id, method, path and which clients exercise it |
| GET | /directory |
the directory envelope (v0.2, authority: manifest), one schema-shaped row per entry |
| GET | /registry/{slug}/manifest |
the byte-identical manifest of one entry; 404 when there is none |
| GET | /ns/artifacts |
the seal register: every sealed artifact with its hash |
| GET | /tools |
the gateway's tool inventory with the weight of every call |
| GET | /publish |
the three admission routes into the directory |
| GET | /facts/{domain} |
the latest measured facts of a domain (checker, benchmark) |
| GET | /facts/{domain}/badge |
the same, as a badge document |
| POST | /login/register/begin, /login/register/finish |
passkey registration (WebAuthn) |
| POST | /login/authenticate/begin, /login/authenticate/finish |
passkey login, issuing a session |
| POST | /login/logout |
ends a session |
| GET | /login/whoami |
who the caller is — the one authorised call |
The read operations serve the repository as it is: no write path exists in the code. The login operations run only when a login backend is configured; without one the server refuses them rather than improvising a store.
With the server from §2 running:
curl -sS http://127.0.0.1:8710/publish
curl -sS http://127.0.0.1:8710/registry/openhelvetia-fedlex-mcp/manifest | head -c 400
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8710/registry/no-such-entry/manifestThe last line answers 404 — an absent entry is an honest not-found, never an empty object.
registry/api/spine— the server and the capability register;emit-openapiprints the specification.registry/api/openapi.json— the committed specification; the corpus gate fails when it differs from what the code emits.registry/api/gen-client+registry/api/client— the client is generated from the specification and committed; a second gate fails on drift. Consumers use the client, never hand-written calls.registry/api/facts— the bitemporal fact core the/factsoperations read.profile/auth,profile/org— authorisation and the organisation model;profile/auth/loginwithstoreandserve— passkeys, sessions, and their storage.
| Path | What |
|---|---|
registry/api/spine/ |
the server, the register, the tests (end-to-end over a real local server) |
registry/api/openapi.json |
the specification, emitted from the code |
registry/api/client/, registry/api/gen-client/ |
the generated client and its generator |
registry/api/facts/ |
the fact core |
profile/auth/, profile/org/ |
authorisation, organisation model, passkey login |
registry/entries/ |
the directory's entries the server reads |
registry/standard/directory.schema.json, artifacts.json |
the envelope schema the tests validate against, and the seal register /ns/artifacts mirrors |
mcp/gateway/tool-inventory.json |
the inventory /tools serves |
LICENSE, NOTICE |
Apache-2.0 and the attributions |
The spine's tests prove that the capability register and the specification's operations cover each other exactly, that /directory satisfies the same directory schema the website emits, that every read operation answers over a real local server, and that the login flow works end to end against an in-memory backend. The client's end-to-end test walks every non-login capability through the generated client. Two drift gates in the corpus keep the committed specification and client equal to what the code emits.
| You see | What it means | What to do |
|---|---|---|
error: package … requires rustc 1.xx |
your Rust is too old | rustup update stable |
serve exits at once |
the root has no registry/entries |
pass --root pointing at the folder you cloned |
| a login route answers that no backend is configured | this build serves read-only | that is intended; the login backend is a deployment concern |
| the port is taken | another process listens on 8710 | choose another --port |
The association develops all its modules in one corpus, on its own GitLab, where every change runs through a gate (formatting, Clippy without warnings, all tests, seal and drift checks). This repository is assembled from that corpus by the publication lane (tools/publish-module.sh there): it takes the crates and exactly the files their builds and tests need, runs the tests in the assembled tree, and pushes here. Each publication is one commit whose message names the corpus commit.
This copy was published from corpus commit 93e2d01 on 2026-09-06.
- Issues here are welcome: a wrong result, a missing case, an unclear sentence in this README. Please include the command you ran and what came back.
- Changes go through the corpus and arrive here with the next publication; a pull request here is read and carried over by hand.
- Security reports, in confidence: admin@openhelvetia.swiss. The association answers within a working week.
- Licence: Apache-2.0 (
LICENSE, attribution inNOTICE).