Skip to main content

Status lists (server side)

A W3C Bitstring Status List is a long string of bits, one per credential, published as a signed credential of its own. A standard VC verifier that does not speak to Ethereum can fetch the list named in a credential's credentialStatus entry and read one bit to learn whether the credential is revoked or suspended.

In Certichain the chain is the source of truth for status. The server never stores status: it reads the chain, derives the bits, signs the list with a publisher key, and serves it. Anyone can rebuild the same bits from the chain and compare.

The concept and the holder and verifier side are in Status lists flow and Verifiable credential. This page covers the server.

Off until configured, as of 2026-10-24

The route answers 503 CONFIG_MISSING "Status lists are not available on this deployment." until the deployment owner sets STATUS_LIST_SIGNER_KEY, NEXT_PUBLIC_STATUS_LIST_BASE_URL and NEXT_PUBLIC_STATUS_LIST_PUBLISHER. The browser adds status list entries to exported VCs only when both public names are set, so set all three together.

The route​

GET /api/status-lists/{chainId}/{registry}/{issuer}/{purpose}/{list}
SegmentMeaning
chainIdDecimal chain id
registryThe set's CredentialRegistry address
issuerThe issuer key address whose credentials the list covers
purposerevocation or suspension
listList number, 0 to 999,999

One issuer has two lists per block of 131,072 credentials: one for revocation, one for suspension.

How a list is built​

Issuer state​

readIssuerState (src/lib/server/status-lists/chain-state.ts) reads, for one issuer:

  1. The issuer's credentials in issuance order (block number, then log index). Only credentials in blocks at least 64 confirmations deep are counted (CONFIRMATIONS in wiring.ts), so a chain reorganisation cannot shift positions.
  2. The latest StatusChanged event of each credential. A credential with no status history is a chain state error (503), never a guess.
  3. Whether the issuer is suspended, read from IssuerSuspensionChanged of the key now in charge. Key retirements (IssuerRetired) are followed up to 8 steps to find that key.

Whether an address was ever an issuer is checked from IssuerAdded events before any of this, so the server does not scan the chain for random addresses.

Bits​

deriveList (src/lib/vc/status/derive.ts):

Status on chainRevocation bitSuspension bit
Active00
Suspended01
Revoked10
Superseded10
Any status, issuer suspendedas above1

Expiry is not a bit; verifiers read validUntil from the credential.

A credential's position is its index in the issuer's issuance order: list floor(index / 131072), bit index mod 131072 (listPosition). It never moves once assigned. List 0 always exists for a known issuer; a higher list number beyond the issuer's credentials is 404 "There is no such status list for this issuer."

The bits are gzip-compressed and written as multibase base64url without padding (a u prefix), as the Bitstring Status List format asks (encodeList in src/lib/vc/status/bitstring.ts). The gzip header is written the same way on every platform, so the same bits give the same encoded list.

The list credential​

buildListCredential (src/lib/vc/status/list-credential.ts) wraps the list:

FieldValue
typeVerifiableCredential, BitstringStatusListCredential
idThe list's URL under NEXT_PUBLIC_STATUS_LIST_BASE_URL
issuerdid:pkh of the publisher address
validFromBuild time
validUntilBuild time plus 600 seconds
credentialSubject{ id: "<url>#list", type: "BitstringStatusList", statusPurpose, encodedList, ttl: 300000 }
proofA DataIntegrityProof holding an EIP-712 signature by the publisher over the registry, issuer, purpose, list number, the keccak256 hash of encodedList, validFrom and validUntil, in the domain of that set's registry

The publisher is a separate key from any issuer. A verifier must pin the publisher address (NEXT_PUBLIC_STATUS_LIST_PUBLISHER) instead of trusting whoever signed the list; see Interop and SDK tests for how an independent library is set up to do that.

Contract sets​

StatusListRouter (router.ts) holds one StatusListService per contract set in the manifest and picks it by the registry segment. A list for a registry that is not listed is 404. Each set's lists cover only that set's credentials.

Caching and limits​

WhatHow long
"Was this address an issuer", issuer state, built listsSTATUS_LIST_CACHE_SECONDS (0 to 300, default 300), at most 512 entries each
HTTP cachingCache-Control: public, max-age=60
List validityvalidUntil 600 seconds after build, ttl 300,000 ms

So a status change on chain shows up in a served list after the 64 confirmations plus up to the cache time. Verifiers that need a faster answer read the chain directly; the verify page and the API do.

Each IP address may fetch 600 lists per hour. Without a shared store each server instance counts on its own.

Answers​

StatusCodeWhen
200The list credential, with Access-Control-Allow-Origin: *
400BAD_REQUESTMalformed path
404NOT_FOUNDUnknown chain, registry, issuer or list
429RATE_LIMITEDIP budget used up
503CONFIG_MISSINGOff, or the setup failed
503UPSTREAM_ERROR"The status list could not be built right now. Try again shortly." with Retry-After: 30

OPTIONS answers 204 with Access-Control-Allow-Origin: *. Failures are logged by error name only.

Configuration​

NameRule
STATUS_LIST_SIGNER_KEY0x plus 64 hex. A funds-free key used only for lists. Server only.
NEXT_PUBLIC_STATUS_LIST_PUBLISHERMust equal the key's address, or the setup fails
NEXT_PUBLIC_STATUS_LIST_BASE_URLhttps, or http for localhost and 127.0.0.1 only; no credentials, query or fragment
STATUS_LIST_CACHE_SECONDS0 to 300, default 300
NEXT_PUBLIC_NETWORK_ID, RPC namesThe chain to read

The monitor's signers check compares the key's address with the published one; see Monitoring endpoints.

Source files​

  • src/app/api/status-lists/[...path]/route.ts
  • src/lib/server/status-lists/ (config.ts, wiring.ts, router.ts, service.ts, chain-state.ts, handler.ts, ttl-cache.ts)
  • src/lib/vc/status/ (location.ts, derive.ts, bitstring.ts, list-credential.ts, entry.ts)

Tests​

  • src/lib/server/status-lists/*.test.ts
  • src/lib/vc/status/*.test.ts
  • interop/test/status-list.test.mjs reads the lists with an independent verifier library
  • E2E: e2e/journeys/status-lists.journey.ts