Skip to main content

API reference

Jobby.dev REST API

Stable v1 REST API for building agent integrations on top of Jobby.dev. Bearer-token auth, predictable error envelope, OpenAPI 3.1.

Last updated

Short answer: the Jobby.dev REST API is a stable v1 surface (~35 endpoints) for building agent integrations. Auth is bearer-token, errors are a uniform { error, code, details? } envelope, every authed response carries X-RateLimit-* headers, and the OpenAPI 3.1 spec at /api/v1/openapi.json is the source of truth.

Base URL

https://jobby.dev/api/v1

All paths in this reference are relative to that base. The v1 surface is committed to: breaking changes go to /api/v2, never to/api/v1.

Authentication

Mint a Personal Access Token at jobby.dev/account/api-tokens and pass it as a bearer token:

curl https://jobby.dev/api/v1/live-events \
  -H "Authorization: Bearer jbb_..."

Tokens are prefixed jbb_ and are scoped — when minting, select only the scopes the integration needs. The full scope list:

  • profile:read, profile:write
  • queue:read, queue:write
  • matches:read, matches:write
  • billing:read, billing:write
  • events:read, events:write (recruiter)
  • jobs:read, jobs:write (recruiter)
  • candidates:read, candidates:write (recruiter)
  • webhooks:read, webhooks:write
  • settings:read, settings:write

A token cannot accept a match or join the live video room. That tap is always human-driven — see on-demand job fair.

Errors

Every non-2xx response carries the same envelope:

{
  "error": "Off-session charges require one-time consent. ...",
  "code": "OFF_SESSION_CONSENT_REQUIRED",
  "details": { "target_plan": "pro", "interval": "monthly" }
}

error is always present and human-readable. code is a stable machine-readable identifier (use this for branching). details is type-specific. Common codes:

  • VALIDATION — request body failed schema check
  • RATE_LIMITED — see Retry-After
  • INSUFFICIENT_SCOPE— token doesn't carry the scope for this endpoint
  • NOT_FOUND — resource doesn't exist orcaller isn't the owner (existence isn't leaked across tenants)
  • DEPENDENCY_UNAVAILABLE — upstream (Stripe, Daily, Anthropic) unreachable; safe to retry

Rate limits

Per-token limits vary by endpoint cost. Cost-bearing endpoints (off- session charge, AI compare, room URL issuance) cap at 3/min; pure reads are 60/min. Every successful and failed response carries:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 1714780000

On 429, also Retry-After: <seconds>. Implement exponential backoff bounded by the reset timestamp.

OpenAPI

The OpenAPI 3.1 spec is the canonical reference and is hand-curated:

A CI invariant (scripts/check-openapi-drift.mjs) asserts every route file under apps/web/src/app/api/v1/ has a matching paths entry — adding a route without registering it fails the build.

SDKs

  • JavaScript / TypeScript@jobbydev/sdk (zero deps, Node 20+ / browsers / Workers / Bun / Deno).
  • Pythonjobbydev (httpx, Python 3.10+).

Both SDKs are scaffold-only today. Publish gate is the post-Tier 8 audit on the v1 surface.

Versioning + deprecation policy

The v1 surface is a forward contract. We commit to:

  • No removed endpoints on v1.
  • No removed response fields on v1.
  • No renamed error codes on v1.
  • No tightened request validation that rejects payloads that previously worked.

Additive changes — new endpoints, new optional request fields, new response fields, new error codes — ship freely on v1. Major version bumps go to /api/v2 with a parallel old/new period of at least 90 days.

Endpoints we plan to remove (rare) emit a Deprecation: true header on every call plus a Sunset: <date> header indicating when the endpoint disappears from /api/v2. Clients should log Deprecation headers and migrate before the sunset date.

Idempotency

Mutating endpoints accept an optional Idempotency-Key request header carrying a UUID v4. If the same key is replayed within 24 hours, the original response is returned — including the original status code and body — without re-applying the side effect.

curl https://jobby.dev/api/v1/billing/charge-offsession \
  -H "Authorization: Bearer jbb_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -d '{"target_plan": "pro", "interval": "monthly"}'

Use this for any retry loop on a non-idempotent endpoint (off-session charge, match decline, event create). Cost-bearing endpoints additionally bucket idempotency by tokenId / resource / interval automatically as a defense-in-depth — see the inline comment on charge-offsession for the bucketing key.

End-to-end worked example

Mint a token at /account/api-tokens with scopes queue:write, queue:read, matches:read. Then:

# 1. Find a live event
curl -s 'https://jobby.dev/api/v1/live-events?limit=5' | jq '.data[0]'
# Suppose this returns event slug "evt_abc123"

# 2. Join its queue (authenticated)
curl https://jobby.dev/api/v1/queue/join \
  -H "Authorization: Bearer $JOBBYDEV_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"event_id": "evt_abc123"}'
# → 200 OK, queue position returned

# 3. Poll status (every ~5 seconds, not faster)
curl -s https://jobby.dev/api/v1/queue/status \
  -H "Authorization: Bearer $JOBBYDEV_API_TOKEN" | jq
# → eventually shows status: "matched" with match_id

# 4. Open the live room (the user does this in their browser)
echo "Open https://jobby.dev/account/matches/<match_id> to accept"

Step 4 is intentionally browser-only — see the humans-only rule. An agent can fetch room URL via GET /api/v1/matches/{id}/interview-room-url after acceptance, but acceptance itself is session-authed.

Related reading