Grant and revoke access

Grant and revoke access roles, scope permissions to contexts, set expiry, and end an existing session immediately using pnm acl commands.

By the end of this guide, you can grant a DID access to a context, change what it is allowed to do there, and revoke it when it should no longer have access.

Every DID that calls the VTA holds an access control list (ACL) entry assigning it a role and, usually, a set of allowed contexts. Managing that list is how you control which applications, services, and operators reach the VTA, and what each one may do once it gets there. For what the five roles mean and how role and context bound each other, see Roles and access.

A DID with no entry is refused regardless of how it was created, so access is something you grant deliberately rather than something a valid key confers on its own. Provisioning an application with pnm bootstrap writes that entry for you, which leaves this guide for the identities bootstrap did not create and for every change after the first one.

Use this guide when:

  • You need to grant access to a DID that was not provisioned through pnm bootstrap.
  • A service’s role or context scope needs to change.
  • A credential has been exposed, or a person or service should no longer have access.
  • You want to give someone access that expires on its own rather than relying on a later cleanup.
Incoming requestauthenticated as a DIDACL lookupEntry foundNo entryRole and context scopedetermine what's allowed.Request is rejected, regardless ofhow the DID was created.The ACL is the single gate every caller passes through,whether provisioned by pnm bootstrap or added manually with pnm acl create.

Prerequisites

  • A running VTA. See Quickstart.
  • pnm CLI connected to your VTA (pnm vta info returns without error).
  • admin or initiator role for the DID you’re managing entries with. Context-scoped admins can only manage entries within their allowed contexts.

Roles

Every ACL entry carries one role. The role determines which endpoints the caller can reach once authenticated. Choose the least-privileged role that covers the caller’s needs. See Roles and access for why the VTA has five roles instead of one shared credential.

RoleWho it’s forWhat it can do
adminOperators, context ownersFull management: create keys, manage ACL, sign, read
initiatorTrusted services that onboard othersManage ACL entries. Sign within allowed contexts
applicationApps and servicesSign, read keys and contexts within allowed contexts
readerRead-only integrationsRead-only: list keys, contexts, DIDs
monitorInfrastructure health checksRead-only: metrics and health endpoints only. No access to keys, contexts, or DIDs

admin and initiator can both manage ACL entries. Only admin can assign the admin role. Context creation needs more than the admin role itself: it requires a super-admin entry specifically, so a context-scoped admin can’t create contexts even though it holds the admin role.

An admin entry created without --contexts is a super-admin: it carries no context restriction and can see and manage every entry across the VTA. Scope --contexts on every other admin entry to limit its reach.

Listing ACL entries

Returns every ACL entry visible to your role. Context-scoped admins only see entries in their allowed contexts. Super-admins see all.

# All entries
pnm acl list

# Entries scoped to a specific context
pnm acl list --context my-app

Getting an entry

Shows full details for a single entry: role, label, context scope, created-at, and who created it.

pnm acl get <did>

Granting access

Creates a new ACL entry. The command is not idempotent. If an entry for the DID already exists, the server returns 409 Conflict. Use pnm acl update to change an existing entry.

Basic grant

pnm acl create \
    --did      did:key:z6Mk... \
    --role     application \
    --contexts my-app \
    --label    "my-app service account"

Multiple contexts

pnm acl create \
    --did      did:key:z6Mk... \
    --role     application \
    --contexts my-app,my-other-app \
    --label    "shared service account"

Temporary / expiring access

Use --expires to create a setup ACL that auto-expires if never claimed. Accepts N[s|m|h|d|w]:

pnm acl create \
    --did      did:key:z6Mk... \
    --role     admin \
    --contexts my-app \
    --label    "bootstrap admin (expires 24h)" \
    --expires  24h

The VTA’s ACL background task removes the entry once its deadline passes, with no automatic handoff to a permanent entry. Grant the DID a permanent entry with pnm acl create before the temporary one expires, if you want it to keep working past that deadline.

Updating an entry

Changes one or more fields on an existing entry. Only supply the fields you want to change. Omitted fields are left as-is.

# Change context scope
pnm acl update <did> --contexts my-app,my-other-app

# Change label
pnm acl update <did> --label "updated label"

Changing a role

Role changes require a compare-and-swap: you must supply the role the entry currently holds (--from) so that a concurrent admin change is surfaced rather than silently overwritten. Re-read with pnm acl get and retry if the server rejects the change.

pnm acl change-role \
    --did  <did> \
    --from application \
    --to   reader

An optional --reason is recorded in the audit log:

pnm acl change-role \
    --did    <did> \
    --from   admin \
    --to     application \
    --reason "retiring operator account"

Revoking access

Removes the DID from the ACL immediately. The caller must be an admin. Context-scoped admins can only delete entries within their allowed contexts.

pnm acl delete <did>

Deletion is immediate and permanent, and it stops the DID opening any new session. A JWT the DID is already holding is separate: it keeps working until it naturally expires, per the session lifetime every access token has. Revoking the session as well is what closes that window, and the next section covers it.

Ending existing sessions immediately

The VTA checks the session record on every authenticated request, so a revoked session stops working on the DID’s very next call rather than when its token would have expired.

pnm has no session subcommand yet, so these are direct REST calls. Use your own admin session token to authenticate them:

export VTA_TOKEN=$(pnm --vta <vta-slug> auth show-token)

See which sessions the VTA is holding. A super-admin sees every session; a context-scoped admin sees those belonging to DIDs in their own contexts:

curl -s "$VTA_URL/v1/auth/sessions" \
    -H "Authorization: Bearer $VTA_TOKEN"

Revoke every session one DID holds. This is the call to reach for after removing an ACL entry, because it covers all of that DID’s sessions without you having to identify each one:

curl -s -X DELETE "$VTA_URL/v1/auth/sessions?did=<did>" \
    -H "Authorization: Bearer $VTA_TOKEN"
{ "revoked": 2 }

To end one specific session instead, name its ID from the listing above:

curl -s -X DELETE "$VTA_URL/v1/auth/sessions/<session-id>" \
    -H "Authorization: Bearer $VTA_TOKEN"

A single session can also be revoked as the auth/revoke-session/0.1 Trust Task over DIDComm. That task revokes exactly the one session you name, so the REST query-parameter form above is the one that covers a DID’s whole set.

Common patterns

Reference snippets for the scenarios you’ll encounter most often. See Sign application payloads without exposing your keys for the pnm bootstrap flow that creates an app’s ACL entry automatically.

Granting a second admin to an existing context

pnm acl create \
    --did      did:key:z6MkSecondAdmin... \
    --role     admin \
    --contexts my-app \
    --label    "backup admin"

Granting read-only access for monitoring

pnm acl create \
    --did      did:key:z6MkMonitor... \
    --role     reader \
    --contexts my-app \
    --label    "read-only monitoring"

Temporary access for onboarding

pnm acl create \
    --did      did:key:z6MkTemp... \
    --role     admin \
    --contexts my-app \
    --expires  7d \
    --label    "setup DID (expires in 7 days)"

Confirm

Test 1: the entry returns the role and contexts you granted

pnm acl get did:key:z6Mk...
DID:              did:key:z6Mk...
Role:             application
Label:            my-app service account
Contexts:         my-app
Created At:       2026-09-16 10:24:07 +08:00
Created By:       did:key:z6MkAdmin...

Role and Contexts are the two fields that decide what this DID can reach. An admin entry with no context restriction shows Role: super admin.

Test 2: the entry appears in the context listing

pnm acl list --context my-app

The DID from Test 1 appears in the listing. A context the DID is not scoped to omits it.

Test 3: after deletion, the DID no longer appears

pnm acl delete did:key:z6Mk...
pnm acl list --context my-app

The deleted DID is absent from the listing. If it was the only entry in that context, the listing reports No ACL entries found instead.

A token the DID already holds keeps working until it expires. See Revoking access for ending the session immediately.

Troubleshooting

SymptomLikely causeFix
409 Conflict when running pnm acl createAn ACL entry for this DID already exists.Use pnm acl update <did> to modify the existing entry, or pnm acl delete followed by pnm acl create to replace it.
Context-scoped admin cannot see all entriesThe caller’s ACL entry lists specific allowed_contexts.Check your own entry with pnm acl get <your-did>. Super-admin access is required to view entries outside your allowed contexts.
pnm acl change-role rejected with a compare-and-swap errorThe entry’s current role differs from the value in --from.Re-read the entry with pnm acl get <did> and retry with the correct --from value.
pnm acl update <did> --role ... is rejectedRole changes must go through the compare-and-swap flow, not pnm acl update.Use pnm acl change-role --did <did> --from <current-role> --to <new-role> instead.
Revoked DID continues to authenticate for up to 15 minutesACL deletion closes off new sessions, and a JWT the DID already holds stays valid until it expires.Revoke the DID’s sessions as well, with DELETE /auth/sessions?did=<did>. See Ending existing sessions immediately.
Session revocation returns cannot revoke sessions for a DID outside your contextsSession revocation is scoped by the ACL entries visible to a context-scoped admin, and this DID’s entry has already been deleted.Ask a super-admin to clear the remaining sessions. In future, revoke sessions before deleting the entry.
forbidden when managing entries in another contextThe caller is context-scoped and the target entry is outside that scope.Use a super-admin credential, or ask a super-admin to make the change.

Next steps

  Sign application payloads without exposing your keys: the bootstrap provisioning flow creates the ACL entry automatically.

  Retrieve credentials from the VTA vault at runtime: ACL context scope determines which services can release a vault entry.

  Back up and restore VTA state: ACL entries are included in VTA backups.