ORBIS.ID Open the walletWallet

Developers · the integration contract, generated from the server

Build on ORBIS.ID.

A machine-readable contract at /openapi.json, quickstarts for the three roles you can play, and copy-pasteable calls for every step from provisioning an organization to checking a proof. Generated from the platform’s own route table, so it cannot describe a server that does not exist.

Building a product on this rail rather than reading the API? Partners is the path from “interested” to “verifying in production”: pick your role, see exactly what ORBIS needs and what the platform will prove before you go live. If you only need to check proofs, it will tell you that you need no account at all — see the relying-party path.

The contract

GET /openapi.json is an OpenAPI 3.1 document served publicly and CORS-open, exactly like the platform’s other public artifacts. It is not written by hand. A generator scans the dispatch code in admin/admin-server.ts, admin/backoffice-api.ts, admin/directory.ts, admin/demo-bridge.ts, oid4vci/issuer-server.ts, oid4vp/verifier-server.ts, admin/partner-keys-routes.ts, admin/portal-routes.ts and re-derives the route table; the build fails if the document and those files disagree.

261

operations documented

from the route table

242

distinct paths

public origin

190

response shapes deliberately not published

marked x-orbis-undocumented

8

source files scanned

scripts/gen-routes.ts

Read the honesty marker before you build

An operation carrying x-orbis-undocumented has a response shape ORBIS has not promised. It is marked rather than guessed, because a contract that lies is worse than no contract. Most of them are the Back Office data plane — the operator SPA’s own view models, which change with the SPA. Integrate against the marked-and-specified operations, and against the standards (OID4VCI metadata, status lists, did:web), which are stable by definition.

Presence of an operation means only that this deployment mounts that route. It is not a claim that the capability behind it is commercially live — see live vs. planned.

What /v1 promises — and what it deliberately does not

ORBIS is two things at once, and they are versioned by opposite rules. Knowing which one you are calling is the single most useful thing on this page.

Verification — versioned by the standards, never by ORBIS

no /v1, ever

did:web documents, OID4VCI issuer metadata, OAuth authorization-server metadata, Token Status Lists, the OID4VP request/response exchange and the published conformance vectors are versioned by OID4VCI, OID4VP, SD-JWT VC and Token Status List — the specifications that define them. They stay at their standard, unversioned addresses forever, and they need no account.

This is enforced, not intended. An ORBIS version segment anywhere on a verification path, or inside any address a verification artifact publishes, fails the ORBIS build. If ORBIS ever wanted to version the rail, it would have to delete the test that stops it.

Participation — versioned by ORBIS

/v1/…

Issuing, provisioning, depositing and the holder’s own wallet operations are ORBIS’s API, so ORBIS versions them. An operation marked x-orbis-stability: promised in /openapi.json is served under /v1 and ORBIS owes you its shape.

The alias is additive. /v1/offers and /api/offers are the same handler, the same auth, the same body, the same response — the router rewrites the path and changes nothing else. No unversioned path is being retired, and there is no date on which the paths you use today stop answering.

The admin plane — excluded, and free to change without notice

209 operations

The Back Office data plane (/api/admin/*) and every ORBIS product interface are deliberately excluded from /v1. They are the operator SPA’s own view models: their shape follows the screen, and it may change at any time, without notice, without a deprecation window.

They are marked so you can tell. Every one carries x-orbis-internal and x-orbis-undocumented in the document. Do not build on them. This is not a warning that they are fragile today — it is a statement that ORBIS will not hold them still.

The promised surface — 7 operations ORBIS owes you
Method/v1 addressAlso served atAuth
POST /v1/tenants /api/tenants operator
POST /v1/types /api/types operator
POST /v1/offers /api/offers operator
POST /v1/tenants/{tenant}/bills /api/tenants/{tenant}/bills operator
GET /v1/member/me /member/me holder
POST /v1/wallet/{accountId}/items/{itemId}/accept /api/wallet/{accountId}/items/{itemId}/accept holder
POST /v1/wallet/{accountId}/items/{itemId}/sharing /api/wallet/{accountId}/items/{itemId}/sharing holder

Compatibility and deprecation

These terms bind ORBIS for every promised operation. They are published verbatim in /openapi.json under x-orbis-versioning.policy, so your tooling reads the same words you do.

What may change

A promised operation may gain OPTIONAL request fields and NEW response fields. It may never remove a field, repurpose one, change a field's type, tighten validation on an existing input, or change the status code an existing outcome returns.

What gets a new version

A breaking change gets a NEW prefix (/v2). /v1 is never changed in place. Two versions may be served side by side for as long as callers need both.

How something is retired

A promised operation is retired only after it is marked `deprecated: true` in /openapi.json with an `x-orbis-sunset` date, announced on /developers, and left answering for at least 180 days after that announcement. ORBIS does not shorten that window for its own convenience.

Your current URLs

Every existing unversioned path keeps answering exactly as it does today. /v1 is an ADDITIVE alias — the same handler, the same auth, the same body, the same response. There is no cutover and no date on which the current paths stop working.

Authentication planes

ORBIS has three credential planes and they never mix. A credential of one plane is not accepted on another, by construction — this is a product law, not a convention.

Operator bearer — the control plane

/api/*

Authorization: Bearer <token>. Three things produce one: an operator session from POST /api/login, the Microsoft Entra bridge at GET /auth/api-token, or a tenant-scoped operator token minted through the provisioning ceremony (revealed exactly once — ORBIS stores only its hash and can never show it to you again).

Roles are re-resolved on every request, from the operators table — a demoted, suspended or removed operator loses access on its very next call, not when a session expires. A tenant-scoped token may only act on its own organization; every mutation must name that organization explicitly, and a body with no organization fails closed.

Holder JWS — the wallet plane

typ orbis-holder+jwt

Authorization: Holder <jws>. A compact ES256 JWS signed by the device key, typ orbis-holder+jwt, with the public JWK in the header, aud set to this origin, an iat inside a five-minute freshness window, and a jti that burns on first use. There is no shared secret on this plane.

The identity IS the key. A holder’s member id is the RFC 7638 thumbprint of that JWK — the same pseudonymous id enrollment derives from a presentation’s cnf.jwk, so “the key that holds the credential” and “the member” are one thing. A path segment naming an account must match the signer, or the request is refused with account_mismatch.

OID4VCI access token — one collection

/token → /credential

Issued by POST /token against a single-use pre-authorized code, and good for exactly one credential collection. It authorises nothing else anywhere on the platform.

Everything under /verify/*, /manifest, /.well-known/* and /status/* is public and CORS-open: a relying party verifies a credential with no account, no key and no protocol code of its own.

Quickstarts

Pick the role you are playing. Each one names the smallest set of operations that gets you there.

Issuer organization

You want to hand out credentials people can carry.

  1. POST /api/tenants — your did:web identity and vault-held key.
  2. POST /api/types — declare the claims.
  3. POST /api/offers — a QR the wallet scans.
  4. POST /api/revoke — take it back; the signed status list is the public evidence.

Needs an operator bearer with the issuer role (or owner).

Relying party / verifier

You want to check a proof. You need no ORBIS account at all.

  1. GET /verify/catalog — what can be asked for.
  2. POST /verify/request — bound to your nonce and audience.
  3. GET /verify/result/{transactionId} — poll the outcome, with the resultToken the request handed you. Still no account — the outcome is simply yours rather than the world’s.

Or skip the hosted flow entirely: resolve the issuer’s did:web, fetch its status list, and run the chain yourself. Both routes check the same five links.

Wallet integrator

You are building a holder surface.

  1. GET /manifest — white-label config per tenant.
  2. POST /tokenPOST /credential — the OID4VCI wire.
  3. GET /api/wallet/{accountId}/items — the bridge.
  4. POST /member/enroll, GET /member/me — membership.

Everything past enrollment is signed by the device key. Never a bearer secret.

The four-step flow, in curl

The same four steps /docs describes conceptually, as calls you can run. Replace $ORBIS_OPERATOR_TOKEN with your own bearer; nothing below is a live credential, and no example here is executed against the production origin.

Step 1 — provision
# 1 · Provision an organization (owner bearer).
#    DEMO-TENANT LAW: a demonstration org MUST be named so its slug starts
#    with "demo-" and its display name ends with " (Demo)". Never un-marked.
curl -sS https://orbis.id/api/tenants \
  -H 'authorization: Bearer $ORBIS_OPERATOR_TOKEN' \
  -H 'content-type: application/json' \
  -d '{"name":"Demo Acme Freight (Demo)"}'
# → {"slug":"demo-acme-freight-demo","name":"Demo Acme Freight (Demo)","did":"did:web:orbis.id:t:demo-acme-freight-demo"}

# 1b · Register a credential type for it.
#      Claim specs: key | key:date | key:number | key:money | key:bool
#                 | key:array(text) | key:array(object) | key:enum(a|b|c)
#      An unrecognised spec is a hard 400 — never a silently-dropped claim.
curl -sS https://orbis.id/api/types \
  -H 'authorization: Bearer $ORBIS_OPERATOR_TOKEN' \
  -H 'content-type: application/json' \
  -d '{"tenant":"demo-acme-freight-demo","name":"Carrier Licence",
       "claims":["carrier_name","licence_class:enum(A|B|C)","expires:date"]}'
Step 2 — issue over OID4VCI
# 2 · Create a credential offer. The offerUri is what you render as a QR.
curl -sS https://orbis.id/api/offers \
  -H 'authorization: Bearer $ORBIS_OPERATOR_TOKEN' \
  -H 'content-type: application/json' \
  -d '{"tenant":"demo-acme-freight-demo","typeId":"carrier-licence",
       "subject":{"carrier_name":"Acme Freight","licence_class":"B","expires":"2027-01-31"}}'
# → {"offerUri":"openid-credential-offer://..."}

# The WALLET then does this part — shown so you can see the whole wire:
curl -sS https://orbis.id/t/demo-acme-freight-demo/token \
  -d 'grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code' \
  -d 'pre-authorized_code=<from the offer>'
# → {"access_token":"...","c_nonce":"...","expires_in":300}

curl -sS https://orbis.id/t/demo-acme-freight-demo/credential \
  -H 'authorization: Bearer <access_token>' \
  -H 'content-type: application/json' \
  -d '{"credential_configuration_id":"carrier-licence",
       "proof":{"proof_type":"jwt","jwt":"<ES256 JWT, typ openid4vci-proof+jwt, aud=this issuer, nonce=c_nonce>"}}'
# → {"credentials":[{"credential":"<SD-JWT VC>"}]}
Step 3 — hold
# 3 · Hold. The credential lives in the holder's wallet under device-key
#     custody. There is no ORBIS API that reads a holder's wallet: the wallet
#     bridge is authenticated by the DEVICE KEY, and the account id IS the
#     RFC 7638 thumbprint of that key. Nothing an operator holds can forge it.
curl -sS https://orbis.id/api/wallet/<thumbprint>/items
# → {"accountId":"...","fixtures":false,"items":[...]}
#   "fixtures": true means this deployment is serving demonstration data.
#   That marker is an honesty law, not a debug flag — never suppress it.
#   This LIST is open (the wallet reads it with no header); every WRITE —
#   accept, sharing — needs the device key's signature. Pending offers
#   materialize only for an already-enrolled account, so reading this URL
#   with an account id you invented can never make an issuer key sign.
Step 4 — verify over OID4VP
# 4 · Verify — with NO ORBIS account, NO API key, from any origin.
curl -sS https://orbis.id/verify/catalog
# → {"types":[{"id":"...","name":"...","vct":"...","tenant":"...","claims":["..."]}]}

# Ask for exactly the claims you need, bound to YOUR nonce and audience.
curl -sS https://orbis.id/verify/request \
  -H 'content-type: application/json' \
  -d '{"vct":"<vct>","claims":["carrier_name"],
       "nonce":"<your session nonce>","aud":"https://your-app.example"}'
# → {"requestUri":"openid4vp://?request_uri=...","transactionId":"...",
#    "resultToken":"..."}
#   Render requestUri as a QR — NEVER the resultToken. The wallet fetches the
#   request object from https://orbis.id/oid4vp/request/<transactionId> and
#   posts the presentation back to https://orbis.id/oid4vp/response.

# Poll the outcome. "outcome" stays null until the round-trip really completed
# — and until you present the resultToken, because the outcome is yours, not
# the world's. A poll without it reads null forever, exactly like an unknown
# transaction; nothing here ever asks you for an account.
curl -sS https://orbis.id/verify/result/<transactionId> \
  -H 'authorization: Bearer <resultToken>'
# → {"outcome":{"ok":true,"presentation":{...},"presentedNonce":"...","presentedAud":"..."}}
#   presentation.claims carries EXACTLY the claims you asked for. The holder's
#   confirmation key is never served: its thumbprint is that holder's wallet
#   account id.
# → {"outcome":{"ok":false,"error":"<which link of the chain failed>"}}

Typed claims are strict on purpose

number and money must arrive as JSON numbers — a numeric string is refused, never coerced. money is a whole number of minor units and a fractional value is refused outright, because silently rounding money is the worst failure available. Sign is not constrained: a credit or a refund is legitimately negative. bool must be a JSON boolean; enum must be one of the registered options; array(text) and array(object) make each element individually disclosable. A claim key may not collide with a reserved SD-JWT member (iss, sub, aud, exp, nbf, iat, jti, cnf, vct, status, _sd, _sd_alg) — overwriting cnf would break holder binding and overwriting status would break revocation.

Verify it yourself — the open rail

ORBIS is a public trust rail, not a platform with an API. The measure of that is not how many customers integrate: it is whether a credential ORBIS issued can still be verified by a stranger — with no ORBIS account, no ORBIS code and no ORBIS permission, ideally even when ORBIS is unreachable. Everything in this section exists to make that testable rather than promised.

The SDK is optional. The vectors are the contract.

Publishing an SDK alone would make ORBIS a soft chokepoint: everyone would run ORBIS’s code, so ORBIS’s bugs would quietly become the specification. Publishing an SDK plus conformance vectors makes ORBIS a spec. Write your verifier in Rust, Go, Java or anything else; if it passes the vectors it is as correct as ours, and you never asked us anything to find that out.

The library, if you want it

@orbis-id/verifier is a zero-dependency verifier that runs in Node and in a browser and performs no I/O at all — you fetch the artifacts, it checks them. That is what makes verification keep working when ORBIS is down. Published It is on npm as @orbis-id/verifier under Apache-2.0, and the conformance runner can be used without installing anything: npx orbis-conformance --cmd <your-verifier>.

Verify a presentation (TypeScript)
// Verify an ORBIS credential offline. Zero dependencies, Node and browser.
// Using this package is OPTIONAL: the conformance vectors are the contract.
import { verifyPresentation } from '@orbis-id/verifier';

const didDocument = await (await fetch('https://orbis.id/.well-known/did.json')).json();
const token       = await (await fetch('https://orbis.id/status/1')).text();

const outcome = await verifyPresentation({
  presentation,                                        // the vp_token you received
  didDocument,                                         // the issuer's published did:web document
  statusList: { uri: 'https://orbis.id/status/1', token },
  expected: { audience: 'https://your-app.example', nonce: yourNonce },
});

if (outcome.verdict === 'accept') use(outcome.claims);

// THREE verdicts, not two. "indeterminate" means the check could not be made —
// the credential names a status list and you supplied none, OR it carries no
// "status" claim at all and so declares no revocation mechanism to read. Both
// answer the same way: a credential whose revocation state was never read is
// never accepted. It is never an accept, and treating it as a reject is YOUR
// policy decision to make out loud.
// outcome.checks is the full ledger: ten links, each with its own status
// (passed | failed | not_applicable | not_performed). The verdict is derived
// from that ledger, so a check that did not run can never be reported as one
// that passed.

It never silently passes a check it did not perform

A verifier that reports “valid” for a credential whose revocation state it could not read is worse than one that reports nothing. So the result is not a boolean: every one of the ten links carries its own status, and the verdict is derived from that ledger. A missing input yields indeterminate — a third answer you must handle deliberately.

One limitation is stated rather than hidden: only top-level named disclosures are checked. A credential using nested or array-element _sd is refused with unsupported_disclosure_structure, never partially verified and reported as accepted.

Self-certify against the published vectors

GET /conformance/vectors is public, CORS-open and requires no credential — permanently, and enforced by a test rather than by intent. It publishes sixteen self-describing vectors: a presentation, the issuer’s did:web document and a signed status list, plus what each one proves and the verdict a correct verifier MUST reach. Everything is inert data: run them with the network switched off.

Prove your own implementation
# 1 · Fetch the vectors. No account, no key, no permission. CORS-open.
curl -sS https://orbis.id/conformance/vectors > vectors.json

# 2 · Point the runner at YOUR implementation, in any language.
npx orbis-conformance --vectors ./vectors.json --cmd './target/release/my-verifier'

# Your command is spawned once and speaks newline-delimited JSON:
#   -> stdin   {"id":"valid","input":{ ...the vector's `input` object verbatim... }}
#   <- stdout  {"id":"valid","verdict":"accept","failure":null,"claims":{...}}
# "verdict" is accept | reject | indeterminate and is the only required field
# besides "id". Exit code 0 when every vector passes, 1 otherwise.

# 3 · Read the report.
#   level 1 (verdict)        16/16  CONFORMANT
#   level 2 (verdict+reason) 16/16  CONFORMANT
# Level 1 is the BINDING contract: the verdict alone. Level 2 also matches the
# failure code and the disclosed claims — recommended, never required. Forcing
# you to adopt ORBIS's enum names would be the chokepoint this exists to prevent.
The published vectors and the property each one measures.
VectorRequired verdictWhat a correct answer proves
validacceptThe whole chain is walked; exactly the disclosed claims are readable and the withheld one never appears.
revokedrejectRevocation is really read from the signed list. Signature and holder binding are still sound.
status-list-withheldindeterminateAn unanswerable question is not an accept. Catches a verifier that fails open when it cannot reach the list.
tampered-issuer-signaturerejectThe issuer signature is verified, not merely parsed. The key binding is re-signed so nothing else can catch it by accident.
wrong-issuer-keyrejectThe key is bound by the signature, not by the identifier — and the status list is signed by the same wrong key, so no incidental catch remains.
stolen-credentialrejectHolder binding is enforced. Without it a credential works for whoever copies it.
wrong-credential-typerejectThe vct you demanded is actually compared.
required-claim-withheldrejectA claim the holder withheld is genuinely absent, and the verifier says so instead of proceeding.
did-document-not-boundrejectThe document is bound to the identifier it claims; otherwise any host could publish keys for any issuer.
forged-disclosurerejectA holder cannot invent a claim the issuer never signed, even with a perfectly valid key binding.
disclosure-set-alteredrejectsd_hash is checked, so the disclosure set cannot be edited in transit.
audience-mismatchrejectA presentation meant for another relying party is refused.
nonce-replayrejectA previously captured presentation cannot answer a fresh challenge.
expired-status-listrejectA stale revocation answer is not an answer.
status-list-substitutedrejectThe list is bound to the URI the credential names — otherwise swapping which list is served un-revokes everything.
malformed-presentationrejectGarbage gets a definite verdict rather than a crash or a coerced decision.

The vectors are minted, not committed — and that is deliberate

A signed SD-JWT VC and a statuslist+jwt are high-entropy eyJ… strings that a secret scanner matches on sight (measured: four jwt findings on an emitted set), and ORBIS reserves its scanner allowlist for verified false positives. So the repository holds a deterministic recipe and the bytes are produced on demand. Never pin the bytes. schema versions the shape, setVersion versions the contract, and digest names the exact bytes a given conformance report was run against. There is no PII, only RFC 6761 .example hosts, and no JWK private scalar anywhere.

The vectors are mutation-tested: a second, fully independent implementation is broken twelve different ways — skipping the issuer signature, skipping revocation, failing open, ignoring the audience, and so on — and every one must be caught at conformance level 1. A vector set that cannot fail a broken verifier would prove nothing, so that is a build gate rather than a claim.

The verification surface is not versioned by ORBIS. There is no /v1 on any trust artifact and there never will be: did:web, OID4VCI metadata, SD-JWT VC and Token Status List are versioned by their own standards, and the vectors by their own setVersion. The participation plane is where ORBIS versioning belongs.

Is orbis.id actually running that code?

Open code proves the design. It does not prove that the machine answering this request is running it — and that is the step most “open source for credibility” stories skip. GET /build is where ORBIS stops asking to be taken at its word: it names the commit this deployment was built from, and it recomputes the digest of its own files on disk rather than reciting a string someone typed at build time.

1 · Ask the running instance
# 1 · Ask the running instance what it is. No account, no key.
curl -s https://orbis.id/build

# {
#   "commit": "<40-hex>",              the commit this artifact was built from
#   "sourceDigest": "sha256:...",      the content digest recorded AT BUILD TIME
#   "observedSourceDigest": "sha256:...",  recomputed from the files on disk NOW
#   "matches": true|false,
#   "divergence": { "missing": 0, "changed": 0, "extra": 0 },
#   "attestation": { "available": false, "reason": "..." }
# }
#
# Read "attestation" FIRST. It says whether anything outside this server signs
# the commit above, and today the honest answer is no.
2 · Recompute the digest from the commit &mdash; coreutils only
# 2 · Recompute the digest from the commit. Needs no ORBIS software:
#      coreutils is the entire trust base.
git checkout <commit-from-step-1>

LC_ALL=C find .orbis package.json package-lock.json tsconfig.json scripts src \
  -type f ! -name '*.test.ts' ! -name '.DS_Store' \
  | LC_ALL=C sort | xargs -n1 sha256sum | sha256sum

# The output must equal "sourceDigest". It is a sha256 over the sorted
# "<sha256>  <path>" lines of every file the deploy artifact contains — so it
# is unaffected by mtimes, file order, permissions or which zip built it.
3 · Rebuild the artifact byte for byte
# 3 · Rebuild the deploy artifact itself, byte for byte.
npx tsx scripts/build-deploy-zip.ts --out deploy.zip
sha256sum deploy.zip

# Must equal the archive digest printed in that release's deploy run.
# The builder refuses a working tree that is not the commit, normalises every
# entry's timestamp to the commit's own, and drops the extra fields that record
# the building machine's access times and uid/gid. What it cannot control is
# the DEFLATE implementation: Info-ZIP 3.0 is what GitHub's runners and macOS
# ship, and a different compressor may produce a different archive digest for
# an identical source tree. That is why step 2, and not this step, is the one
# that decides.
4 · Check the signature
# 4 · Check who signed it — and today, find out that nobody did.
gh attestation verify deploy.zip --repo Jonatans11/OID

# GitHub refuses to create artifact attestations for user-owned PRIVATE
# repositories. Measured, not assumed:
#
#   POST /repos/Jonatans11/OID/attestations
#   422 "Feature not available for user-owned private repositories.
#        To enable this feature, please make this repository public."
#
# The deploy workflow already carries a fully configured attest step, gated on
# that one fact. Until it lifts, steps 1-3 tie the running bytes to a commit;
# nothing ties them to GitHub's word that GitHub built them.

Exactly where this chain still breaks

A verification recipe that overstates itself is worth less than none, so the gaps are named rather than left for you to find.

  • Nothing signs the artifact yet. GitHub will not attest builds from a user-owned private repository, so today /build’s commit is a claim by the server — checkable against the source, not signed by a third party. The attestation field says so on every response.
  • The repository is private. Steps 2 and 3 need the source, so today they are checks an auditor with access can run rather than ones an anonymous stranger can. What is published today is @orbis-id/verifier and the sixteen conformance vectors above; the server is not.
  • Dependencies are installed on the host, not shipped. The artifact carries package-lock.json — which the digest covers — and the platform installs from it on the App Service. The digest therefore proves which dependency graph was requested, not which bytes the registry served.
  • A deployment can be a superset of its commit. The platform is deployed by overlaying a zip onto the running directory, which never removes what is already there. That is what divergence.extra counts, and it is a real number on a real system, not a hypothetical.

Webhooks

ORBIS delivers events by HTTP POST with an HMAC-SHA256 signature over the raw body. Endpoints are registered and their secrets rotated from the Back Office integration desk (/api/admin/integration/webhooks); deliveries are retried and dead-lettered, and the destination URL is re-validated against the SSRF allowlist on every attempt, not just at registration — a URL can outlive a topology change.

Signature verification (Node)
// Verify an ORBIS webhook. Compare bytes, never strings, and never trust
// an unsigned body. Headers ORBIS sends:
//   x-orbis-signature: sha256=<hex>   HMAC-SHA256 of the RAW body, hex
//   x-orbis-event:     <event type>
//   x-orbis-delivery:  <delivery id>  (idempotency key — dedupe on this)
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verifyOrbisWebhook(rawBody: string, header: string | null, secret: string): boolean {
  if (!header) return false;
  const m = /^sha256=([0-9a-f]{64})$/i.exec(header.trim());
  if (!m) return false;
  const expected = Buffer.from(createHmac('sha256', secret).update(rawBody, 'utf8').digest('hex'), 'hex');
  const given = Buffer.from(m[1].toLowerCase(), 'hex');
  return expected.length === given.length && timingSafeEqual(expected, given);
}

// Read the RAW body. If your framework has already parsed and re-serialised
// the JSON, the bytes have changed and the signature will never match.

Three ways to get this wrong

  • Comparing strings. Use a constant-time comparison over decoded bytes, as above.
  • Signing a re-serialised body. The HMAC is over the bytes ORBIS sent. Capture the raw body before any JSON middleware touches it.
  • Not deduplicating. Retries are real. x-orbis-delivery is your idempotency key.

A redirect is never followed on delivery: a 3xx is recorded as a failure rather than chased to a host the allowlist did not approve.

Sandbox & the demo-tenant law

There is no separate sandbox host. You experiment inside the demo namespace, and that namespace is marked — permanently, in code.

The law

  • A demonstration organization’s slug starts with demo-.
  • Its display name ends with “ (Demo)”.
  • A demo tenant is never un-marked — not in the console, not in the wallet, not in an export.
  • Fixture data always answers fixtures: true on the wallet bridge. That flag is an honesty marker, not a debug switch.
  • Real organizations are created only through the provisioning ceremony, never by editing a demo one into place.

The platform enforces the namespace rather than trusting it: the off-boarding and demo-hygiene surfaces select tenants by that exact predicate (demo- prefix or an explicit demo marker on the profile), so a sweep can never take a real organization with it — and can never miss a demo one that was renamed to look real.

Nothing on this page has been executed against the production origin, and no example value here is live data. When you run the quickstarts, run them against a demo- organization first.

Live vs. planned — what you can actually integrate with today

The table below is about the integration surface, not about the business. Every row is stated at the strength the platform’s own records support it, and a route being mounted is never by itself evidence that a capability is in production use.

Integration surfaces by state. “Mounted” means this deployment serves the route; it is not a usage claim.
SurfaceStateWhat that means for you
Public trust artifacts — did:web, OID4VCI metadata, status lists, /manifest Live Standards-defined and CORS-open. Safe to build against; verified in this repository’s conformance suite.
Issuance over OID4VCI — /token, /credential Live The wire is the specification. Shapes above are read off the server source.
Hosted verification — /verify/*, /oid4vp/* Live Zero-account verification with caller-supplied nonce and audience binding.
Tenant provisioning & credential types — /api/tenants, /api/types, /api/offers Live Operator bearer required. Request shapes are specified above; several success bodies are console view models and are marked accordingly.
Wallet bridge — /api/wallet/*, /member/* Live Holder-signed. The item wire shape is pinned by the demo-bridge contract document, which is the authority — not this page.
Deposit rail — POST /api/tenants/{tenant}/bills Built & mounted Request and response shapes are contracted (contracts 0.3.0) and specified above. Whether a given deployment has an external caller is a deployment fact, not an API fact.
Webhook event plane Built & mounted HMAC-signed, retried, dead-lettered, SSRF-guarded. Register endpoints from the Back Office; the verification recipe above is the whole contract.
Holder directory — /directory/* Conditional Answers 503 directory_unconfigured unless the deployment is configured with a directory pepper. Its shapes live in the directory contract document, not here.
Back Office data plane — /api/admin/* Internal Mounted and role-gated, and the roles are published here. The response shapes are not a partner contract and are marked x-orbis-undocumented. Do not build against them.
Harness admin — /harness/* Not on this image Compile-gated to the test-harness build and absent from production, so it is deliberately excluded from the document rather than described as if it existed.
Partner self-serve pages — /partners, the four profile paths, apply, status Live Served by this deployment. The profiles and the eight-state pipeline are rendered from the platform’s own definitions, so they cannot drift from the operator desk’s.
Partner application intake — POST /api/partners/applications (public) Live Public and unauthenticated, and it records into the operator Partners desk’s own pipeline — the same record, the same state machine, the same history an operator-typed application gets. It can create stage applied and nothing else: no tenant, token, scope or stage is nameable from it. Bounded, strict-schema and rate-limited per caller; not CORS-open.
Certification readiness board for partners Built & mounted The engine runs. It is reached as the four-eyes action partner.certify-run from the operator Partners desk — not as a partner-callable route, so there is nothing here for you to integrate against. A run produces a recorded verdict per exercise; the readiness board reads the latest run. Exercises that need an effect this deployment cannot supply report could not run, never a pass.
Conformance vectors — GET /conformance/vectors Live Public, CORS-open, no credential ever. Sixteen vectors, mutation-tested against twelve broken verifiers. See the open rail. The bytes are minted per process and must not be pinned; the contract is setVersion plus each vector’s required verdict.
Verifier SDK — @orbis-id/verifier Published It is on npm as @orbis-id/verifier under Apache-2.0, and the conformance runner can be used without installing anything: npx orbis-conformance --cmd <your-verifier>. Zero dependencies, Node and browser, no I/O. Using it remains optional: the vectors are the contract, and a verifier that passes them is conformant whatever it is written in. Verified against this deployment’s live vector set at publication — 16/16 at level 1 and level 2.
Per-key rate limits and RateLimit headers Live Participation plane only. A request authenticated by an operator key is metered against that key’s declared rate class and answers the IETF standard fields (RateLimit-Policy, RateLimit, and Retry-After on a 429) — not proprietary X-Orbis-* names. Budgets belong to keys, never to source addresses. The verification plane is never metered: no identity, no per-key accounting, no upsell — see the open rail. Human console sessions carry no quota.
The /v1 prefix Partial 7 participation operations answer under /v1 today, each an additive alias of its unversioned path: same handler, same auth, same body. The set is rendered from public-api-v1.ts, never typed here, so this row cannot drift from what the router serves. It does not yet cover the whole participation plane, and no unversioned path is being retired.
Self-service API keys Partial All three phases are built and the routes are mounted: a partner signs in with a code sent to the contact email on their approved application, then mints, lists, rotates and revokes keys inside the grant an operator attached at go-live. The tenant comes from that stored grant and is never read from the request, so a key cannot be pointed at another customer. What is still missing is the customer record the grant should hang off — grants are keyed by application reference today — and the portal screens a partner would use, so in practice an operator still attaches the grant before any of this is reachable for a given partner.
Issuer and wallet SDKs Not built Only the verifier SDK is published. There is no library for issuing a credential and none for holding one, so both of those are yours to write against the wire formats above.

What this page does not claim

  • No throughput, uptime, latency or customer numbers — none are measured here.
  • No promise that an x-orbis-undocumented shape will stay as you find it.
  • No SLA. No versioning guarantee beyond the document’s own info.version.
  • The wallet is web-only; there are no native app-store builds, and account recovery is not implemented.

Reference

Every operation this deployment mounts, grouped as the document groups them. Paths are as served on the public origin. For request and response detail, read /openapi.json — this table and that document are generated from the same route inventory in the same build.

Human surfaces (HTML) 22

MethodPathOperationAuthNotes
GET / Public front page public
GET /admin Back Office Entra session
GET /capabilities Public capability register public
GET /console Identity Console Entra session
GET /demo Investor-demo console public
GET /developers Developer portal public
GET /docs How ORBIS.ID works public
GET /for-business For Business — audience page public
GET /for-cities For Cities — audience page public
GET /for-government For Government — audience page public
GET /for-people For People — audience page public
GET /openapi.json This document public
GET /partners Partners — start integrating public
GET /partners/apply Partner application form public
GET /partners/data-partner Partner path — data partner public
GET /partners/issuer Partner path — issuer public
GET /partners/relying-party Partner path — relying party / verifier public
GET /partners/status Partner application status lookup public
GET /partners/wallet Partner path — wallet public
GET /regulations Regulations and GDPR operations public
GET /standards Standards and conformance public
GET /verify Hosted verification page public

Trust artifacts 9

MethodPathOperationAuthNotes
GET /.well-known/did.json Platform did:web document public shape not published
GET /.well-known/security.txt Security contact (RFC 9116) public shape not published
GET /build Build provenance of the running instance public
GET /conformance/vectors Open Rail conformance vector set public
GET /manifest Tenant manifest (wallet white-label config) public
GET /status/{seq} Signed status list by sequence (platform issuer) public
GET /status/1 Signed status list (list 1, live) public
GET /t/{tenant}/did.json Tenant did:web document public shape not published
GET /t/{tenant}/status/{seq} Signed status list by sequence (tenant issuer) public

Issuance — OID4VCI 7

MethodPathOperationAuthNotes
GET /.well-known/oauth-authorization-server OAuth 2.0 authorization server metadata (RFC 8414) public
GET /.well-known/openid-credential-issuer OID4VCI credential issuer metadata public
POST /api/offers Create a credential offer operator bearer
POST /api/revoke Revoke an issued credential operator bearer shape not published
POST /credential Collect the credential OID4VCI token
POST /token Redeem a pre-authorized code for an access token public
POST /v1/offers Create a credential offer operator bearer

Back office 136

MethodPathOperationAuthNotes
GET /api/admin/activity GET /api/admin/activity operator bearer shape not published internal
GET /api/admin/analytics GET /api/admin/analytics operator bearer shape not published internal
GET /api/admin/approvals GET /api/admin/approvals operator bearer shape not published internal
POST /api/admin/approvals POST /api/admin/approvals operator bearer shape not published internal
GET /api/admin/approvals/{approvalId} GET /api/admin/approvals/{approvalId} operator bearer shape not published internal
POST /api/admin/approvals/{approvalId}/approve POST /api/admin/approvals/{approvalId}/approve operator bearer shape not published internal
POST /api/admin/approvals/{approvalId}/reject POST /api/admin/approvals/{approvalId}/reject operator bearer shape not published internal
POST /api/admin/approvals/{approvalId}/withdraw POST /api/admin/approvals/{approvalId}/withdraw operator bearer shape not published internal
GET /api/admin/audit GET /api/admin/audit operator bearer shape not published internal
GET /api/admin/audit/{seq} GET /api/admin/audit/{seq} operator bearer shape not published internal
POST /api/admin/compliance/accountability/attest POST /api/admin/compliance/accountability/attest operator bearer shape not published internal
GET /api/admin/compliance/accountability/attestations GET /api/admin/compliance/accountability/attestations operator bearer shape not published internal
GET /api/admin/compliance/accountability/role-history GET /api/admin/compliance/accountability/role-history operator bearer shape not published internal
GET /api/admin/compliance/accountability/role-separation GET /api/admin/compliance/accountability/role-separation operator bearer shape not published internal
POST /api/admin/compliance/dsar/export POST /api/admin/compliance/dsar/export operator bearer shape not published internal
POST /api/admin/compliance/dsar/lookup POST /api/admin/compliance/dsar/lookup operator bearer shape not published internal
POST /api/admin/compliance/dsar/negative-response POST /api/admin/compliance/dsar/negative-response operator bearer shape not published internal
GET /api/admin/compliance/dsar/requests GET /api/admin/compliance/dsar/requests operator bearer shape not published internal
GET /api/admin/compliance/erasure/requests GET /api/admin/compliance/erasure/requests operator bearer shape not published internal
POST /api/admin/compliance/erasure/requests POST /api/admin/compliance/erasure/requests operator bearer shape not published internal
GET /api/admin/compliance/erasure/requests/{requestId} GET /api/admin/compliance/erasure/requests/{requestId} operator bearer shape not published internal
POST /api/admin/compliance/erasure/requests/{requestId}/refuse POST /api/admin/compliance/erasure/requests/{requestId}/refuse operator bearer shape not published internal
POST /api/admin/compliance/erasure/requests/{requestId}/verify POST /api/admin/compliance/erasure/requests/{requestId}/verify operator bearer shape not published internal
GET /api/admin/compliance/incidents GET /api/admin/compliance/incidents operator bearer shape not published internal
POST /api/admin/compliance/incidents POST /api/admin/compliance/incidents operator bearer shape not published internal
GET /api/admin/compliance/incidents/{incidentId} GET /api/admin/compliance/incidents/{incidentId} operator bearer shape not published internal
POST /api/admin/compliance/incidents/{incidentId} POST /api/admin/compliance/incidents/{incidentId} operator bearer shape not published internal
GET /api/admin/compliance/incidents/{incidentId}/affected-scope GET /api/admin/compliance/incidents/{incidentId}/affected-scope operator bearer shape not published internal
GET /api/admin/compliance/meta-inventory GET /api/admin/compliance/meta-inventory operator bearer shape not published internal
GET /api/admin/compliance/program GET /api/admin/compliance/program operator bearer shape not published internal
POST /api/admin/compliance/retention/purge-run POST /api/admin/compliance/retention/purge-run operator bearer shape not published internal
GET /api/admin/compliance/retention/rules GET /api/admin/compliance/retention/rules operator bearer shape not published internal
POST /api/admin/compliance/retention/rules POST /api/admin/compliance/retention/rules operator bearer shape not published internal
GET /api/admin/compliance/ropa GET /api/admin/compliance/ropa operator bearer shape not published internal
GET /api/admin/compliance/ropa/purposes GET /api/admin/compliance/ropa/purposes operator bearer shape not published internal
POST /api/admin/compliance/ropa/type-purpose POST /api/admin/compliance/ropa/type-purpose operator bearer shape not published internal
GET /api/admin/credential GET /api/admin/credential operator bearer shape not published internal
GET /api/admin/credential-type GET /api/admin/credential-type operator bearer shape not published internal
GET /api/admin/credential-types GET /api/admin/credential-types operator bearer shape not published internal
GET /api/admin/credentials GET /api/admin/credentials operator bearer shape not published internal
GET /api/admin/holder GET /api/admin/holder operator bearer shape not published internal
GET /api/admin/holders GET /api/admin/holders operator bearer shape not published internal
GET /api/admin/integration/contracts GET /api/admin/integration/contracts operator bearer shape not published internal
POST /api/admin/integration/contracts/handshake POST /api/admin/integration/contracts/handshake operator bearer shape not published internal
GET /api/admin/integration/deliveries GET /api/admin/integration/deliveries operator bearer shape not published internal
GET /api/admin/integration/deliveries/{deliveryId} GET /api/admin/integration/deliveries/{deliveryId} operator bearer shape not published internal
POST /api/admin/integration/deliveries/{deliveryId}/redrive POST /api/admin/integration/deliveries/{deliveryId}/redrive operator bearer shape not published internal
GET /api/admin/integration/deposits GET /api/admin/integration/deposits operator bearer shape not published internal
GET /api/admin/integration/deposits/{depositId} GET /api/admin/integration/deposits/{depositId} operator bearer shape not published internal
GET /api/admin/integration/deposits/integrity GET /api/admin/integration/deposits/integrity operator bearer shape not published internal
GET /api/admin/integration/key-options GET /api/admin/integration/key-options operator bearer shape not published internal
GET /api/admin/integration/provisioning GET /api/admin/integration/provisioning operator bearer shape not published internal
POST /api/admin/integration/provisioning POST /api/admin/integration/provisioning operator bearer shape not published internal
GET /api/admin/integration/provisioning/{requestId} GET /api/admin/integration/provisioning/{requestId} operator bearer shape not published internal
POST /api/admin/integration/provisioning/{requestId}/reject POST /api/admin/integration/provisioning/{requestId}/reject operator bearer shape not published internal
GET /api/admin/integration/tokens GET /api/admin/integration/tokens operator bearer shape not published internal
POST /api/admin/integration/tokens POST /api/admin/integration/tokens operator bearer shape not published internal
POST /api/admin/integration/tokens/{tokenId}/revoke POST /api/admin/integration/tokens/{tokenId}/revoke operator bearer shape not published internal
POST /api/admin/integration/tokens/{tokenId}/rotate POST /api/admin/integration/tokens/{tokenId}/rotate operator bearer shape not published internal
GET /api/admin/integration/webhooks GET /api/admin/integration/webhooks operator bearer shape not published internal
POST /api/admin/integration/webhooks POST /api/admin/integration/webhooks operator bearer shape not published internal
GET /api/admin/integration/webhooks/{webhookId} GET /api/admin/integration/webhooks/{webhookId} operator bearer shape not published internal
POST /api/admin/integration/webhooks/{webhookId}/active POST /api/admin/integration/webhooks/{webhookId}/active operator bearer shape not published internal
POST /api/admin/integration/webhooks/{webhookId}/rotate-secret POST /api/admin/integration/webhooks/{webhookId}/rotate-secret operator bearer shape not published internal
GET /api/admin/integration/webhooks/coverage GET /api/admin/integration/webhooks/coverage operator bearer shape not published internal
GET /api/admin/me GET /api/admin/me operator bearer shape not published internal
GET /api/admin/offboarding GET /api/admin/offboarding operator bearer shape not published internal
GET /api/admin/ops/audit GET /api/admin/ops/audit operator bearer shape not published internal
GET /api/admin/ops/azure GET /api/admin/ops/azure operator bearer shape not published internal
POST /api/admin/ops/backup POST /api/admin/ops/backup operator bearer shape not published internal
GET /api/admin/ops/backup-drills GET /api/admin/ops/backup-drills operator bearer shape not published internal
GET /api/admin/ops/backups GET /api/admin/ops/backups operator bearer shape not published internal
GET /api/admin/ops/comms-health GET /api/admin/ops/comms-health operator bearer shape not published internal
GET /api/admin/ops/demo-hygiene GET /api/admin/ops/demo-hygiene operator bearer shape not published internal
GET /api/admin/ops/did-drift GET /api/admin/ops/did-drift operator bearer shape not published internal
GET /api/admin/ops/expiry GET /api/admin/ops/expiry operator bearer shape not published internal
POST /api/admin/ops/expiry-dates POST /api/admin/ops/expiry-dates operator bearer shape not published internal
POST /api/admin/ops/fixtures POST /api/admin/ops/fixtures operator bearer shape not published internal
GET /api/admin/ops/health GET /api/admin/ops/health operator bearer shape not published internal
GET /api/admin/ops/keys GET /api/admin/ops/keys operator bearer shape not published internal
GET /api/admin/ops/revocations GET /api/admin/ops/revocations operator bearer shape not published internal
POST /api/admin/ops/revoke POST /api/admin/ops/revoke operator bearer shape not published internal
GET /api/admin/ops/rotation GET /api/admin/ops/rotation operator bearer shape not published internal
GET /api/admin/ops/status-integrity GET /api/admin/ops/status-integrity operator bearer shape not published internal
GET /api/admin/ops/status-lists GET /api/admin/ops/status-lists operator bearer shape not published internal
GET /api/admin/ops/status-lists/{issuerDid} GET /api/admin/ops/status-lists/{issuerDid} operator bearer shape not published internal
GET /api/admin/ops/stuck-transactions GET /api/admin/ops/stuck-transactions operator bearer shape not published internal
GET /api/admin/ops/trust GET /api/admin/ops/trust operator bearer shape not published internal
POST /api/admin/ops/trust/add POST /api/admin/ops/trust/add operator bearer shape not published internal
POST /api/admin/ops/trust/restore POST /api/admin/ops/trust/restore operator bearer shape not published internal
POST /api/admin/ops/trust/suspend POST /api/admin/ops/trust/suspend operator bearer shape not published internal
GET /api/admin/ops/wire-funnels GET /api/admin/ops/wire-funnels operator bearer shape not published internal
GET /api/admin/orgs GET /api/admin/orgs operator bearer shape not published internal
POST /api/admin/orgs POST /api/admin/orgs operator bearer shape not published internal
GET /api/admin/orgs/{slug} GET /api/admin/orgs/{slug} operator bearer shape not published internal
GET /api/admin/orgs/{slug}/admins GET /api/admin/orgs/{slug}/admins operator bearer shape not published internal
POST /api/admin/orgs/{slug}/admins POST /api/admin/orgs/{slug}/admins operator bearer shape not published internal
POST /api/admin/orgs/{slug}/admins/remove POST /api/admin/orgs/{slug}/admins/remove operator bearer shape not published internal
GET /api/admin/orgs/{slug}/domain GET /api/admin/orgs/{slug}/domain operator bearer shape not published internal
POST /api/admin/orgs/{slug}/domain POST /api/admin/orgs/{slug}/domain operator bearer shape not published internal
POST /api/admin/orgs/{slug}/domain/activate POST /api/admin/orgs/{slug}/domain/activate operator bearer shape not published internal
GET /api/admin/orgs/{slug}/domain/document GET /api/admin/orgs/{slug}/domain/document operator bearer shape not published internal
POST /api/admin/orgs/{slug}/domain/revoke POST /api/admin/orgs/{slug}/domain/revoke operator bearer shape not published internal
POST /api/admin/orgs/{slug}/domain/verify POST /api/admin/orgs/{slug}/domain/verify operator bearer shape not published internal
GET /api/admin/orgs/{slug}/lifecycle GET /api/admin/orgs/{slug}/lifecycle operator bearer shape not published internal
POST /api/admin/orgs/{slug}/reactivate POST /api/admin/orgs/{slug}/reactivate operator bearer shape not published internal
POST /api/admin/orgs/{slug}/suspend POST /api/admin/orgs/{slug}/suspend operator bearer shape not published internal
POST /api/admin/orgs/preview-branding POST /api/admin/orgs/preview-branding operator bearer shape not published internal
GET /api/admin/partners GET /api/admin/partners operator bearer shape not published internal
POST /api/admin/partners POST /api/admin/partners operator bearer shape not published internal
GET /api/admin/partners/{id} GET /api/admin/partners/{id} operator bearer shape not published internal
POST /api/admin/partners/{id}/link-tenant POST /api/admin/partners/{id}/link-tenant operator bearer shape not published internal
POST /api/admin/partners/{id}/stage POST /api/admin/partners/{id}/stage operator bearer shape not published internal
GET /api/admin/partners/portfolio GET /api/admin/partners/portfolio operator bearer shape not published internal
GET /api/admin/people/coin-ledger GET /api/admin/people/coin-ledger operator bearer shape not published internal
GET /api/admin/people/consent GET /api/admin/people/consent operator bearer shape not published internal
GET /api/admin/people/economy GET /api/admin/people/economy operator bearer shape not published internal
GET /api/admin/people/governance GET /api/admin/people/governance operator bearer shape not published internal
POST /api/admin/people/governance POST /api/admin/people/governance operator bearer shape not published internal
GET /api/admin/people/members GET /api/admin/people/members operator bearer shape not published internal
GET /api/admin/people/operators GET /api/admin/people/operators operator bearer shape not published internal
POST /api/admin/people/operators POST /api/admin/people/operators operator bearer shape not published internal
GET /api/admin/people/operators/{username} GET /api/admin/people/operators/{username} operator bearer shape not published internal
POST /api/admin/people/operators/{username}/enable POST /api/admin/people/operators/{username}/enable operator bearer shape not published internal
POST /api/admin/people/operators/{username}/entra POST /api/admin/people/operators/{username}/entra operator bearer shape not published internal
POST /api/admin/people/operators/{username}/password POST /api/admin/people/operators/{username}/password operator bearer shape not published internal
POST /api/admin/people/operators/{username}/remove POST /api/admin/people/operators/{username}/remove operator bearer shape not published internal
POST /api/admin/people/operators/{username}/roles POST /api/admin/people/operators/{username}/roles operator bearer shape not published internal
POST /api/admin/people/operators/{username}/suspend POST /api/admin/people/operators/{username}/suspend operator bearer shape not published internal
GET /api/admin/search GET /api/admin/search operator bearer shape not published internal
GET /api/admin/support/credential/{statusIndex} GET /api/admin/support/credential/{statusIndex} operator bearer shape not published internal
GET /api/admin/support/lookup GET /api/admin/support/lookup operator bearer shape not published internal
GET /api/admin/support/timeline/{subject} GET /api/admin/support/timeline/{subject} operator bearer shape not published internal
GET /api/admin/today GET /api/admin/today operator bearer shape not published internal
GET /api/admin/types GET /api/admin/types operator bearer shape not published internal
POST /api/admin/types POST /api/admin/types operator bearer shape not published internal

Operator console 19

MethodPathOperationAuthNotes
POST /api/agent/activity Report an agent’s own tool-call activity operator bearer shape not published internal
GET /api/compliance Compliance summary operator bearer shape not published internal
POST /api/operators Create an operator account operator bearer shape not published internal
POST /api/operators/enable Reinstate a suspended operator account operator bearer shape not published internal
POST /api/operators/remove Remove an operator account operator bearer shape not published internal
POST /api/operators/roles Grant xFIN / platform financial roles operator bearer shape not published internal
POST /api/operators/suspend Suspend an operator account operator bearer shape not published internal
GET /api/org-channels Organization comms channels (read) operator bearer shape not published internal
POST /api/org-channels/{tenant}/{accountId}/accept Apply an accept decision to an org channel operator bearer shape not published internal
POST /api/org-channels/{tenant}/{accountId}/decline Apply a decline decision to an org channel operator bearer shape not published internal
POST /api/org-channels/{tenant}/{accountId}/revoke Apply a revoke decision to an org channel operator bearer shape not published internal
POST /api/org-channels/request Request an organization comms channel operator bearer shape not published internal
GET /api/qr Render a string as an SVG QR code public
GET /api/state Console state snapshot operator bearer shape not published internal
POST /api/trust/remove Remove an issuer from the trust registry operator bearer shape not published internal
POST /api/trust/restore Restore an issuer to the trust registry operator bearer shape not published internal
POST /api/wallets Create a console-side test wallet operator bearer shape not published internal
POST /api/wallets/accept Console test wallet accepts an offer operator bearer shape not published internal
POST /api/wallets/respond Console test wallet answers a presentation request operator bearer shape not published internal

Operator sign-in 6

MethodPathOperationAuthNotes
POST /api/login Exchange operator credentials for a session bearer public
GET /auth/api-token Exchange an Entra sign-in for an operator API session Entra session shape not published
GET /auth/callback Microsoft Entra redirect target public config-dependent
GET /auth/login Begin Microsoft Entra sign-in public config-dependent
GET /auth/logout End the Entra session Entra session config-dependent
GET /auth/whoami Show the caller’s own Entra object id and link status Entra session shape not published config-dependent

Membership & governance 8

MethodPathOperationAuthNotes
POST /api/members/enroll Enroll a member (operator plane) operator bearer shape not published internal
POST /api/members/update Update a member record operator bearer shape not published internal
POST /api/proposals Create a governance proposal operator bearer shape not published internal
POST /api/proposals/vote Cast a vote (operator plane) operator bearer shape not published internal
POST /member/enroll Enroll as a member using a verified presentation public
GET /member/me The holder’s own memberships and open proposals holder JWS
POST /member/vote Cast a governance vote holder JWS shape not published
GET /v1/member/me The holder’s own memberships and open proposals holder JWS

Partner onboarding (public) 21

MethodPathOperationAuthNotes
POST /api/partners/applications Apply to become an ORBIS.ID partner public
POST /api/partners/applications/status Read your own partner application public
GET /api/partners/keys List this partner’s own keys operator bearer shape not published
POST /api/partners/keys Mint a key inside the grant operator bearer shape not published
POST /api/partners/keys/{id}/revoke Revoke one of this partner’s keys operator bearer shape not published
POST /api/partners/keys/{id}/rotate Rotate one of this partner’s keys operator bearer shape not published
POST /api/partners/session/start Begin a partner key-management session public shape not published
POST /api/partners/session/verify Exchange the code for a session public shape not published
GET /portal The partner portal — where you are in the pipeline operator bearer
GET /portal/keys The partner portal — your API keys operator bearer
POST /portal/keys/mint Issue a key from the portal operator bearer
POST /portal/keys/revoke Revoke a key from the portal operator bearer
POST /portal/keys/rotate Rotate a key from the portal operator bearer
GET /portal/signin The partner portal sign-in page public
POST /portal/signin Ask the portal for a sign-in code public
POST /portal/signout End the browser session operator bearer
GET /portal/team The partner portal — who is on this account operator bearer
POST /portal/team/add Add a contact to the partner account operator bearer
POST /portal/team/remove Remove a contact from the partner account operator bearer
POST /portal/team/role Change a contact’s role operator bearer
POST /portal/verify Exchange the code for a browser session public

Wallet bridge (holder) 10

MethodPathOperationAuthNotes
POST /api/demo/seed Seed the demo-bridge catalogue operator bearer shape not published internal
GET /api/products Demo-bridge product catalogue operator bearer shape not published internal
POST /api/products Create a demo-bridge product operator bearer shape not published internal
POST /api/products/{productId}/approve Approve a demo-bridge product operator bearer shape not published internal
POST /api/products/{productId}/require-channel Require an org channel for a demo-bridge product operator bearer shape not published internal
GET /api/wallet/{accountId}/items The wallet items held by an account public shape not published
POST /api/wallet/{accountId}/items/{itemId}/accept Accept an offered item holder JWS shape not published
POST /api/wallet/{accountId}/items/{itemId}/sharing Set per-element sharing on a held item holder JWS shape not published
POST /v1/wallet/{accountId}/items/{itemId}/accept Accept an offered item holder JWS shape not published
POST /v1/wallet/{accountId}/items/{itemId}/sharing Set per-element sharing on a held item holder JWS shape not published

Presentation — OID4VP 3

MethodPathOperationAuthNotes
POST /api/requests Create a presentation request (operator plane) operator bearer shape not published
GET /oid4vp/request/{transactionId} Authorization request object (DCQL query) public
POST /oid4vp/response Wallet posts the presentation (direct_post) public shape not published

Tenants 3

MethodPathOperationAuthNotes
POST /api/tenants Provision a tenant issuer operator bearer
POST /api/tenants/update Update an organization profile and branding operator bearer
POST /v1/tenants Provision a tenant issuer operator bearer

Deposit rail 3

MethodPathOperationAuthNotes
POST /api/tenants/{tenant}/bills Deposit a business event as a wallet item (contracts 0.3.0) operator bearer
POST /api/tenants/{tenant}/revoke Revoke a credential issued by this organization operator bearer
POST /v1/tenants/{tenant}/bills Deposit a business event as a wallet item (contracts 0.3.0) operator bearer

Credential types 2

MethodPathOperationAuthNotes
POST /api/types Register a credential type operator bearer shape not published
POST /v1/types Register a credential type operator bearer shape not published

Holder directory 7

MethodPathOperationAuthNotes
POST /directory/descriptor Fetch a directory descriptor holder JWS shape not published
DELETE /directory/listing Remove the holder’s own listing holder JWS shape not published
POST /directory/listing Create or update the holder’s own directory listing holder JWS shape not published
GET /directory/listing/me Read the holder’s own listing holder JWS shape not published
POST /directory/lookup Look up a hashed directory entry holder JWS shape not published
POST /directory/search/community Search the directory by community holder JWS shape not published
POST /directory/search/name Search the directory by name holder JWS shape not published

Holder onboarding 2

MethodPathOperationAuthNotes
POST /onboard/start Send an identifier-ownership code public shape not published
POST /onboard/verify Prove the code and receive a signed ownership attestation public

Hosted verification 3

MethodPathOperationAuthNotes
GET /verify/catalog Credential types that can be requested public
POST /verify/request Create a presentation request public
GET /verify/result/{transactionId} Poll the outcome of a presentation public