Permissions
Source of truth: packages/registry-core/src/constants.ts (scopes), apps/web/src/server/api.ts (route table), apps/web/src/server/auth.ts (rules). If this file and the code disagree, the code is right and this file has a bug.
Principals
| Principal | How it authenticates | What it carries |
|---|---|---|
| Anonymous | nothing | read access to public content, abuse reports |
| Session | skills_session cookie after email + password login |
every non-elevated right of the user; elevated rights if the user is owner or admin; token creation and rotation need a password entry within 15 minutes |
| Token | Authorization: Bearer skr_… |
exactly its scopes, within exactly its namespaces |
| Local operator | shell on the server, pnpm admin … |
everything; never reachable over the network |
Scopes
| Scope | Allows | In the default publisher token |
|---|---|---|
artifacts:read |
Read artifacts in allowed namespaces, including non-public ones | yes |
artifacts:create |
Create artifacts in allowed namespaces | yes |
artifacts:update |
Update mutable metadata: summary, description, links, keywords, visibility | yes |
artifacts:deprecate |
Deprecate, undeprecate or archive artifacts | no |
uploads:create |
Begin release uploads and send archive bytes | yes |
releases:publish |
Finalize an upload into an immutable release | yes |
releases:update |
Edit release notes only | yes |
releases:revoke |
Revoke a compromised release | no |
tokens:read |
List the owner's tokens (metadata only) | no |
tokens:write |
Create or revoke tokens, never broader than the calling token | no |
audit:read |
Read the owner's audit trail | no |
admin:moderate |
ELEVATED. Reports, quarantine review, takedown, platform audit trail | no |
admin:settings |
ELEVATED. Collection curation | no |
Rules that apply on top of scopes:
- Every write to an artifact or release also requires that the user owns the namespace (or holds
org_adminorpublisherin the owning organization) and, for a token, that the namespace is in the token's list. - Elevated scopes are only honoured for users whose platform role is
owneroradmin. Elevated tokens expire within 24 hours and cannot be issued or rotated by another token. - A token can always revoke or rotate itself without any token scope.
- Public visibility, deprecation, archival, revocation and takedown require an explicit
confirm/confirmPublicflag in the request; without it the API answers 428.
Route matrix
All paths are under /api/v1. "Bucket" is the rate-limit bucket; the key is the token id, else the user id, else the client IP.
| Method | Path | Auth | Scope | Bucket | Idempotency-Key |
|---|---|---|---|---|---|
| GET | /health/live |
public | — | — | — |
| GET | /health/ready |
public | — | — | — |
| GET | /openapi.json |
public | — | — | — |
| GET | /artifacts |
public | — | anon-read | — |
| GET | /search |
public | — | search | — |
| GET | /categories |
public | — | anon-read | — |
| GET | /collections |
public | — | anon-read | — |
| GET | /collections/{slug} |
public | — | anon-read | — |
| GET | /artifacts/{id} |
public | — | anon-read | — |
| GET | /artifacts/{namespace}/{slug} |
public | — | anon-read | — |
| GET | /artifacts/{namespace}/{slug}/versions |
public | — | anon-read | — |
| GET | /artifacts/{namespace}/{slug}/history |
public | — | anon-read | — |
| GET | /artifacts/{namespace}/{slug}/versions/{version} |
public | — | anon-read | — |
| GET | /artifacts/{namespace}/{slug}/versions/{version}/download |
public | — | download | — |
| GET | /artifacts/{namespace}/{slug}/versions/{version}/checksum |
public | — | anon-read | — |
| GET | /artifacts/{namespace}/{slug}/versions/{version}/files?path= |
public | — | anon-read | — |
| GET | /artifacts/{namespace}/{slug}/versions/{version}/install?client= |
public | — | anon-read | — |
| POST | /artifacts |
required | artifacts:create |
token-write | yes |
| PATCH | /artifacts/{id} |
required | artifacts:update |
token-write | — |
| POST | /artifacts/{id}/releases |
required | uploads:create |
upload | yes |
| GET | /uploads/{id} |
required | uploads:create |
token-write | — |
| PUT | /uploads/{id} |
required | uploads:create |
upload | — |
| POST | /uploads/{id}/finalize |
required | releases:publish |
publish | yes |
| POST | /artifacts/{id}/deprecate |
required | artifacts:deprecate |
token-write | — |
| POST | /artifacts/{id}/undeprecate |
required | artifacts:deprecate |
token-write | — |
| POST | /artifacts/{id}/archive |
required | artifacts:deprecate |
token-write | — |
| POST | /releases/{id}/revoke |
required | releases:revoke |
token-write | — |
| PATCH | /releases/{id} |
required | releases:update |
token-write | — |
| POST | /validate-manifest |
required | any | token-write | — |
| GET | /me |
required | any | token-write | — |
| GET | /me/tokens |
required | tokens:read |
token-write | — |
| POST | /me/tokens |
required | tokens:write |
token-write | — |
| DELETE | /me/tokens/{id} |
required | self: any; other tokens: tokens:write |
token-write | — |
| POST | /me/tokens/{id}/rotate |
required | self: any; other tokens: tokens:write |
token-write | — |
| GET | /me/audit |
required | audit:read |
token-write | — |
| POST | /reports |
public | — | report | — |
| GET | /admin/reports |
required | admin:moderate |
token-write | — |
| POST | /admin/reports/{id}/resolve |
required | admin:moderate |
token-write | — |
| GET | /admin/quarantine |
required | admin:moderate |
token-write | — |
| POST | /admin/releases/{id}/review |
required | admin:moderate |
token-write | — |
| POST | /admin/artifacts/{id}/moderate |
required | admin:moderate |
token-write | — |
| GET | /admin/audit |
required | admin:moderate |
token-write | — |
Public read routes still honour credentials: with a session or a suitable token, the same routes also return the caller's private, draft and quarantined items.
Not exposed over HTTP at all: owner bootstrap, user creation, namespace creation, password recovery, identity verification of a namespace. These exist only in the server console.
Rate-limit buckets
| Bucket | Limit | Window |
|---|---|---|
| anon-read | 240 | 60 s |
| search | 90 | 60 s |
| download | 120 | 60 s |
| mcp | 240 | 60 s |
| token-write | 120 | 60 s |
| upload | 30 | 1 h |
| publish | 30 | 1 h |
| report | 5 | 1 h |
| login | 8 | 15 min (per IP and email) |
| auth-fail | 20 | 10 min (per IP; locks out bearer authentication) |
Responses carry RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset. Exceeding a limit returns 429 with Retry-After. Limits are held in process memory (ADR-0004).
Roles
Platform roles are a column on users. Organization roles are rows in memberships.
| Role | Status in V1 | Rights |
|---|---|---|
| Anonymous | Implemented + tested | Read public artifacts, releases, files, collections; download; file abuse reports |
| Member (platform) | Implemented + tested | Everything anonymous can do, plus publish to namespaces they own, manage their own tokens, read their own audit trail |
| Publisher (organization) | Authorization check implemented; no interface to create organizations or memberships | Publish to namespaces owned by the organization |
| Organization admin | Same as above | Same publish rights as publisher in V1; member management is Planned |
| Reviewer (platform or organization) | Schema only | Grants nothing in V1 |
| Platform admin | Implemented; role is assigned by the operator in the database | Moderation (admin:moderate), curation (admin:settings), read private artifacts through a session. No implicit right to publish in someone else's namespace |
| Platform owner | Implemented + tested | Same as platform admin. Exactly one owner can exist (database constraint). Created only by pnpm admin:bootstrap |
| Local operator | Implemented | Server console: bootstrap, create users and namespaces, issue or revoke any user's tokens, recover accounts, verify namespace identity, seed and curate collections, cleanup |
Planned: organization management, member invitations, ownership transfers with two-party confirmation, a reviewer role that can approve quarantined releases without full moderation rights.
Troubleshooting 401 and 403
| Status | error.code |
Meaning | What to do |
|---|---|---|---|
| 401 | unauthorized |
No credential sent | Add Authorization: Bearer <token> or run skills login |
| 401 | invalid_token |
Malformed, unknown, wrong secret, IP not on the allowlist, or the user is disabled | Check the stored token and the calling IP; create a new token |
| 401 | token_expired |
Past expiresAt |
Create a new token (an expired token cannot be rotated) |
| 401 | token_revoked |
Revoked or replaced by rotation | Use the replacement or create a new token |
| 403 | insufficient_scope |
The token lacks the scope named in details.required |
Issue a token with that scope |
| 403 | namespace_forbidden |
The namespace is not yours or not on this token | Use a token that lists the namespace |
| 403 | reauth_required |
Session password entry is older than 15 minutes | Re-enter the password |
| 403 | admin_required / elevation_forbidden |
Elevated action without an administrator session or elevated token | Use the dashboard or an elevated console-issued token |
| 403 | csrf |
Cookie-authenticated write without same-origin and CSRF token | Use a bearer token for scripts |
| 403 | password_change_required |
Initial password not yet changed | Sign in on the website and change it |
| 428 | confirmation_required |
Destructive or exposing action without confirmation | Resend with confirm: true / confirmPublic: true after a human approved it |
| 429 | rate_limited |
Too many requests or too many failed authentications from this IP | Wait for Retry-After |