Registry Utility - Allowlist-Entry Credential API Example

This example shows how to use the HTTP JSON API (CNU 0.12.x and later) to create allowlist-entry Credentials, where the issuer is also the holder. Allowlist-entry credentials are commonly used to fulfill credential requirements.

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.

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

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

Example response:

  1{
  2    "transaction": {
  3        "updateId": "1220b7ef9881a77addd06b0f8017887e3f5268e0fce93cf894e16d550238aa232d91",
  4        "commandId": "ECFC478E-34B4-43C9-9BBF-97E6B35BCFA5",
  5        "workflowId": "",
  6        "effectiveAt": "2026-03-20T15:05:58.344962Z",
  7        "events": [
  8            {
  9                "CreatedEvent": {
 10                    "offset": 201,
 11                    "nodeId": 0,
 12                    "contractId": "008911112b6fed10264c299b73eeb3ceed67ddaf36bee2efc28ea26110aacaba79ca1212207cbb9a7a3d0a30c6e116d04a231f247fa967f4e1a212e017444791f3fe168264",
 13                    "templateId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70:Utility.Credential.V0.Credential:Credential",
 14                    "contractKey": null,
 15                    "createArgument": {
 16                        "issuer": "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
 17                        "holder": "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
 18                        "id": "allowlist-entry: 1713483593-1",
 19                        "description": "Allowlist-Entry Credential",
 20                        "validFrom": null,
 21                        "validUntil": null,
 22                        "claims": [
 23                            {
 24                                "subject": "holder1::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
 25                                "property": "IsHolderOf",
 26                                "value": "INST1"
 27                            },
 28                            {
 29                                "subject": "holder1::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
 30                                "property": "IsHolderOf",
 31                                "value": "INST2"
 32                            }
 33                        ],
 34                        "observers": {
 35                            "map": []
 36                        }
 37                    },
 38                    "createdEventBlob": "",
 39                    "interfaceViews": [],
 40                    "witnessParties": [
 41                        "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93"
 42                    ],
 43                    "signatories": [
 44                        "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93"
 45                    ],
 46                    "observers": [],
 47                    "createdAt": "2026-03-20T15:05:58.344962Z",
 48                    "packageName": "utility-credential-v0",
 49                    "representativePackageId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70",
 50                    "acsDelta": true
 51                }
 52            },
 53            {
 54                "CreatedEvent": {
 55                    "offset": 201,
 56                    "nodeId": 1,
 57                    "contractId": "0050207afccd4687c30434287c410d259d4bb0d2cdac1aa8688e23b8a97ac52195ca12122029d44c7568b2bfc4786a986ba6b253df1a8deaa24b37cf8bebcb3f7391b99684",
 58                    "templateId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70:Utility.Credential.V0.Credential:Credential",
 59                    "contractKey": null,
 60                    "createArgument": {
 61                        "issuer": "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
 62                        "holder": "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
 63                        "id": "allowlist-entry: 1713483593-2",
 64                        "description": "Allowlist-Entry Credential",
 65                        "validFrom": null,
 66                        "validUntil": null,
 67                        "claims": [
 68                            {
 69                                "subject": "holder2::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
 70                                "property": "IsHolderOf",
 71                                "value": "INST1"
 72                            },
 73                            {
 74                                "subject": "holder2::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
 75                                "property": "IsHolderOf",
 76                                "value": "INST2"
 77                            }
 78                        ],
 79                        "observers": {
 80                            "map": []
 81                        }
 82                    },
 83                    "createdEventBlob": "",
 84                    "interfaceViews": [],
 85                    "witnessParties": [
 86                        "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93"
 87                    ],
 88                    "signatories": [
 89                        "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93"
 90                    ],
 91                    "observers": [],
 92                    "createdAt": "2026-03-20T15:05:58.344962Z",
 93                    "packageName": "utility-credential-v0",
 94                    "representativePackageId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70",
 95                    "acsDelta": true
 96                }
 97            },
 98            {
 99                "CreatedEvent": {
100                    "offset": 201,
101                    "nodeId": 2,
102                    "contractId": "00ccbc9e51341ceb1fc40af3d7ca6573ed8511894c019ccf728a61257ae9e51927ca1212204008c342a2646bf0ff8fa505e47305be5281c01990ca7ed7ea61216c86bd0afd",
103                    "templateId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70:Utility.Credential.V0.Credential:Credential",
104                    "contractKey": null,
105                    "createArgument": {
106                        "issuer": "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
107                        "holder": "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
108                        "id": "allowlist-entry: 1713483593-3",
109                        "description": "Allowlist-Entry Credential",
110                        "validFrom": null,
111                        "validUntil": null,
112                        "claims": [
113                            {
114                                "subject": "holder3::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
115                                "property": "IsHolderOf",
116                                "value": "INST1"
117                            },
118                            {
119                                "subject": "holder3::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
120                                "property": "IsHolderOf",
121                                "value": "INST2"
122                            }
123                        ],
124                        "observers": {
125                            "map": []
126                        }
127                    },
128                    "createdEventBlob": "",
129                    "interfaceViews": [],
130                    "witnessParties": [
131                        "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93"
132                    ],
133                    "signatories": [
134                        "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93"
135                    ],
136                    "observers": [],
137                    "createdAt": "2026-03-20T15:05:58.344962Z",
138                    "packageName": "utility-credential-v0",
139                    "representativePackageId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70",
140                    "acsDelta": true
141                }
142            }
143        ],
144        "offset": 201,
145        "synchronizerId": "global-domain::1220c952380e0034e742aae8dfdd5b03a14c3b99c1bd0bdaa01d982584444084b1a6",
146        "traceContext": {
147            "traceparent": "00-52e2c60c13a4124812bcc4042788723d-b439f31e9548dec5-01",
148            "tracestate": null
149        },
150        "recordTime": "2026-03-20T15:05:58.370893Z",
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
10SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11DATAFILE="${SCRIPT_DIR}/source.sh"
12source "$DATAFILE"
13
14OFFSET=$(curl -sS --fail-with-body \
15    --url "${HTTP_JSON_API}/v2/state/ledger-end" \
16    --header "Accept: application/json" \
17    --header "Authorization: Bearer ${CREDENTIAL_ISSUER_TOKEN}")
18
19echo "$OFFSET" | jq
20
21OUTPUTFILE="${SCRIPT_DIR}/response-step-2.json"
22echo "$OFFSET" > "$OUTPUTFILE"

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

Example response:

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

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

Example response:

 1[
 2    {
 3        "contractId": "008911112b6fed10264c299b73eeb3ceed67ddaf36bee2efc28ea26110aacaba79ca1212207cbb9a7a3d0a30c6e116d04a231f247fa967f4e1a212e017444791f3fe168264",
 4        "createdAt": "2026-03-20T15:05:58.344962Z",
 5        "id": "allowlist-entry: 1713483593-1",
 6        "issuer": "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
 7        "holder": "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
 8        "description": "Allowlist-Entry Credential",
 9        "validFrom": null,
10        "validUntil": null,
11        "claims": [
12            {
13                "subject": "holder1::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
14                "property": "IsHolderOf",
15                "value": "INST1"
16            },
17            {
18                "subject": "holder1::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
19                "property": "IsHolderOf",
20                "value": "INST2"
21            }
22        ]
23    },
24    {
25        "contractId": "0050207afccd4687c30434287c410d259d4bb0d2cdac1aa8688e23b8a97ac52195ca12122029d44c7568b2bfc4786a986ba6b253df1a8deaa24b37cf8bebcb3f7391b99684",
26        "createdAt": "2026-03-20T15:05:58.344962Z",
27        "id": "allowlist-entry: 1713483593-2",
28        "issuer": "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
29        "holder": "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
30        "description": "Allowlist-Entry Credential",
31        "validFrom": null,
32        "validUntil": null,
33        "claims": [
34            {
35                "subject": "holder2::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
36                "property": "IsHolderOf",
37                "value": "INST1"
38            },
39            {
40                "subject": "holder2::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
41                "property": "IsHolderOf",
42                "value": "INST2"
43            }
44        ]
45    },
46    {
47        "contractId": "00ccbc9e51341ceb1fc40af3d7ca6573ed8511894c019ccf728a61257ae9e51927ca1212204008c342a2646bf0ff8fa505e47305be5281c01990ca7ed7ea61216c86bd0afd",
48        "createdAt": "2026-03-20T15:05:58.344962Z",
49        "id": "allowlist-entry: 1713483593-3",
50        "issuer": "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
51        "holder": "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
52        "description": "Allowlist-Entry Credential",
53        "validFrom": null,
54        "validUntil": null,
55        "claims": [
56            {
57                "subject": "holder3::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
58                "property": "IsHolderOf",
59                "value": "INST1"
60            },
61            {
62                "subject": "holder3::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93",
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
13SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14DATAFILE="${SCRIPT_DIR}/source.sh"
15source "$DATAFILE"
16
17CONTRACTS_FILE="${SCRIPT_DIR}/response-step-3.json"
18
19if [[ ! -f "$CONTRACTS_FILE" ]]; then
20  echo "Error: ${CONTRACTS_FILE} not found. Run step 3 first." >&2
21  exit 1
22fi
23
24# Extract contractIds from step 3 output.
25CONTRACT_IDS_JSON=$(jq -c '[.[].contractId] | map(select(type == "string" and length > 0))' "$CONTRACTS_FILE")
26COUNT=$(echo "$CONTRACT_IDS_JSON" | jq 'length')
27
28if [[ "$COUNT" -eq 0 ]]; then
29  echo "No contractIds found in ${CONTRACTS_FILE}. Nothing to archive." >&2
30  echo "[]" > "${SCRIPT_DIR}/response-step-4.json"
31  exit 0
32fi
33
34COMMANDS_JSON=$(jq -n \
35  --arg templateId "${CREDENTIAL_TEMPLATE}" \
36  --argjson contractIds "${CONTRACT_IDS_JSON}" \
37  '[
38    $contractIds[]
39    | {
40        ExerciseCommand: {
41          templateId: $templateId,
42          contractId: .,
43          choice: "Archive",
44          choiceArgument: {}
45        }
46      }
47  ]'
48)
49
50RESULT=$(
51  curl -sS --fail-with-body \
52    --url "${HTTP_JSON_API}/v2/commands/submit-and-wait-for-transaction" \
53    --header "Authorization: Bearer ${CREDENTIAL_ISSUER_TOKEN}" \
54    --header "Content-Type: application/json" \
55    --request POST \
56    --data @- <<EOF
57{
58  "commands": {
59    "commands": ${COMMANDS_JSON},
60    "workflowId": "",
61    "userId": "${CREDENTIAL_ISSUER_USER_ID}",
62    "commandId": "$(uuidgen | tr -d '\n')",
63    "deduplicationPeriod": {
64      "DeduplicationDuration": {
65        "value": { "seconds": 30, "nanos": 0 }
66      }
67    },
68    "actAs": [
69      "${CREDENTIAL_ISSUER_PARTY_ID}"
70    ],
71    "readAs": [],
72    "submissionId": "$(uuidgen | tr -d '\n')",
73    "disclosedContracts": [],
74    "domainId": "",
75    "packageIdSelectionPreference": []
76  }
77}
78EOF
79)
80
81echo "--- Command response (archived count=${COUNT}) ---"
82echo "$RESULT" | jq
83
84OUTPUTFILE="${SCRIPT_DIR}/response-step-4.json"
85echo "$RESULT" > "$OUTPUTFILE"

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

Example response:

 1{
 2    "transaction": {
 3        "updateId": "122068301b79d3c66e22bfe1a2886c9bdaaf478742cfb108fb767cea17e7c2df1cb9",
 4        "commandId": "EE01A338-7A30-49F5-BF30-267FD22513B7",
 5        "workflowId": "",
 6        "effectiveAt": "2026-03-20T15:07:31.448324Z",
 7        "events": [
 8            {
 9                "ArchivedEvent": {
10                    "offset": 208,
11                    "nodeId": 0,
12                    "contractId": "008911112b6fed10264c299b73eeb3ceed67ddaf36bee2efc28ea26110aacaba79ca1212207cbb9a7a3d0a30c6e116d04a231f247fa967f4e1a212e017444791f3fe168264",
13                    "templateId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70:Utility.Credential.V0.Credential:Credential",
14                    "witnessParties": [
15                        "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93"
16                    ],
17                    "packageName": "utility-credential-v0",
18                    "implementedInterfaces": []
19                }
20            },
21            {
22                "ArchivedEvent": {
23                    "offset": 208,
24                    "nodeId": 1,
25                    "contractId": "0050207afccd4687c30434287c410d259d4bb0d2cdac1aa8688e23b8a97ac52195ca12122029d44c7568b2bfc4786a986ba6b253df1a8deaa24b37cf8bebcb3f7391b99684",
26                    "templateId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70:Utility.Credential.V0.Credential:Credential",
27                    "witnessParties": [
28                        "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93"
29                    ],
30                    "packageName": "utility-credential-v0",
31                    "implementedInterfaces": []
32                }
33            },
34            {
35                "ArchivedEvent": {
36                    "offset": 208,
37                    "nodeId": 2,
38                    "contractId": "00ccbc9e51341ceb1fc40af3d7ca6573ed8511894c019ccf728a61257ae9e51927ca1212204008c342a2646bf0ff8fa505e47305be5281c01990ca7ed7ea61216c86bd0afd",
39                    "templateId": "5a29ead611a0abd5f5b3fc3caf7d0f67c0ff802032ab6d392824aa9060e56d70:Utility.Credential.V0.Credential:Credential",
40                    "witnessParties": [
41                        "registrar::122074e948ade481df88c49a94f80a7c6091dd6fb0df94c503b530817a13019d7f93"
42                    ],
43                    "packageName": "utility-credential-v0",
44                    "implementedInterfaces": []
45                }
46            }
47        ],
48        "offset": 208,
49        "synchronizerId": "global-domain::1220c952380e0034e742aae8dfdd5b03a14c3b99c1bd0bdaa01d982584444084b1a6",
50        "traceContext": {
51            "traceparent": "00-c4b2f53db034e5e25fa28588170696d1-9efe3a546da0c9fe-01",
52            "tracestate": null
53        },
54        "recordTime": "2026-03-20T15:07:31.482944Z",
55        "externalTransactionHash": null
56    }
57}