Skip to content

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.

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.

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.

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.

Terminal window
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 name is unique per org. Reusing a name returns 409 A role with that name already exists.
  • permissions must 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 editedPUT on Owner / Admin / Editor / Viewer returns 404 Role not found or is a system role.
  • DELETE /api/v1/roles/{id} removes a custom role. The endpoint refuses with 409 if any member is currently assigned that role — reassign them first.

Adding a teammate to an org takes a single POST. Requires org:manage.

Terminal window
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.

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 |

  • Building a scraping pipeline / CI job / agent integration → API key. Set it as JUSTCRAWL_API_KEY in the environment, pass it as Authorization: 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.
Terminal window
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.

Create a new key, deploy it to your consumers, then DELETE the old one. Old key stops authenticating immediately on delete.

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.

  • 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 lifecyclejobs:submit is the permission that fires the lifecycle
  • Integrations — webhook / S3 output configs require org:manage