Use keys and scopes
Make a key for each scope, see what each one may do and what it is refused, and make a key with more than one scope. Every request and its answer is shown.
A key holds scopes, and each route needs one scope. This how-to makes a key for each of the five tenant scopes and an operator key. It uses each key on the route its scope allows and on one route its scope does not allow. Tenants and keys lists every scope and route.
CI runs every command on this page against a live server, after getting started, and checks each answer.
Before you start
- A running server, set up as in getting started,
with the tenant
acme, and the certificate's copy for clients at/usr/local/share/vcon-server/ca.pem. - One shell for every step: later steps use variables that earlier steps set.
URL=https://vcon.example.com:8443
CONFIG=/etc/vcon-server/vcon-server.toml
TENANT=$(sudo -u vcon vcon-server tenant list --config $CONFIG | jq -r 'select(.name == "acme") | .id')
Make a key for each scope
A tenant key belongs to one tenant and holds any of write, read,
delete, legal and audit. Make one key for each, labelled with its
scope. key create runs beside the server:
for scope in write read delete legal audit; do
token=$(sudo -u vcon vcon-server key create --tenant "$TENANT" --scope $scope --label "scopes-$scope" \
--config $CONFIG | jq -r .token)
declare "KEY_${scope^^}=$token"
done
That sets KEY_WRITE, KEY_READ, KEY_DELETE, KEY_LEGAL and KEY_AUDIT.
An operator key belongs to no tenant and holds only admin. It is made on
the server's host only, never over HTTP:
KEY_ADMIN=$(sudo -u vcon vcon-server key create --operator --label scopes-admin --config $CONFIG | jq -r .token)
Ask a key what it holds
GET /key answers any valid key with its own id, tenant and scopes:
curl -sS --cacert /usr/local/share/vcon-server/ca.pem "$URL/key" -H "Authorization: Bearer $KEY_LEGAL"
{"id":"<key id>","scopes":["legal"],"tenant":"<tenant id>"}
An operator key's tenant is null:
curl -sS --cacert /usr/local/share/vcon-server/ca.pem "$URL/key" -H "Authorization: Bearer $KEY_ADMIN"
{"id":"<key id>","scopes":["admin"],"tenant":null}
write: store and replace
The write key stores a vCon:
UUID=$(cat /proc/sys/kernel/random/uuid)
printf '{"vcon":"0.4.0","uuid":"%s","created_at":"2026-10-27T12:00:00Z","parties":[{"tel":"+15555550102"}],"dialog":[],"attachments":[],"analysis":[]}' \
"$UUID" > scopes.vcon
curl -sS -o /dev/null -w '%{http_code}\n' --cacert /usr/local/share/vcon-server/ca.pem "$URL/vcons" \
-H "Authorization: Bearer $KEY_WRITE" -H "Content-Type: application/vcon" --data-binary @scopes.vcon
201
It cannot read one back:
curl -sS -o /dev/null -w '%{http_code}\n' --cacert /usr/local/share/vcon-server/ca.pem "$URL/vcons/$UUID" -H "Authorization: Bearer $KEY_WRITE"
403
read: read and list
The read key reads the vCon and lists the tenant's vCons:
curl -sS -o /dev/null -w '%{http_code}\n' --cacert /usr/local/share/vcon-server/ca.pem "$URL/vcons/$UUID" -H "Authorization: Bearer $KEY_READ"
curl -sS --cacert /usr/local/share/vcon-server/ca.pem "$URL/vcons" -H "Authorization: Bearer $KEY_READ" | jq -r --arg u "$UUID" '.vcons[] | select(.uuid == $u) | .uuid'
200
<uuid>
It cannot store one:
curl -sS -o /dev/null -w '%{http_code}\n' --cacert /usr/local/share/vcon-server/ca.pem "$URL/vcons" \
-H "Authorization: Bearer $KEY_READ" -H "Content-Type: application/vcon" --data-binary @scopes.vcon
403
delete: delete
The read key cannot delete the vCon. The delete key can:
curl -sS -o /dev/null -w '%{http_code}\n' --cacert /usr/local/share/vcon-server/ca.pem -X DELETE "$URL/vcons/$UUID" -H "Authorization: Bearer $KEY_READ"
curl -sS -o /dev/null -w '%{http_code}\n' --cacert /usr/local/share/vcon-server/ca.pem -X DELETE "$URL/vcons/$UUID" -H "Authorization: Bearer $KEY_DELETE"
403
204
The delete key cannot read what it deleted, and neither can the read key:
curl -sS -o /dev/null -w '%{http_code}\n' --cacert /usr/local/share/vcon-server/ca.pem "$URL/vcons/$UUID" -H "Authorization: Bearer $KEY_DELETE"
curl -sS -o /dev/null -w '%{http_code}\n' --cacert /usr/local/share/vcon-server/ca.pem "$URL/vcons/$UUID" -H "Authorization: Bearer $KEY_READ"
403
410
legal: read what was deleted, hold it, restore it
The legal scope reaches the /legal/ routes. They read a deleted vCon,
list its versions, hold it, and restore it. The legal key reads the deleted
vCon. vcon-state says it is deleted:
curl -sS -i --cacert /usr/local/share/vcon-server/ca.pem "$URL/legal/vcons/$UUID" -H "Authorization: Bearer $KEY_LEGAL" | grep -i -e '^HTTP' -e '^vcon-state'
HTTP/2 200
vcon-state: deleted
It lists the vCon's kept versions, oldest first:
curl -sS --cacert /usr/local/share/vcon-server/ca.pem "$URL/legal/vcons/$UUID/versions" -H "Authorization: Bearer $KEY_LEGAL" | jq -c '.[] | {current, hidden: (.hidden_at != null)}'
{"current":false,"hidden":true}
A legal hold keeps every version of the uuid from being purged, whatever its retention period. Hold it, restore it, and release the hold:
curl -sS -o /dev/null -w '%{http_code}\n' --cacert /usr/local/share/vcon-server/ca.pem -X PUT "$URL/legal/vcons/$UUID/hold" -H "Authorization: Bearer $KEY_LEGAL"
curl -sS -o /dev/null -w '%{http_code}\n' --cacert /usr/local/share/vcon-server/ca.pem -X POST "$URL/legal/vcons/$UUID/restore" -H "Authorization: Bearer $KEY_LEGAL"
curl -sS -o /dev/null -w '%{http_code}\n' --cacert /usr/local/share/vcon-server/ca.pem -X DELETE "$URL/legal/vcons/$UUID/hold" -H "Authorization: Bearer $KEY_LEGAL"
204
200
204
The restored vCon is live again for the read key:
curl -sS -o /dev/null -w '%{http_code}\n' --cacert /usr/local/share/vcon-server/ca.pem "$URL/vcons/$UUID" -H "Authorization: Bearer $KEY_READ"
200
The read key cannot use the legal routes:
curl -sS -o /dev/null -w '%{http_code}\n' --cacert /usr/local/share/vcon-server/ca.pem "$URL/legal/vcons/$UUID" -H "Authorization: Bearer $KEY_READ"
403
Retention and legal holds describes when a deleted vCon is purged and what a hold stops.
audit: the tenant's audit log
The audit key reads the tenant's audit log, one JSON record a line, oldest
first. Each request above is in it, refusals included. The hold, the
restore and the release are recorded as hold, restore and release:
curl -sS --cacert /usr/local/share/vcon-server/ca.pem "$URL/audit?limit=10000" -H "Authorization: Bearer $KEY_AUDIT" \
| jq -c 'select(.action == "hold" or .action == "restore" or .action == "release") | {action, status}'
{"action":"hold","status":204}
{"action":"restore","status":200}
{"action":"release","status":204}
A key without audit is refused:
curl -sS -o /dev/null -w '%{http_code}\n' --cacert /usr/local/share/vcon-server/ca.pem "$URL/audit" -H "Authorization: Bearer $KEY_LEGAL"
403
Audit logs describes every field of a record.
admin: tenants and keys, never a vCon
The operator key lists the tenants:
curl -sS --cacert /usr/local/share/vcon-server/ca.pem "$URL/admin/tenants" -H "Authorization: Bearer $KEY_ADMIN" | jq -r '.[].name'
acme
It cannot read a vCon, and a tenant key cannot use the admin routes:
curl -sS -o /dev/null -w '%{http_code}\n' --cacert /usr/local/share/vcon-server/ca.pem "$URL/vcons" -H "Authorization: Bearer $KEY_ADMIN"
curl -sS -o /dev/null -w '%{http_code}\n' --cacert /usr/local/share/vcon-server/ca.pem "$URL/admin/tenants" -H "Authorization: Bearer $KEY_AUDIT"
403
403
A key with more than one scope
A producer that stores vCons and reads them back needs write and read.
Give --scope once for each:
KEY_RW=$(sudo -u vcon vcon-server key create --tenant "$TENANT" --scope write --scope read --label scopes-rw \
--config $CONFIG | jq -r .token)
curl -sS --cacert /usr/local/share/vcon-server/ca.pem "$URL/key" -H "Authorization: Bearer $KEY_RW" | jq -c .scopes
["write","read"]
Give each producer, reader and process its own key, with only the scopes it
needs and a label that names who holds it. A request whose key lacks the
scope is refused with forbidden, and the audit
log records the refusal.
Clean up
Revoke the keys this page made. key revoke runs beside the server:
sudo -u vcon vcon-server key list --config $CONFIG | jq -r 'select(.label | startswith("scopes-")) | .id' \
| while read -r id; do sudo -u vcon vcon-server key revoke "$id" --config $CONFIG; done
sudo -u vcon vcon-server key list --config $CONFIG | jq -r 'select(.label | startswith("scopes-")) | select(.revoked_at == null) | .label'
The second command prints nothing: no key from this page is left unrevoked.
Source of this page: website/content/docs/keys-and-scopes.md