Skip to content
Registry stack docs v0 · draft

Your first call

This tutorial walks you through four steps: bring up the Registry Lab demo, read one entity from a Relay instance, evaluate one claim from a Notary instance, and optionally issue a verifiable credential. All commands use the fixtures that ship with Registry Lab, so the output is reproducible on any machine with Docker and just installed.

Prerequisites: Docker (OrbStack or Docker Desktop), just, and git. Estimated time: 15 minutes for the first run; under 2 minutes on repeat runs after images are built.

Clone the repository with its submodules and start the demo topology.

Terminal window
git clone --recurse-submodules https://github.com/jeremi/registry-lab.git
cd registry-lab
just generate
just build
just up

just generate writes .env with local demo credentials and generates fixture data. Do not hand-edit .env; just generate rewrites it on every run.

just up starts the civil, social protection, and health Relay instances, four Notary instances, Postgres, Zitadel, the OpenFn sidecar, and the static metadata publisher. Wait until all services are ready (the civil Relay health check at http://127.0.0.1:4311/health returns 200) before continuing.

To bring everything up and run the core checks in one command:

Terminal window
just quick

When you are done, stop the containers:

Terminal window
just down

Step 2: read one entity from a Relay instance

Section titled “Step 2: read one entity from a Relay instance”

The civil registry Relay runs on host port 4311. It serves civil person records from a CSV fixture under the entity route /datasets/civil_registry/civil_person.

Load the credentials generated by just generate:

Terminal window
set -a
. ./.env
set +a

Then fetch one civil person record:

Terminal window
curl -fsS \
-H "Authorization: Bearer ${CIVIL_ROW_READER_RAW}" \
-H "Data-Purpose: https://demo.example.gov/purpose/decentralized-evidence-demo" \
-H "x-request-id: tutorial-001" \
"http://127.0.0.1:4311/datasets/civil_registry/civil_person?limit=1"

Expected response shape:

{
"data": [
{
"national_id": "NID-1001",
"given_name": "...",
"surname": "...",
"birth_date": "...",
"civil_status": "...",
"deceased": false,
"district": "..."
}
],
"meta": {
"total": 1,
"limit": 1,
"offset": 0
}
}

The Data-Purpose header is required on all Relay routes. Omitting it returns 400. The Authorization: Bearer token must carry the civil_registry:rows scope. A token with only civil_registry:evidence_verification returns 403.

Step 3: evaluate a claim from a Notary instance

Section titled “Step 3: evaluate a claim from a Notary instance”

The civil Notary runs on host port 4321. It evaluates the person-is-alive claim by fetching the deceased field from the civil Relay and applying the CEL rule source.civil.deceased == false.

Post a claim evaluation request for subject NID-1001:

Terminal window
curl -fsS \
-X POST \
-H "Authorization: Bearer ${CIVIL_EVIDENCE_CLIENT_BEARER}" \
-H "Content-Type: application/json" \
-H "Data-Purpose: https://demo.example.gov/purpose/decentralized-evidence-demo" \
-H "x-request-id: tutorial-002" \
"http://127.0.0.1:4321/claims/evaluate" \
--data '{
"subject": {"id": "NID-1001", "id_type": "national_id"},
"claims": ["person-is-alive"],
"disclosure": "predicate",
"format": "application/vnd.registry-notary.claim-result+json"
}'

Expected response shape:

{
"results": [
{
"claim_id": "person-is-alive",
"satisfied": true,
"disclosure": "predicate",
"format": "application/vnd.registry-notary.claim-result+json",
"evaluation_id": "01HX...",
"issued_at": "2026-05-26T12:00:00Z",
"provenance": {
"source_count": 1,
"computed_by": "demo.registry-notary"
}
}
]
}

"satisfied": true means the CEL rule evaluated to true for this subject. The predicate disclosure mode means the response contains only whether the claim was satisfied, not the underlying field value. Note the evaluation_id; step 4 references it.

To confirm that a missing subject returns a stable status, replace NID-1001 with NID-9999. The response will be 200, 404, or 422 depending on Notary configuration; all three are considered stable outcomes for an unknown subject.

Step 4: issue a verifiable credential (optional)

Section titled “Step 4: issue a verifiable credential (optional)”

SD-JWT VC issuance is a separate call from claim evaluation. You first evaluate the claim with the application/dc+sd-jwt format (which stores the evaluation in that format), then call POST /credentials/issue with the returned evaluation_id to materialize the credential.

First, evaluate the claim in the SD-JWT format. Replace the format field, keep the same endpoint:

Terminal window
EVALUATION_ID=$(curl -fsS \
-X POST \
-H "Authorization: Bearer ${CIVIL_EVIDENCE_CLIENT_BEARER}" \
-H "Content-Type: application/json" \
-H "Data-Purpose: https://demo.example.gov/purpose/decentralized-evidence-demo" \
-H "x-request-id: tutorial-003a" \
"http://127.0.0.1:4321/claims/evaluate" \
--data '{
"subject": {"id": "NID-1001", "id_type": "national_id"},
"claims": ["person-is-alive"],
"disclosure": "predicate",
"format": "application/dc+sd-jwt"
}' | jq -r '.results[0].evaluation_id')
echo "Stored evaluation: ${EVALUATION_ID}"

Then issue the credential from that stored evaluation:

Terminal window
curl -fsS \
-X POST \
-H "Authorization: Bearer ${CIVIL_EVIDENCE_CLIENT_BEARER}" \
-H "Content-Type: application/json" \
-H "Data-Purpose: https://demo.example.gov/purpose/decentralized-evidence-demo" \
-H "x-request-id: tutorial-003b" \
"http://127.0.0.1:4321/credentials/issue" \
--data "{\"evaluation_id\":\"${EVALUATION_ID}\"}"

The response body is a compact SD-JWT VC string. The credential is issued by did:web:civil-evidence.demo.example using the key from REGISTRY_NOTARY_ISSUER_JWK generated during just generate. The issuer DID and key are demo values; do not use them outside the local lab.

If you want a wallet to drive issuance instead, the lab also runs an OID4VCI flow against a host-side Notary on port 4325 via just citizen-oid4vci-login, just citizen-oid4vci-code, just citizen-oid4vci-token, and just citizen-oid4vci-report. See Evidence issuance, end to end for the three ways a caller can consume an evaluation.

For a cross-authority claim that draws from all three Relay instances, use the shared eligibility Notary on port 4323 with claim eligible-for-combined-support. That claim depends on civil-record-present, social-program-active, and health-service-available, each sourced from a different Relay.

Terminal window
curl -fsS \
-X POST \
-H "Authorization: Bearer ${SHARED_EVIDENCE_CLIENT_BEARER}" \
-H "Content-Type: application/json" \
-H "Data-Purpose: https://demo.example.gov/purpose/decentralized-evidence-demo" \
-H "x-request-id: tutorial-004" \
"http://127.0.0.1:4323/claims/evaluate" \
--data '{
"subject": {"id": "NID-1001", "id_type": "national_id"},
"claims": ["eligible-for-combined-support"],
"disclosure": "predicate",
"format": "application/vnd.registry-notary.claim-result+json"
}'

The provenance.source_count field in the response will be 3 or higher, confirming that all three source connections contributed to the evaluation.

  • Capabilities: the product surfaces and supporting capabilities.
  • Registry Relay: full route reference, auth scopes, and configuration guide.
  • Registry Notary: claim configuration, disclosure modes, and OID4VCI issuance.
  • Registry Lab: the full demo topology, fixture data contract, and smoke test suite.