API reference

This page describes the API itself, independent of any one deployment. Three placeholders stand in for the three logical endpoints below — see an environment page (test, production) for the concrete base URLs to substitute:

PlaceholderRole
<auth-endpoint>Authorization Server — token issuance
<endpoint>Issuer API — allocate, status, accounting
<verifier-endpoint>Verifier — published status lists

Download OpenAPI 3.0 specification

Hand-authored, not generated from code annotations — but cross-checked against the real registered routes in CI (docs/openapi_coverage_test.go), so it can't silently drift from what the service actually implements. Source of truth: siros-status-service/docs/openapi.yaml — the copy here is synced from there, not maintained independently.

Authentication

Issuer requests carry a bearer access token, obtained via the OAuth 2.0 client-credentials grant with a JWT-bearer client assertion (RFC 7523) — there is no separate registered client secret and no pre-registration step. The assertion itself, self-signed, is the whole proof of possession:

MethodPath
POST<auth-endpoint>/token

Request — form-encoded (application/x-www-form-urlencoded):

FieldValue
grant_typeclient_credentials
client_assertion_typeurn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertionthe JWT described below

The client_assertion JWT — present your existing signing certificate chain as x5c if you have one (a real issuer's signing key almost always already does, and it's the form this service would rather you use); a bare jwk header works too, for a key with no chain to present:

FieldWhereValue
algheaderES256 (only algorithm accepted)
x5cheadercertificate chain, leaf first (RFC 7515 §4.1.6) — omit if using jwk
jwkheaderbare public key (RFC 7515 §4.1.3) — omit if using x5c; exactly one of the two must be present
issclaimyour chosen issuer identifier
subclaimsame value as iss
audclaim<auth-endpoint>/token — exact match required
iat, expclaimnow, and no more than 5 minutes later

Sign with the EC (P-256) private key matching your chain's leaf certificate (or matching the bare jwk) — that key is your identity; there's nothing else to register. Generate a fresh assertion for every call: it is a proof of possession for one /token request, not a credential to save and reuse.

Response (200):

FieldValue
access_tokenbearer token for the issuer API below
token_typeBearer
expires_inseconds until access_token expires

A rejected assertion — expired, wrong aud, malformed, or an untrusted key/chain — returns 401 with {"error":"invalid_client", ...}, the same shape regardless of which of those it was.

Issuer API

Every request below requires Authorization: Bearer <access_token>. A missing, malformed, invalid, or expired token returns 401 with an RFC 6750 §3 WWW-Authenticate challenge header — e.g. Bearer error="invalid_token", error_description="the access token expired" — rather than a JSON body.

Allocate an index

MethodPath
POST<endpoint>/allocate

Request body (JSON, optional):

FieldValue
expoptional; the credential's own expiration (RFC 3339). Omit it to get this deployment's configured maximum. A value further out than that maximum is rejected with 400, not silently clamped.

Response (201):

FieldValue
list_urlthe status list this index lives in — fetch it from the verifier endpoint below
indexthis credential's index within that list
expthe expiration actually recorded (echoes the request, or the deployment's maximum if omitted)

Set status

MethodPath
PATCH<endpoint>/status/<list-id>/<index>

Request body (JSON):

FieldValue
statusone of VALID, INVALID (revoked), SUSPENDED

Response: 204 on success (no body). 403 if the calling issuer did not allocate this index — ownership is enforced server-side, not just by knowing a valid-looking index. 404 if the list doesn't exist. 410 if the list has been archived (every credential in it has already expired — there's never a legitimate revocation left to make at that point).

Accounting

MethodPath
GET<endpoint>/accounting/me

No request body. Response (200):

FieldValue
issuer_idthe caller's own issuer identity, from their access token
usagearray of {"shard_id": "...", "index_count": N} — one entry per shard this issuer has ever allocated in

Authorization Server JWKS

MethodPath
GET<auth-endpoint>/.well-known/jwks.json

The Authorization Server's own public signing key, RFC 7517 JSON Web Key Set format. What this deployment's own ingress and ingestion services fetch and cache to verify access tokens offline — not something a typical issuer/verifier integration calls directly.

Verifier API

MethodPath
GET<verifier-endpoint>/lists/<list-id>

No authentication. Returns a signed Status List Token (RFC-shaped JWT, Content-Type: application/statuslist+jwt, per IETF draft-ietf-oauth-status-list-21) — decompress and bit-unpack its status_list claim to read any index in the list, not just one you ask for by name. Standard HTTP caching applies (ETag, Cache-Control) — poll as often as you like; a If-None-Match match returns 304 with no body.