The API description#
/openapi.json is OpenAPI 3.1 and is generated, never hand-maintained. server/openapi.test.js fails the build when it drifts:
- every route the operator serves is described;
- every described route answers;
- security matches the operator's own public set;
- the file is fresh.
Two things in it are unusual:
- The raw-data wall is in the schema. Every request body forbids the fields the operator refuses: value, unit count, band, table, salt, grade, document salt and the rest. So a generated client cannot even be asked to send one.
- The pushes are described. The top-level
webhookssection documents the signed deliveries, their headers and the retry contract.
The JavaScript client (tested)#
The reference client ships inside /mirror-kit.js (MirrorKit.client), next to the kernels it verifies with. It runs in Node 20+ and in the browser.
const { createClient } = require("./mirror-kit.js").client; // browser: MirrorKit.client
const mirror = createClient({ baseUrl: "https://mirror-operator.vasileburuian94.workers.dev" });
await mirror.login(kid, (canonicalText) => signWithYourKeyOrHsm(canonicalText));
const { record } = await mirror.reconcile({ shipment_ref, legs, policy }); // already verified
const clock = await mirror.clock({ budget_ms: 1000 }); // within_budget | out_of_budget | undetermined
- Every signed answer is verified before you see it, against
/jwks, with the same kernel the operator signs with. A tampered answer throwsunverified. - It refuses to send raw trade data. The same fields the operator refuses are refused before a byte leaves, at any depth and in any case (
raw_leaked). - It never holds your key.
login()takes a signing function, so the key can stay in the HSM. - Duplicate financing.
mirror.exposure.commit(claim)posts a claim made inside the bank bymirror-bander exposure-claim, and refuses anything that is not a locator commitment, so a document number cannot be posted by mistake.mirror.exposure.collisions(commitment)asks about one document you committed, andmirror.exposure.check(claim)about all of a claim's. A collision record is verified before it is returned. An amount, a borrower or a commodity riding with an exposure is refused before a request is made. See duplicate financing. - Tested against a running operator by
server/sdk.test.js.
For AI agents (MCP)#
sdk/mirror-mcp.js wraps this client as a Model Context Protocol server over standard input and output, so an agent can call Mirror's checks with the same verification and the same raw-data wall. It has ten tools, holds no key, and has no tool that bands, scores or files. See Mirror for AI agents.
Python, Go, Java and .NET#
Four more SDKs do the same job in a bank's own language. Each ships as source in the repository, has no dependency outside its standard library (Go adds golang.org/x/text for Unicode normalisation), and contains two parts.
The checks a bank's code runs:
- canonical JSON;
- the key id (
kid) of a public key; - ES256 verification with the key selected by
proof.kid, and the low-S rule for the signature's form; - the raw-data scan, with the same key normalisation as the operator: NFKC, invisible characters, look-alike letters and camelCase;
- webhook subscription and delivery ids, and delivery verification;
- the residency check (
meets), described in data residency.
A client that verifies every signed answer before returning it:
- reads:
/jwks,/cannot,/roadmap,/residency, and a nonce-bound/time; - sign-in through a signing function you supply, so your key stays in your HSM;
- webhook subscriptions;
- generic
get/post, wherepostrefuses a raw field before anything is sent.
| Language | Where | Tested with |
|---|---|---|
| Python | sdk/python/mirror_sdk (standard library only; P-256 is implemented in the package) | Python 3.14.7 |
| Go | sdk/go (module mirror/sdk) | Go 1.27.1 |
| Java | sdk/java/src/dev/mirror/sdk (JDK only) | Temurin JDK 21.0.12 |
| .NET | sdk/dotnet/Mirror.Sdk (net8.0, no packages) | .NET SDK 8.0.425 |
from mirror_sdk import Client
mirror = Client("https://mirror-operator.vasileburuian94.workers.dev")
mirror.login(kid, lambda text, challenge: your_hsm_sign(text)) # base64url ES256, made by your key holder
refusals = mirror.cannot() # raises MirrorError("unverified") if tampered
where = mirror.residency("eu") # {"record": ..., "meets": {"ok": True, "reasons": []}}
How they are held to the same answers#
/sdk-vectors.json holds 100 cases generated from the JavaScript kernels by server/build-sdk-vectors.js. They include:
- canonical text, including number formatting and string escapes;
- kids;
- valid, tampered, wrong-key and high-S signatures;
- disguised raw fields;
- webhook ids and deliveries;
- residency answers.
The checks:
server/sdk-vectors.test.jsholds the JavaScript kit to the vectors, and fails if the file is stale.server/sdk-polyglot.test.jsruns each language's own vector runner. It then runs each client against a running operator that the test starts in production posture. It checks sign-in through the bander, verified reads, a changed answer refused, raw data refused before sending, and the webhook round trip. Current counts, vectors and client checks together: Python 113, Go 112, Java 112, .NET 112.
What they are not#
- Not published to a package registry. Nothing is on PyPI, Maven Central, NuGet or the Go module proxy, and no package names are reserved. Build or vendor them from source.
- Not generated from
/openapi.json. They are written by hand and held to the vectors. For endpoints they do not wrap, useget/post, or generate an HTTP layer from the OpenAPI document with your usual generator. - Independent of the specification text? No. They are ports of the kernels. The separate evidence that SPEC ยง8.2 is enough on its own is
conformance/reference-impl.js: it was written from the spec text without importing the kernels, and reproduces/conformance-vectors.jsonbyte for byte.
SIEM#
POST /audit/export (member session; a desk key needs read) returns your own signed records as JSON lines:
- verdicts where your leg authenticated;
- governance events you signed.
Each line names the record by digest and signer, and on request carries the signed record itself. The batch is signed, and lines_digest covers the lines. Pull on a schedule with the returned cursor, which pages through every line exactly once. Mirror assigns no severity and no score.
The operator stores no SIEM credential: your SIEM pulls, or a relay inside the bank forwards verified webhook deliveries with the SIEM's token.