linq has three roles that form a strict linear hierarchy:Documentation Index
Fetch the complete documentation index at: https://mintlify.com/org-quicko/linq/llms.txt
Use this file to discover all available pages before exploring further.
viewer < editor < admin. Every API key carries exactly one role, expressed as an expanded set of CASL claims stored directly on the key. There are no groups, no per-resource ownership, and no way to grant partial access within a role — a key either has a claim or it does not. Because permissions are defined in @linq/shared and consumed by both the server and the Client UI, the two can never drift apart: the UI hides actions the key cannot perform, and the server enforces the same rules independently.
Role Hierarchy
Viewer
Read-only access. Can inspect links, domains, visits, analytics, QR codes,
rules, and keys (summary only). Cannot create, modify, or delete anything.
Ideal for dashboards, monitoring integrations, and read-only reporting tools.
Editor
Everything a viewer can do, plus the ability to create and update links,
rules, and QR codes. Cannot archive, restore, or purge links, and cannot
touch domains or keys. The right role for deployment pipelines and content
publishing integrations.
Admin
Everything an editor can do, plus the ability to archive, restore, and purge
links; create, update, archive, and purge domains; and create, update, and
revoke API keys. Required for any management operation. Bootstrap keys are
always admin.
Permissions Reference
The table below lists every guarded action and the minimum role required. A higher role inherits all permissions of every role below it.| Action | Minimum Role |
|---|---|
| Read links | viewer |
| Read domains | viewer |
| Read visits and analytics | viewer |
| Read QR codes | viewer |
| Read rules | viewer |
| Read keys (summary) | viewer |
| Read keys (full, with prefix and timestamps) | admin |
| Create a link | editor |
| Update any link | editor |
| Create a rule on a link | editor |
| Update a rule on a link | editor |
| Create a QR code for a link | editor |
| Update a QR code | editor |
| Delete a QR code | editor |
| Archive a link | admin |
| Restore an archived link | admin |
| Purge an archived link | admin |
| Create a domain | admin |
| Update a domain | admin |
| Archive a domain | admin |
| Purge an archived domain | admin |
| Create an API key | admin |
| Update an API key (name, claims, expiry) | admin |
| Revoke (delete) an API key | admin |
These permissions are sourced directly from
packages/shared/src/abilities.ts
(claimsForPreset) and packages/shared/src/permissions.ts (can). The same
defineAbility function builds the CASL ability object on the server at
authentication time and in the Client UI for gating navigation and form
controls. The source of truth is the claim set on the key, not the preset label.Preset Claim Sets
When you create a key withpreset: "viewer", preset: "editor", or
preset: "admin", linq expands the preset to its full claim list at write time
and stores the expanded claims — not the preset name — in the database. The
preset field returned by the API is derived at read time by comparing the
stored claims against the known preset definitions.
Viewer claims (read all subjects)
Viewer claims (read all subjects)
Editor claims (viewer + link/rule/QR mutations)
Editor claims (viewer + link/rule/QR mutations)
Admin claims (editor + archive/purge/domain/key management)
Admin claims (editor + archive/purge/domain/key management)
Links Are Unowned
An important property of linq’s permission model: links carry no reference to the key that created them. There is no concept of “a link I own” vs “a link someone else owns”. An editor key can create and update any link, regardless of which key created it. This means:- Revoking an editor’s key never changes, archives, or deletes any link.
- All editors share the same write scope — the whole link namespace.
- Admin-level operations (archive, restore, purge) apply to all links equally, regardless of creation history.
docs/adr/0016 in the source repo for the reasoning. It simplifies permission checks and avoids a class of bugs where a resource becomes inaccessible because its creator’s key was revoked.
Assigning and Changing Roles
At creation time, passpreset in the request body:
--preset:
PATCH /api/v1/keys/{id} with a new preset or
explicit claims array:
401 vs 403
linq distinguishes two authorization failure modes:| Status | Meaning | Cause |
|---|---|---|
401 Unauthorized | The request carries no valid key | Key is missing, unrecognised, or expired |
403 Forbidden | The key is valid but lacks the required claim | Correct key, insufficient role for this operation |
authenticate middleware (auth/middleware.ts) rejects unknown or expired keys with 401 before any handler runs; then each handler calls assertCan(principal, action, subject) (auth/permissions.ts) which throws 403 if the key’s CASL ability does not include the required claim.
Using CASL in Your Own Code
If you are building a UI or integration that mirrors linq’s permission model, import@linq/shared and use the same can helper the Client UI uses. Pass the authenticated key’s claims to reconstruct the same ability object without making additional API calls:
can object exposes named helpers: createLink, editLink, archiveLink,
purge, manageDomains, manageKeys, changeRoleOf, and revokeKey. Each
evaluates the CASL ability built from the key’s claims — the same evaluation the
server performs in assertCan.