API reference
The registrar exposes a small HTTP surface. This page covers the wire
conventions common to every endpoint; the Registrar API page
documents each endpoint, and Bundles & grants covers the
share/verify exchange in detail. The handlers are thin wrappers over the
registrar operations in
crates/tenure-registrar/src/http.rs.
Transport
- HTTP with JSON bodies. Request and response bodies are JSON.
- Canonical bytes are still BCS. JSON is the transport, never the signed form. Any signed object inside a request carries its canonical BCS bytes base64url-encoded; signatures are verified over those bytes, not over the JSON.
Signed objects on the wire
A signed protocol object travels as the Signed envelope:
{
"payload_b64": "…", // base64url(no-pad) of bcs(("tn-xxx-v1", body))
"signer_pk": "…", // lowercase-hex Ed25519 public key
"sig": "…" // base64url(no-pad) Ed25519 signature over the payload bytes
}
The registrar verifies the signature over payload_b64’s decoded bytes and then
decodes under the expected domain tag. A tampered byte fails the signature check
before the payload is interpreted.
Authenticated envelopes
Mutating calls from signers (the employer Signer, a holder, etc.) are wrapped in an envelope that authenticates the call, not just the embedded object:
{
"payload_bcs_b64": "…",
"signer_pk": "…",
"sig": "…",
"nonce": "…", // unique per signer (single-use)
"ts": 0 // unix seconds; accepted within ±5 minutes
}
Replay defense (per the security requirements):
- nonces are unique per signer and recorded, so a captured envelope cannot be replayed;
tsmust be within ±5 minutes of the registrar’s clock;- ShareGrant nonces are single-use per audience.
Unauthenticated surfaces — the public head, checkpoint, and revocation endpoints, and share-link fetches — carry no envelope. In production these are rate-limited and proof-of-work gated; that gating is not wired into this build.
Operation receipts
Every mutating response that appends to the log includes an operation receipt so the caller can retain a witnessed head:
{
"employer_id": "…",
"log_seq": 0,
"entry_hash": "…",
"log_head": { "epoch_no": 0, "seq": 0, "head_hash": "…", "head_sig": "…" }
}
Wallets keep these as inclusion receipts.
Errors
Errors are a JSON object { "error": "…" } with a status code:
| Status | Condition |
|---|---|
401 Unauthorized | Envelope verification failed (bad signature, stale ts, replayed nonce) |
404 Not Found | Unknown employer, grant, or entity |
422 Unprocessable Entity | The request was authenticated but rejected by policy (e.g. a mint outside delegation, a batch that fails the cross-check) |
500 Internal Server Error | Storage or internal failure |