Skip to content
On this page

Authentication

Spooled uses API keys for authentication. Each key belongs to one organization and can be restricted to specific queues.

API Keys

API keys authenticate all requests to Spooled. Create a separate key per service, so each one can be restricted to the queues it uses and revoked on its own.

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ecfdf5', 'primaryTextColor': '#065f46', 'primaryBorderColor': '#10b981', 'lineColor': '#6b7280'}}}%%
sequenceDiagram
  participant App as Your App
  participant S as Spooled API
  
  App->>S: Request with API Key
  Note right of S: Authorization: Bearer sp_live_...
  S->>S: Validate key
  S-->>App: Response

Key Format

Prefix Environment Example
sp_live_ Production sp_live_abc123def456...
sp_test_ Test/Development sp_test_xyz789uvw012...

Dashboard Tip

Account → API Keys

What to look for:

  • → Key name and creation date
  • → Last used timestamp (coarse — written at most once every 5 minutes)
  • → Queue scope (All queues, or the listed queues)

Actions:

  • Create new keys
  • Revoke compromised keys
  • Rotate keys regularly

Using API Keys

For REST, include your API key or JWT in the Authorization: Bearer … header (JWT tokens are detected by an eyJ prefix). The header is the only transport REST accepts: do not use an X-API-Key header, and do not put the credential in the query string.

Authentication header
# All API requests require authentication
curl -X POST https://api.spooled.cloud/api/v1/jobs \
  -H "Authorization: Bearer sp_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"queue_name": "my-queue", "payload": {...}}'

Query-string credentials are rejected on REST

A REST request carrying ?api_key=… or ?token=… returns 401 Unauthorized. Move the credential into the Authorization: Bearer … header — nothing else changes about the request. Query strings are recorded by reverse-proxy and CDN access logs, tracing spans, browser history and the Referer header, and API keys do not expire by default, so a key that lands in any of those stays usable indefinitely (CWE-598).

Four realtime routes still accept the credential in the query string, because browser EventSource and WebSocket clients cannot set headers: /api/v1/ws, /api/v1/events, /api/v1/events/jobs/{id} and /api/v1/events/queues/{name}. See Real-time Updates. Everywhere else, use the header.

For gRPC, pass the API key only in metadata as x-api-key — the gRPC path does not accept JWT.

x-api-key: sp_live_your_api_key

Environment Variables

Never hardcode API keys. Use environment variables to keep them secure.

Environment configuration
# .env file (add to .gitignore!)
SPOOLED_API_KEY=sp_live_your_api_key

# Use in shell
export SPOOLED_API_KEY=sp_live_your_api_key
curl -H "Authorization: Bearer $SPOOLED_API_KEY" ...

Security Warning

  • • Never commit API keys to version control
  • • Add .env to your .gitignore
  • • Use different keys for development and production
  • • Rotate keys periodically

Creating API Keys

Create keys via the dashboard or API:

Create API key
# Create a new API key
curl -X POST https://api.spooled.cloud/api/v1/api-keys \
  -H "Authorization: Bearer sp_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Email worker",
    "queues": ["emails"]
  }'

# Response (key shown only once!)
# Omit "queues" (or send []) for a key that can reach every queue.
{
  "id": "key_xxxxx",
  "key": "sp_live_a1b2c3d4...",
  "name": "Email worker",
  "queues": ["emails"],
  "created_at": "2025-01-15T10:30:00Z",
  "expires_at": null
}

Restricting a key

Spooled keys have no per-permission scopes (there is no read-only key): every key can call every API route of its organization. The way to limit a key is its queue scope. Pass queues when creating or updating a key, and it can only enqueue, claim, read and manage jobs in those queues:

queues What the key can reach
omitted, [] or ["*"] All queues — plus organization-wide routes such as API keys, billing and settings
["emails", "billing"] Only jobs, workers, schedules and events in emails and billing. It cannot manage API keys or organization settings, and cannot mint a key broader than itself.
["orders.*"] Every queue whose name starts with orders. (for example orders.eu and orders.eu.west), including queues created later. It does not cover orders itself: list that name too if you need it. * is only valid alone or as a trailing .* after a prefix; orders* and *.eu are rejected.

One queue per location, keys by prefix

For a tenant, store, restaurant or customer, give each location one queue and name the queues with a shared prefix: orders.store-42, orders.store-43. A key scoped to ["orders.store-42"] reaches only that location, so a leaked key exposes one site. A key scoped to ["orders.*"] reaches every location, including ones created later: use it for a service that works for many locations (a spooler, a dispatcher) and keep it on trusted machines.

Do the routing inside a location with the payload or the job type, for example a printer field for which printer in the kitchen should print a ticket, rather than with one queue per printer or device. Every queue counts against the plan's queue cap, and so do API keys and workers, so a queue per printer runs out of queues long before a queue per location does. Size the plan to the number of locations (see Rate Limits & Quotas); queue-prefix keys change how you scope access, not what the plan allows.

A permissions field is rejected

Older versions of this page described jobs:read-style permissions. The API never enforced them, so a key created with "permissions": ["jobs:read"] had full access. Since API 0.1.114, sending permissions returns 400 VALIDATION_ERROR instead of silently creating that key. Use queues, and give each service its own key so a leaked one can be revoked without touching the rest.

Key Rotation

Rotate API keys periodically for security. The recommended approach:

  1. Create a new API key with the same queue scope
  2. Update your services to use the new key
  3. Verify everything works with the new key
  4. Revoke the old key

API Key Issues

Account → API Keys

What to look for:

  • → Key status (active vs revoked)
  • → Last used timestamp — updated at most once every 5 minutes, so a working key can still look idle
  • → Key prefix matches environment

Actions:

  • Check key hasn't been revoked
  • Verify using correct environment key
  • Confirm the credential is in the Authorization header, not the query string
  • Generate a new key if compromised

Rate Limits

API keys are subject to per-plan rate limits. When rate limited, you'll receive a 429 Too Many Requests response with a Retry-After header telling you how long to wait before retrying.

The exact request-per-second limits, burst allowances, and quota-enforcement behaviour are listed — as a single source of truth — on the Rate Limits & Quotas page.

Next Steps

Last updated 2026-10-16