Roles, permissions & API keys
Every request to justcrawl carries an org context and a permission set. Routes check requirePermission(...) before they run — if your token's permissions don't include what the route needs, you get 403 Insufficient permissions with the missing permission name in the body. This page covers how to read that gate, the four built-in roles, how to build a custom role, how to invite teammates, and when to use an API key vs a session JWT.
The 13 permissions
Section titled “The 13 permissions”Every protected action maps to one of these strings. They're stored verbatim in the roles.permissions array and checked by the gateway.
| Permission | What it gates |
|---|---|
| workflows:read | List workflows, fetch one by ID, see the DAG. |
| workflows:write | Create or edit a workflow. |
| workflows:publish | Mint a new published version that schedules and live traffic start using. |
| workflows:delete | Soft-delete a workflow. |
| urls:read | List URLs in the library, fetch one. |
| urls:write | Upload a CSV, add a URL, edit tags. |
| urls:delete | Remove a URL from the library. |
| jobs:read | List jobs, fetch a job + its result. |
| jobs:submit | POST /api/v1/jobs — scrape a URL. The credit-charging permission. |
| schedules:read | List schedules, fetch one. |
| schedules:write | Create or edit a schedule. |
| schedules:delete | Remove a schedule. |
| org:manage | The "admin" permission. Edit org settings, manage members, create custom roles, rotate API keys. |
These are the only 13 strings the gateway will recognize. Adding an unknown permission to a role is silently accepted on write but is never granted on a check.
The four built-in roles
Section titled “The four built-in roles”Every org is seeded with four system roles at creation time. They cannot be edited or deleted.
| Role | Permissions | When to use |
|---|---|---|
| Owner | All 13 | The org creator. Identical to Admin in permissions, but protected against demotion when sole owner. |
| Admin | All 13 | Trusted operators who manage settings, members, and roles. |
| Editor | All 13 except org:manage | Builders. Can create workflows, schedules, jobs, and URLs but cannot manage members or rotate keys. |
| Viewer | workflows:read, urls:read, jobs:read, schedules:read | Read-only. Cannot submit jobs (no jobs:submit), cannot publish workflows. |
Owner and Admin have identical permissions today — the difference is the sole-owner guard: the gateway refuses to demote or remove the last Owner of an org, so you can't accidentally lock everyone out.
Custom roles
Section titled “Custom roles”If the four system roles don't fit (e.g., "build workflows but never publish them to live traffic"), build your own. Requires org:manage.
curl -X POST https://dashboard.justcrawl.io/api/v1/roles \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Draft Builder", "permissions": [ "workflows:read", "workflows:write", "urls:read", "urls:write", "jobs:read", "schedules:read" ] }'Same surface available in Settings → Roles in the dashboard.
Rules:
- The role
nameis unique per org. Reusing a name returns409 A role with that name already exists. permissionsmust be a non-empty array. The gateway does not validate that each string is a known permission — unknowns are stored but never granted.PUT /api/v1/roles/{id}edits the name or permissions of a custom role. System roles cannot be edited —PUTon Owner / Admin / Editor / Viewer returns404 Role not found or is a system role.DELETE /api/v1/roles/{id}removes a custom role. The endpoint refuses with409if any member is currently assigned that role — reassign them first.
Inviting members
Section titled “Inviting members”Adding a teammate to an org takes a single POST. Requires org:manage.
curl -X POST https://dashboard.justcrawl.io/api/v1/orgs/current/members \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "email": "teammate@example.com", "roleId": "role-uuid-from-listing" }'The inviting admin must also have a verified email — the endpoint returns 403 Please verify your email before inviting team members otherwise. Check Settings → Account for the verification link.
To change a member's role: PATCH /api/v1/orgs/current/members/{userId}/role with { "roleId": "..." }. The gateway blocks demoting the sole Owner — transfer ownership first by promoting another member, then demote the original.
To remove a member: DELETE /api/v1/orgs/current/members/{userId}. Same sole-Owner guard applies.
API keys vs session JWTs
Section titled “API keys vs session JWTs”The same requirePermission gate runs for both auth modes. They differ in how the token is minted, how long it lives, and what carries the permission set.
| | API key (sr_live_…) | Session JWT |
|---|---|---|
| Created by | POST /api/v1/api-keys (any authenticated user in the org) | Issued automatically on login / sign-up |
| Format | sr_live_<32 hex chars> (40 chars total) | Standard JWT — eyJ... |
| Lifespan | No expiry by default. Optional expiresAt on create. Revoked via DELETE /api/v1/api-keys/{id}. | 15-minute access token + 7-day refresh token |
| Permissions source | The role the creating user holds right now, looked up live on every request via (api_key → org_membership → role). Demote the user and the key's powers shrink on the very next request. | The role the user holds right now, baked into the access token each time it's minted |
| Returned plaintext | Once, in the POST response body — never retrievable again. Lose it, rotate it. | Held by the browser session; not directly accessible |
| Where to use | CLI, scripts, CI/CD, MCP server, anything non-browser | The dashboard SPA |
| Impersonation marker | Never | Admin impersonation JWTs carry impersonator_admin_id for audit |
Picking the right one
Section titled “Picking the right one”- Building a scraping pipeline / CI job / agent integration → API key. Set it as
JUSTCRAWL_API_KEYin the environment, pass it asAuthorization: Bearer sr_live_…. - Logged-in user clicking around the dashboard → session JWT. Handled for you automatically.
- Building a server-side integration that acts on behalf of multiple orgs → one API key per org. Each key is org-scoped; there's no super-key.
Issuing a key
Section titled “Issuing a key”curl -X POST https://dashboard.justcrawl.io/api/v1/api-keys \ -H "Authorization: Bearer YOUR_SESSION_JWT" \ -H "Content-Type: application/json" \ -d '{ "name": "CI bot — main pipeline", "expiresAt": "2027-01-01T00:00:00Z" }'Response includes the full key exactly once:
{ "id": "ak_...", "name": "CI bot — main pipeline", "keyPrefix": "sr_live_abcd", "expiresAt": "2027-01-01T00:00:00Z", "key": "sr_live_abcd1234ef567890..."}Stash it somewhere safe (secrets manager, env var). Subsequent GET /api/v1/api-keys returns the prefix only — there is no way to recover the full key.
Rotating
Section titled “Rotating”Create a new key, deploy it to your consumers, then DELETE the old one. Old key stops authenticating immediately on delete.
Reading the 403
Section titled “Reading the 403”When a request is gated, the gateway emits:
{ "error": "Insufficient permissions", "missing": ["workflows:publish"]}The missing array lists every required permission the token does not have. Use it to figure out which permission to add (or which role to switch to) — no guessing.
If the token has no org context (e.g., the user hasn't completed onboarding), the gateway returns 403 No organization. Complete onboarding first. instead.
Related
Section titled “Related”- API Quickstart — get a
sr_live_…key and make your first authenticated request - Rate limits & quotas — what credit-consuming routes do when you're out of quota
- Job lifecycle —
jobs:submitis the permission that fires the lifecycle - Integrations — webhook / S3 output configs require
org:manage