DocumentsBuilding on it

API, SDK and SIEM export

4 min readSource sdk.mdsha256 2e9ab712166058a8…

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 webhooks section 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 throws unverified.
  • 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 by mirror-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, and mirror.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, where post refuses a raw field before anything is sent.
LanguageWhereTested with
Pythonsdk/python/mirror_sdk (standard library only; P-256 is implemented in the package)Python 3.14.7
Gosdk/go (module mirror/sdk)Go 1.27.1
Javasdk/java/src/dev/mirror/sdk (JDK only)Temurin JDK 21.0.12
.NETsdk/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.js holds the JavaScript kit to the vectors, and fails if the file is stale.
  • server/sdk-polyglot.test.js runs 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, use get / 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.json byte 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.