Link permissions
Links and campaigns use the same private-resource policy. A human owns what they create; readers can inspect it, editors can change/delete it, and managers can also share it. Platform administrator claims do not bypass this policy.
Ordinary personal use
Create a campaign and links as usual. A workspace selector is optional. Omitting it uses the account or organization's internal root; you do not need to create a named workspace. Human resources default to private. A missing or foreign explicit workspace is an error and never falls back to that root.
snug link campaign create --name personal --domain go.example.com
snug link create --campaign-id CAMPAIGN --url https://example.com
snug link access grant --resource-id LINK --user-id USER --role readerTeam workspaces and groups
Create a named workspace when a team needs a separate sharing boundary. Workspace roles are reader, editor, manager and creator; these are shared across services. A creator grant permits creation without granting readership of every resource. The account root's initial administration is limited to the live personal owner or organization Owner/Admin. It does not unlock private resources.
snug access workspace create --name Engineering --visibility private
snug access group create --name Reviewers
snug access group add-member --id GROUP --user-id USER
snug access grant --target workspaces --id WORKSPACE \
--subject-kind group --subject-id GROUP --role reader
snug link campaign create --name releases --domain go.example.com \
--workspace-id WORKSPACE --visibility workspaceA workspace resource uses its workspace's grants. A private resource excludes
those grants and admits only its owner/direct sharing. Named workspaces inherit
parent grants unless inheritance is disabled. Changing the default visibility
changes future creation defaults; it does not rewrite existing resources.
snug access workspace policy --id WORKSPACE --inherit-access false --visibility private
snug access revoke --target workspaces --id WORKSPACE \
--subject-kind group --subject-id GROUP --role readerGroups are evaluated live. Removing a member removes the group path; an independent direct grant remains. These content groups contain human memberships and are not copied from token group claims. A removed organization member fails the live Auth boundary. When the same user rejoins, surviving ownership and grants apply again.
Independent service accounts
A person's API key still acts as that person. A service account is a different, organization/account-owned identity with separately revocable credentials. Its resources use workspace authority; the key issuer is not their owner.
snug access service-account create --name Publisher
snug access grant --target workspaces --id WORKSPACE \
--subject-kind service_account --subject-id SERVICE --role creator
snug access service-account issue-key --id SERVICE --name deployment --scope link:writeKey issuance returns the secret once. Store it securely and use it as a bearer
credential. Permission roles are stored in the database, not in the key or JWT.
Scopes are an additional ceiling: link:read cannot write even when its principal
has editor access. Empty scopes grant nothing. Issuing a new key requires a human
session with service-account administration rights.
A service-created resource must use workspace visibility. It retains the service
creator for attribution, while workspace managers control it. A create-only service
can create a campaign without subsequently reading its private management data.
Rotate by issuing a second key, updating the caller, then revoking the first:
snug access service-account list-keys --id SERVICE
snug access service-account revoke-key --id SERVICE --key-id OLD_KEY
snug access service-account disable --id SERVICERevoking one key leaves other keys and resource custody intact. Disabling the service removes its permission eligibility for every key. These credentials never receive the privileges of trusted internal service tokens.
HTTP equivalents
The shared control plane is under /api/v1/access:
GET/POST /workspaces;PATCH/DELETE /workspaces/{id}.GET/POST /groups;PATCH/DELETE /groups/{id};POST/DELETE /groups/{id}/members.GET/POST /service-accounts;PATCH /service-accounts/{id}.GET/POST /service-accounts/{id}/keys;DELETE /service-accounts/{id}/keys/{key}.POST/DELETE /{target}/{id}/grants, where target isworkspaces,linksorcampaigns.
A shared grant body names one subject and role:
{
"subject": {"kind": "group", "id": "GROUP"},
"role": "reader",
"expires_at": "2027-01-01T00:00:00Z"
}Subject kinds are user, group, and service_account. Expiry is optional.
The existing Link-specific /links/{id}/grants and campaign grant endpoints take
user_id and role for direct human sharing. Revocation removes only the named role.
Lists use the normal bounded items-collection/page contract for their named
collection; filtering and totals retain the authenticated permission boundary.
Public redirect URLs are a separate, narrow capability. They do not grant access to campaign/link management, and password, status, expiry and routing still apply. Human Auth membership and resource transactions use separate stores: an already running resource transaction cannot be atomically cancelled by a Redis membership change. Subsequent requests resolve live membership again.
