Registry Utility - Allowlist-Entry Credential API Example

This example shows how to create allowlist-entry Credential\s via the HTTP JSON API (v2), i.e., credentials where the issuer is also the holder.

The workflows covered here are for the Credential issuer to:

  1. Create new allowlist-entry credentials in a batch (atomically).

  2. Query the ledger end offset.

  3. Read all currently-active allowlist-entry credentials of the given issuer with matching claims.

  4. Revoke (archieve) the corresponding credentials.

Preparation

Prerequisites:

  • Access to a running Canton HTTP JSON API (v2).

  • Local tools: bash, curl, jq, and uuidgen.

Add all the required information to the source.sh file:

 1#!/usr/bin/env bash
 2
 3## =================================================================================================
 4## Purpose: Configurations for this example, amend variables as needed.
 5## Script: source.sh
 6## =================================================================================================
 7
 8# Credential issuer details
 9CREDENTIAL_ISSUER_TOKEN="eyJhbGciOiJIUzI1NiJ9.eyJzY29wZSI6ImRhbWxfbGVkZ2VyX2FwaSIsImlhdCI6MTc2OTAxMTAzNSwiYXVkIjoiaHR0cHM6Ly91dGlsaXR5LmNhbnRvbi5uZXR3b3JrIiwic3ViIjoicmVnaXN0cmFyIn0.mm8wFTWR7y3DRcLC9xU1t9CUcsGStwf6mpBtuSvbvQA"
10CREDENTIAL_ISSUER_PARTY_ID="registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
11CREDENTIAL_ISSUER_USER_ID="registrar"
12
13# Subjects to create an allowlist-entry credential for.
14CREDENTIAL_SUBJECT_PARTY_IDS='[
15	"holder1::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
16	"holder2::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
17  "holder3::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
18]'
19
20# Description used for all created credentials
21CREDENTIAL_DESCRIPTION="Allowlist-Entry Credential"
22
23# Claims to attach to each created credential (JSON list).
24# The scripts will set the `subject` field of each claim to the current subject party.
25CREDENTIAL_CLAIMS_JSON='[
26	{"property":"IsHolderOf","value":"INST1"},
27	{"property":"IsHolderOf","value":"INST2"}
28]'
29
30# JSON API endpoint
31# Example (local): HTTP_JSON_API="http://localhost:8001/api/json-api"
32# Example (remote): HTTP_JSON_API="https://<your-host>/api/json-api"
33HTTP_JSON_API="http://localhost:8001/api/json-api"
34
35# Daml template IDs (package-name qualified)
36CREDENTIAL_TEMPLATE="#utility-credential-v0:Utility.Credential.V0.Credential:Credential"

The required information is:

Details of

Description

Credential issuer

CREDENTIAL_ISSUER_TOKEN, CREDENTIAL_ISSUER_USER_ID, CREDENTIAL_ISSUER_PARTY_ID

Subjects

CREDENTIAL_SUBJECT_PARTY_IDS: JSON array of subject party IDs (strings)

Claims

CREDENTIAL_CLAIMS_JSON: JSON list of (property + value) pairs

Description

CREDENTIAL_DESCRIPTION: description applied to all created credentials

JSON API base URL

HTTP_JSON_API: HTTP JSON API base URL

Step 1: Create Allowlist-Entry Credentials (in a Batch)

This step submits a single transaction that contains one CreateCommand per subject from CREDENTIAL_SUBJECT_PARTY_IDS. The transaction is atomic, either all credentials are created, or none are.

Run the following script:

 1#!/usr/bin/env bash
 2
 3## =================================================================================================
 4## How-to Tutorial: Manage allowlist-entry credentials
 5## Step 1: Credential issuer createss allowlist-entry `Utility.Credential.V0.Credential:Credential`s
 6## for multiple subjects in a single transaction.
 7## Authorized by: credential issuer
 8## Script: step-1-credential-issuer-creates-allowlist-entries.sh
 9## =================================================================================================
10
11set -euo pipefail
12
13DATAFILE="source.sh"
14source "$DATAFILE"
15
16# Validate inputs
17if [[ -z "${CREDENTIAL_SUBJECT_PARTY_IDS:-}" ]]; then
18    echo "Error: CREDENTIAL_SUBJECT_PARTY_IDS is not set in source.sh"
19    exit 1
20fi
21if [[ -z "${CREDENTIAL_CLAIMS_JSON:-}" ]]; then
22    echo "Error: CREDENTIAL_CLAIMS_JSON is not set in source.sh"
23    exit 1
24fi
25echo "${CREDENTIAL_SUBJECT_PARTY_IDS}" | jq -e 'type == "array" and length > 0 and all(.[]; type == "string")' >/dev/null
26echo "${CREDENTIAL_CLAIMS_JSON}" | jq -e 'type == "array" and length > 0 and all(.[]; has("property") and has("value"))' >/dev/null
27
28# Random Credential ID prefix.
29ID_PREFIX="allowlist-entry: $(od -An -N4 -tu4 < /dev/urandom | tr -d ' ')"
30
31COMMANDS_JSON=$(jq -n \
32    --arg templateId "${CREDENTIAL_TEMPLATE}" \
33    --arg issuer "${CREDENTIAL_ISSUER_PARTY_ID}" \
34    --arg description "${CREDENTIAL_DESCRIPTION}" \
35    --arg holder "${CREDENTIAL_ISSUER_PARTY_ID}" \
36    --arg idPrefix "${ID_PREFIX}-" \
37    --argjson subjects "${CREDENTIAL_SUBJECT_PARTY_IDS}" \
38    --argjson claimPairs "${CREDENTIAL_CLAIMS_JSON}" \
39    '[
40        ($subjects | to_entries[]) as $entry
41        | ($entry.key + 1) as $n
42        | ($entry.value) as $subjectParty
43        | {
44                CreateCommand: {
45                    templateId: $templateId,
46                    createArguments: {
47                        issuer: $issuer,
48                        holder: $holder,
49                        id: ($idPrefix + ($n | tostring)),
50                        description: $description,
51                        validFrom: null,
52                        validUntil: null,
53                        claims: (
54                          $claimPairs
55                          | map({subject: $subjectParty, property: .property, value: .value})
56                        ),
57                        observers: { map: [] }
58                    }
59                }
60            }
61    ]')
62
63RESULT=$(
64    curl -s \
65        --url "${HTTP_JSON_API}/v2/commands/submit-and-wait-for-transaction" \
66        --header "Authorization: Bearer ${CREDENTIAL_ISSUER_TOKEN}" \
67        --header "Content-Type: application/json" \
68        --request POST \
69        --data @- <<EOF
70{
71    "commands": {
72        "commands": ${COMMANDS_JSON},
73        "workflowId": "",
74        "userId": "${CREDENTIAL_ISSUER_USER_ID}",
75        "commandId": "$(uuidgen | tr -d '\n')",
76        "deduplicationPeriod": {
77            "DeduplicationDuration": {
78                "value": { "seconds": 30, "nanos": 0 }
79            }
80        },
81        "actAs": [
82            "${CREDENTIAL_ISSUER_PARTY_ID}"
83        ],
84        "readAs": [],
85        "submissionId": "$(uuidgen | tr -d '\n')",
86        "disclosedContracts": [],
87        "domainId": "",
88        "packageIdSelectionPreference": []
89    }
90}
91EOF
92)
93
94echo "--- Command response ---"
95echo "$RESULT" | jq
96
97OUTPUTFILE="response-step-1.json"
98echo "$RESULT" > "$OUTPUTFILE"

The result is the transaction response stored in response-step-1.json.

Example response:

  1{
  2    "transaction": {
  3        "updateId": "12200dff8b12b6136ad9326352e25207992b33a9ab5c9b16d5edb1e7d693fcaeb32b",
  4        "commandId": "5D5BA47A-8C47-4E2C-BD64-6539B25446F9",
  5        "workflowId": "",
  6        "effectiveAt": "2026-01-21T15:58:04.458345Z",
  7        "events": [
  8            {
  9                "CreatedEvent": {
 10                    "offset": 198,
 11                    "nodeId": 0,
 12                    "contractId": "009d7ada69b1e26db27535a60a985d3902d602a4d684b37cd31859903e6d5f7a66ca121220d7d60b9fe0f67118aab24d3814b3807ab787fe9c49c103660625bee5d4f54886",
 13                    "templateId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70:Utility.Credential.V0.Credential:Credential",
 14                    "contractKey": null,
 15                    "createArgument": {
 16                        "issuer": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
 17                        "holder": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
 18                        "id": "allowlist-entry: 290808241-1",
 19                        "description": "Allowlist-Entry Credential",
 20                        "validFrom": null,
 21                        "validUntil": null,
 22                        "claims": [
 23                            {
 24                                "subject": "holder1::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
 25                                "property": "IsHolderOf",
 26                                "value": "INST1"
 27                            },
 28                            {
 29                                "subject": "holder1::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
 30                                "property": "IsHolderOf",
 31                                "value": "INST2"
 32                            }
 33                        ],
 34                        "observers": {
 35                            "map": []
 36                        }
 37                    },
 38                    "createdEventBlob": "",
 39                    "interfaceViews": [],
 40                    "witnessParties": [
 41                        "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
 42                    ],
 43                    "signatories": [
 44                        "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
 45                    ],
 46                    "observers": [],
 47                    "createdAt": "2026-01-21T15:58:04.458345Z",
 48                    "packageName": "utility-credential-v0",
 49                    "representativePackageId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70",
 50                    "acsDelta": true
 51                }
 52            },
 53            {
 54                "CreatedEvent": {
 55                    "offset": 198,
 56                    "nodeId": 1,
 57                    "contractId": "00cafb02692ee25fd1ce37433e8f2c7e9d8fa4826cd36353e68b7b84b36aa94df1ca1212204b97fbecd571fc03180e36d0f8e643455e782ca6ba09f2214a740c4c6ff3c55e",
 58                    "templateId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70:Utility.Credential.V0.Credential:Credential",
 59                    "contractKey": null,
 60                    "createArgument": {
 61                        "issuer": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
 62                        "holder": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
 63                        "id": "allowlist-entry: 290808241-2",
 64                        "description": "Allowlist-Entry Credential",
 65                        "validFrom": null,
 66                        "validUntil": null,
 67                        "claims": [
 68                            {
 69                                "subject": "holder2::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
 70                                "property": "IsHolderOf",
 71                                "value": "INST1"
 72                            },
 73                            {
 74                                "subject": "holder2::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
 75                                "property": "IsHolderOf",
 76                                "value": "INST2"
 77                            }
 78                        ],
 79                        "observers": {
 80                            "map": []
 81                        }
 82                    },
 83                    "createdEventBlob": "",
 84                    "interfaceViews": [],
 85                    "witnessParties": [
 86                        "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
 87                    ],
 88                    "signatories": [
 89                        "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
 90                    ],
 91                    "observers": [],
 92                    "createdAt": "2026-01-21T15:58:04.458345Z",
 93                    "packageName": "utility-credential-v0",
 94                    "representativePackageId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70",
 95                    "acsDelta": true
 96                }
 97            },
 98            {
 99                "CreatedEvent": {
100                    "offset": 198,
101                    "nodeId": 2,
102                    "contractId": "0039dd342399e147a805d867b9675cfeaa333264a11ee66239c589ad6d22183b80ca12122009066bc94452281a107527c17f572aa2d976af73701d59eff0553ca039b6067c",
103                    "templateId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70:Utility.Credential.V0.Credential:Credential",
104                    "contractKey": null,
105                    "createArgument": {
106                        "issuer": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
107                        "holder": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
108                        "id": "allowlist-entry: 290808241-3",
109                        "description": "Allowlist-Entry Credential",
110                        "validFrom": null,
111                        "validUntil": null,
112                        "claims": [
113                            {
114                                "subject": "holder3::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
115                                "property": "IsHolderOf",
116                                "value": "INST1"
117                            },
118                            {
119                                "subject": "holder3::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
120                                "property": "IsHolderOf",
121                                "value": "INST2"
122                            }
123                        ],
124                        "observers": {
125                            "map": []
126                        }
127                    },
128                    "createdEventBlob": "",
129                    "interfaceViews": [],
130                    "witnessParties": [
131                        "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
132                    ],
133                    "signatories": [
134                        "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
135                    ],
136                    "observers": [],
137                    "createdAt": "2026-01-21T15:58:04.458345Z",
138                    "packageName": "utility-credential-v0",
139                    "representativePackageId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70",
140                    "acsDelta": true
141                }
142            }
143        ],
144        "offset": 198,
145        "synchronizerId": "global-domain::1220840e7f637a4c596517230420f05d25e9914a364d62e2c01ea9a162fe90823fa1",
146        "traceContext": {
147            "traceparent": "00-8fd12fc7e9d2dddd48dc67d228362df5-3ecad24d429a391d-01",
148            "tracestate": null
149        },
150        "recordTime": "2026-01-21T15:58:04.485074Z",
151        "externalTransactionHash": null
152    }
153}

Step 2: Get Ledger Offset

Run the following script to obtain the ledger end offset:

 1#!/usr/bin/env bash
 2
 3## =================================================================================================
 4## How-to Tutorial: Manage allowlist-entry credentials
 5## Step 2: Credential issuer gets ledger offset
 6## Authorized by: credential issuer
 7## Script: step-2-credential-issuer-gets-offset.sh
 8## =================================================================================================
 9
10DATAFILE="source.sh"
11source "$DATAFILE"
12
13OFFSET=$(curl -s \
14    --url "${HTTP_JSON_API}/v2/state/ledger-end" \
15    --header "Accept: application/json" \
16    --header "Authorization: Bearer ${CREDENTIAL_ISSUER_TOKEN}")
17
18echo "$OFFSET" | jq
19
20OUTPUTFILE="response-step-2.json"
21echo "$OFFSET" > "$OUTPUTFILE"

The result is the ledger end offset at this moment, stored in response-step-2.json.

Example response:

1{
2    "offset": 206
3}

Step 3: Read All Issued Allowlist-Entry Credentials

This step queries active contracts visible to the issuer for the credential template, and filters the result to only those where createArgument.issuer == CREDENTIAL_ISSUER_PARTY_ID, and where the claims match those specified in CREDENTIAL_CLAIMS_JSON (ignoring the subject).

This script expects the ledger end offset from step 2 to be present in response-step-2.json.

Run the following script:

  1#!/usr/bin/env bash
  2
  3## =================================================================================================
  4## How-to Tutorial: Manage allowlist-entry credentials
  5## Step 3: Credential issuer retrieve active `Utility.Credential.V0.Credential:Credential` contracts
  6## issued for itself, filtered by expected claim property/value pairs.
  7## Authorized by: credential issuer
  8## Script: step-3-credential-issuer-retrieves-issued-credentials.sh
  9## =================================================================================================
 10
 11set -euo pipefail
 12
 13DATAFILE="source.sh"
 14source "$DATAFILE"
 15
 16# Validate expected claim pairs config (property/value)
 17if [[ -z "${CREDENTIAL_CLAIMS_JSON:-}" ]]; then
 18  echo "Error: CREDENTIAL_CLAIMS_JSON is not set in source.sh" >&2
 19  exit 1
 20fi
 21
 22echo "${CREDENTIAL_CLAIMS_JSON}" | jq -e 'type == "array" and all(.[]; (has("property") and has("value")))' >/dev/null
 23
 24# Get offset from previous step
 25if [[ -f "response-step-2.json" ]]; then
 26  JSONCONTENT=$(cat "response-step-2.json")
 27  OFFSET=$(echo "$JSONCONTENT" | jq -r ".offset")
 28else
 29  echo "Error: response-step-2.json not found"
 30  exit 1
 31fi
 32
 33RESULT=$(curl -s \
 34    --url "${HTTP_JSON_API}/v2/state/active-contracts" \
 35    --header "Authorization: Bearer ${CREDENTIAL_ISSUER_TOKEN}" \
 36    --header "Content-Type: application/json" \
 37    --request POST \
 38    --data @- <<EOF
 39{
 40  "verbose": false,
 41  "activeAtOffset": "${OFFSET}",
 42  "filter": {
 43    "filtersByParty": {
 44      "${CREDENTIAL_ISSUER_PARTY_ID}": {
 45        "cumulative": [
 46          {
 47            "identifierFilter": {
 48              "TemplateFilter": {
 49                "value": {
 50                  "templateId": "${CREDENTIAL_TEMPLATE}",
 51                  "includeCreatedEventBlob": false
 52                }
 53              }
 54            }
 55          }
 56        ]
 57      }
 58    }
 59  }
 60}
 61EOF
 62)
 63
 64# Keep only credentials whose createArgument.issuer matches CREDENTIAL_ISSUER_PARTY_ID and whose
 65# claim property/value pairs match the configured CREDENTIAL_CLAIMS_JSON pairs (ignoring subject).
 66FILTERED=$(echo "$RESULT" | jq \
 67  --arg CREDENTIAL_ISSUER_PARTY_ID "$CREDENTIAL_ISSUER_PARTY_ID" \
 68  --argjson expectedClaimPairs "${CREDENTIAL_CLAIMS_JSON}" \
 69  '[
 70    def normalizePairs($xs): ($xs | map({property: .property, value: .value}) | unique);
 71    def matchesPairsExactly($claims; $expected):
 72      (normalizePairs($expected)) as $e
 73      | (normalizePairs($claims)) as $a
 74      | ($a == $e);
 75
 76    .[]
 77    | .contractEntry.JsActiveContract.createdEvent
 78    | {
 79        contractId: .contractId,
 80        createdAt: .createdAt,
 81        id: .createArgument.id,
 82        issuer: .createArgument.issuer,
 83        holder: .createArgument.holder,
 84        description: .createArgument.description,
 85        validFrom: .createArgument.validFrom,
 86        validUntil: .createArgument.validUntil,
 87        claims: .createArgument.claims
 88      }
 89    | select(.issuer == $CREDENTIAL_ISSUER_PARTY_ID)
 90    | select(matchesPairsExactly(.claims; $expectedClaimPairs))
 91  ]'
 92)
 93
 94COUNT=$(echo "$FILTERED" | jq 'length')
 95
 96echo "--- Issued credentials of ${CREDENTIAL_ISSUER_PARTY_ID} as of offset ${OFFSET} (count=${COUNT}) ---"
 97echo "$FILTERED" | jq
 98
 99OUTPUTFILE="response-step-3.json"
100echo "$FILTERED" > "$OUTPUTFILE"

The result is a JSON array written to response-step-3.json.

Example response:

 1[
 2  {
 3    "contractId": "009d7ada69b1e26db27535a60a985d3902d602a4d684b37cd31859903e6d5f7a66ca121220d7d60b9fe0f67118aab24d3814b3807ab787fe9c49c103660625bee5d4f54886",
 4    "createdAt": "2026-01-21T15:58:04.458345Z",
 5    "id": "allowlist-entry: 290808241-1",
 6    "issuer": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
 7    "holder": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
 8    "description": "Allowlist-Entry Credential",
 9    "validFrom": null,
10    "validUntil": null,
11    "claims": [
12      {
13        "subject": "holder1::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
14        "property": "IsHolderOf",
15        "value": "INST1"
16      },
17      {
18        "subject": "holder1::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
19        "property": "IsHolderOf",
20        "value": "INST2"
21      }
22    ]
23  },
24  {
25    "contractId": "00cafb02692ee25fd1ce37433e8f2c7e9d8fa4826cd36353e68b7b84b36aa94df1ca1212204b97fbecd571fc03180e36d0f8e643455e782ca6ba09f2214a740c4c6ff3c55e",
26    "createdAt": "2026-01-21T15:58:04.458345Z",
27    "id": "allowlist-entry: 290808241-2",
28    "issuer": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
29    "holder": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
30    "description": "Allowlist-Entry Credential",
31    "validFrom": null,
32    "validUntil": null,
33    "claims": [
34      {
35        "subject": "holder2::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
36        "property": "IsHolderOf",
37        "value": "INST1"
38      },
39      {
40        "subject": "holder2::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
41        "property": "IsHolderOf",
42        "value": "INST2"
43      }
44    ]
45  },
46  {
47    "contractId": "0039dd342399e147a805d867b9675cfeaa333264a11ee66239c589ad6d22183b80ca12122009066bc94452281a107527c17f572aa2d976af73701d59eff0553ca039b6067c",
48    "createdAt": "2026-01-21T15:58:04.458345Z",
49    "id": "allowlist-entry: 290808241-3",
50    "issuer": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
51    "holder": "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
52    "description": "Allowlist-Entry Credential",
53    "validFrom": null,
54    "validUntil": null,
55    "claims": [
56      {
57        "subject": "holder3::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
58        "property": "IsHolderOf",
59        "value": "INST1"
60      },
61      {
62        "subject": "holder3::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b",
63        "property": "IsHolderOf",
64        "value": "INST2"
65      }
66    ]
67  }
68]

Step 4: Revoke Issued Allowlist-Entry Credentials

This step revokes or archives (consumes) the active credential contracts returned by step 3.

This script expects the filtered list from step 3 to be present in response-step-3.json.

Run the following script:

 1#!/usr/bin/env bash
 2
 3## =================================================================================================
 4## How-to Tutorial: Manage allowlist-entry credentials
 5## Step 4: Credential issuer revokes the active `Utility.Credential.V0.Credential:Credential`s
 6## contracts returned by step 3
 7## Authorized by: credential issuer
 8## Script: step-4-credential-issuer-revokes-issued-credentials.sh
 9## =================================================================================================
10
11set -euo pipefail
12
13DATAFILE="source.sh"
14source "$DATAFILE"
15
16CONTRACTS_FILE="response-step-3.json"
17
18if [[ ! -f "$CONTRACTS_FILE" ]]; then
19  echo "Error: ${CONTRACTS_FILE} not found. Run step 3 first." >&2
20  exit 1
21fi
22
23# Extract contractIds from step 3 output.
24CONTRACT_IDS_JSON=$(jq -c '[.[].contractId] | map(select(type == "string" and length > 0))' "$CONTRACTS_FILE")
25COUNT=$(echo "$CONTRACT_IDS_JSON" | jq 'length')
26
27if [[ "$COUNT" -eq 0 ]]; then
28  echo "No contractIds found in ${CONTRACTS_FILE}. Nothing to archive." >&2
29  echo "[]" > "response-step-4.json"
30  exit 0
31fi
32
33COMMANDS_JSON=$(jq -n \
34  --arg templateId "${CREDENTIAL_TEMPLATE}" \
35  --argjson contractIds "${CONTRACT_IDS_JSON}" \
36  '[
37    $contractIds[]
38    | {
39        ExerciseCommand: {
40          templateId: $templateId,
41          contractId: .,
42          choice: "Archive",
43          choiceArgument: {}
44        }
45      }
46  ]'
47)
48
49RESULT=$(
50  curl -s \
51    --url "${HTTP_JSON_API}/v2/commands/submit-and-wait-for-transaction" \
52    --header "Authorization: Bearer ${CREDENTIAL_ISSUER_TOKEN}" \
53    --header "Content-Type: application/json" \
54    --request POST \
55    --data @- <<EOF
56{
57  "commands": {
58    "commands": ${COMMANDS_JSON},
59    "workflowId": "",
60    "userId": "${CREDENTIAL_ISSUER_USER_ID}",
61    "commandId": "$(uuidgen | tr -d '\n')",
62    "deduplicationPeriod": {
63      "DeduplicationDuration": {
64        "value": { "seconds": 30, "nanos": 0 }
65      }
66    },
67    "actAs": [
68      "${CREDENTIAL_ISSUER_PARTY_ID}"
69    ],
70    "readAs": [],
71    "submissionId": "$(uuidgen | tr -d '\n')",
72    "disclosedContracts": [],
73    "domainId": "",
74    "packageIdSelectionPreference": []
75  }
76}
77EOF
78)
79
80echo "--- Command response (archived count=${COUNT}) ---"
81echo "$RESULT" | jq
82
83OUTPUTFILE="response-step-4.json"
84echo "$RESULT" > "$OUTPUTFILE"

The result is the transaction response stored in response-step-4.json.

Example response:

 1{
 2    "transaction": {
 3        "updateId": "12201bc9ff362bf24503fde97f9e4401934f29ae4ef07361bcd2e67ae31d6d7e3427",
 4        "commandId": "62A57B3A-B0D3-4962-9035-1B260AD302E8",
 5        "workflowId": "",
 6        "effectiveAt": "2026-01-21T15:58:43.555374Z",
 7        "events": [
 8            {
 9                "ArchivedEvent": {
10                    "offset": 207,
11                    "nodeId": 0,
12                    "contractId": "009d7ada69b1e26db27535a60a985d3902d602a4d684b37cd31859903e6d5f7a66ca121220d7d60b9fe0f67118aab24d3814b3807ab787fe9c49c103660625bee5d4f54886",
13                    "templateId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70:Utility.Credential.V0.Credential:Credential",
14                    "witnessParties": [
15                        "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
16                    ],
17                    "packageName": "utility-credential-v0",
18                    "implementedInterfaces": []
19                }
20            },
21            {
22                "ArchivedEvent": {
23                    "offset": 207,
24                    "nodeId": 1,
25                    "contractId": "00cafb02692ee25fd1ce37433e8f2c7e9d8fa4826cd36353e68b7b84b36aa94df1ca1212204b97fbecd571fc03180e36d0f8e643455e782ca6ba09f2214a740c4c6ff3c55e",
26                    "templateId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70:Utility.Credential.V0.Credential:Credential",
27                    "witnessParties": [
28                        "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
29                    ],
30                    "packageName": "utility-credential-v0",
31                    "implementedInterfaces": []
32                }
33            },
34            {
35                "ArchivedEvent": {
36                    "offset": 207,
37                    "nodeId": 2,
38                    "contractId": "0039dd342399e147a805d867b9675cfeaa333264a11ee66239c589ad6d22183b80ca12122009066bc94452281a107527c17f572aa2d976af73701d59eff0553ca039b6067c",
39                    "templateId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70:Utility.Credential.V0.Credential:Credential",
40                    "witnessParties": [
41                        "registrar::12206746c7f1cdbde4c01cf2e83d45d1a25b34293e0ad07056547a5ce25074a7b30b"
42                    ],
43                    "packageName": "utility-credential-v0",
44                    "implementedInterfaces": []
45                }
46            }
47        ],
48        "offset": 207,
49        "synchronizerId": "global-domain::1220840e7f637a4c596517230420f05d25e9914a364d62e2c01ea9a162fe90823fa1",
50        "traceContext": {
51            "traceparent": "00-8823f2d62154ad7f34c50fdb3c5b51b0-fc0881278522b625-01",
52            "tracestate": null
53        },
54        "recordTime": "2026-01-21T15:58:43.591203Z",
55        "externalTransactionHash": null
56    }
57}