Skip to main content

Joby Developer API

REST API for reading and writing leads, clients, appointments, and tasks. How to request access, manage keys, and integrate with ChatGPT, Claude, Zapier, Make, n8n, or your own backend.

Written by Sophia Martinez

Developer Reference

Joby API

Read and write your Joby workspace data over a REST API. Use it to connect ChatGPT Actions, Claude tools, Zapier, Make, n8n, custom scripts, or your own backend.

Requesting access

Important

API access is gated. The Developer API module is off by default, and the API Keys tab is hidden until our team enables it on your organization.

To request access, contact Joby Support with your organization name and a one-line description of what you want to integrate:

Email [email protected], or message us from the in-app chat. We typically enable within one business day.

Managing API keys

Once your organization is enabled, admins will see a new tab at Settings → Developer → API Keys. From there you can list existing keys, create new ones, and revoke any that are no longer needed.

When creating a key, you set:

  • name — a human-readable label (max 80 characters)

  • scopes — one or more permissions (see below)

  • rate_limit_per_minute — defaults to 60, capped at 300

  • expires_at — optional ISO date or date-time

Revoked keys immediately return 401 on every request.

Storing your key

Warning

Your raw API key is displayed once at creation and never again. Joby stores only a SHA-256 hash — we cannot recover the original. Copy it to your password manager or secrets vault immediately. If you lose it, revoke and create a new one.

Treat the key like a password:

  • Never commit it to source control.

  • Don't paste it in chat, screenshots, logs, or support tickets — and if you ever do, rotate the key.

  • Use environment variables or a secrets manager.

  • Create separate keys per integration so any single revocation has the smallest blast radius.

Base URL

All requests use this production base URL:

https://api.joby.io/v1

Authentication

Send your key on every request using the standard Bearer header:

Authorization: Bearer JOBY_API_KEY

An alternative header is also accepted:

X-Joby-API-Key: JOBY_API_KEY

Keys have this shape:

joby_live_<public_id>_<secret>

A minimal request looks like this:

curl "https://api.joby.io/v1/leads?limit=10" \
  -H "Authorization: Bearer YOUR_JOBY_API_KEY" \
  -H "Accept: application/json"

Data isolation

Every key is scoped to one organization. The caller never supplies organization_id — the backend derives it from the key row, and every read query and foreign-key write is validated against it. A key from Organization A reading a record from Organization B always gets 404.

DELETE is not supported on any resource — by design. Hard deletes go through the Joby app, not the API.

Scopes

A key carries one or more scopes that define what it can do. Pick the smallest set the integration needs.

Resource

Read

Create

Update

Leads

leads:read

leads:create

leads:update

Clients

clients:read

clients:create

clients:update

Appointments

appointments:read

appointments:create

appointments:update

Tasks

tasks:read

tasks:create

tasks:update

If a key is missing the required scope, the response is:

{
  "error": "Missing scope",
  "required_scope": "leads:create"
}

Rate limits and pagination

Setting

Value

Default rate limit (new key)

60 requests / minute

Maximum rate limit at creation

300 requests / minute

Exceeded response

429 Too Many Requests

List page size (default)

50

List page size (max)

1000 (auto-clamped)

Offset (max)

10000

Per-key audit log retention

Latest 50 valid requests

Note

No-code platforms like Zapier, Make, and n8n tend to retry aggressively. Keep their rate limits on the lower end (60 is usually enough) to avoid runaway spend on retries.

Resources

The v1 API exposes four resources. Every resource follows the same method pattern:

GET    /<resource>          # list
GET    /<resource>/<id>     # single record
POST   /<resource>          # create
PATCH  /<resource>/<id>     # partial update# DELETE is not supported and returns 405.

Leads

Inbound or self-created leads. Includes name, contact info, address, status, sub-status, job type, ad source, and scheduled time. Foreign-key validation ensures any client_id you pass belongs to your organization.

GET/leads GET/leads/<id> POST/leads PATCH/leads/<id>

Scopes: leads:read, leads:create, leads:update

Exact filters: id, lead_number, status, phone, email, client_id

Create example:

curl "$BASE/leads" \
  -H "Authorization: Bearer YOUR_JOBY_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
    "first_name": "API",
    "last_name": "Lead",
    "phone": "+15555550123",
    "email": "[email protected]",
    "status": "New",
    "job_type": "Locksmith"
  }' 

Clients

Saved customer records. Reusable across multiple leads and jobs. Includes contact info, address, customer type, lead source, and notes.

GET/clients GET/clients/<id> POST/clients PATCH/clients/<id>

Scopes: clients:read, clients:create, clients:update

Exact filters: id, phone, email

Create example:

curl "$BASE/clients" \
  -H "Authorization: Bearer YOUR_JOBY_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
    "first_name": "API",
    "last_name": "Client",
    "phone": "+15555550124",
    "email": "[email protected]"
  }' 

Appointments

Scheduled visits with date, start time, end time, and status. Can optionally link to a parent lead and job type, and carry arbitrary custom_fields JSON.

GET/appointments GET/appointments/<id> POST/appointments PATCH/appointments/<id>

Scopes: appointments:read, appointments:create, appointments:update

Exact filters: id, status, phone, email, lead_id, job_type_id

Create example:

curl "$BASE/appointments" \
  -H "Authorization: Bearer YOUR_JOBY_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
    "first_name": "API",
    "last_name": "Appointment",
    "phone": "+15555550125",
    "appointment_date": "2026-07-20",
    "appointment_time": "10:00",
    "status": "scheduled"
  }' 

Tasks

Internal follow-ups and reminders with title, description, priority, due date, assignee, and optional links to a lead or client. Useful for triggering team work from upstream systems.

GET/tasks GET/tasks/<id> POST/tasks PATCH/tasks/<id>

Scopes: tasks:read, tasks:create, tasks:update

Exact filters: id, status, priority, assigned_to_email, lead_id, client_id

Create example:

curl "$BASE/tasks" \
  -H "Authorization: Bearer YOUR_JOBY_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
    "title": "API follow-up",
    "description": "Created from Developer API",
    "status": "open",
    "priority": "medium",
    "due_at": "2026-07-20T10:00:00Z"
  }' 

List query parameters

Parameter

Description

limit

Number of rows. Default 50, max 1000 (auto-clamped).

offset

Row offset for pagination. Default 0, max 10000.

created_since

ISO date or date-time. Filters on created_at.

updated_since

ISO date or date-time. Filters on updated_at. Best for incremental syncs.

q

Simple search. Looks up first_name on leads/clients/appointments, title on tasks.

Schema discovery

GET/schema returns the live API version, every resource, its scopes, create and update fields, available filters, and limits. Useful for generating SDK code or validating an integration's assumptions before going to production.

curl "$BASE/schema" \
  -H "Authorization: Bearer YOUR_JOBY_API_KEY"

Response format

List endpoints return:

{
  "data": [ /* records */ ],
  "limit": 50,
  "offset": 0,
  "request_id": "uuid"
}

Single record reads, create, and update return:

{
  "data": { /* record */ },
  "request_id": "uuid"
}

Errors share a consistent shape. Some include additional context:

{
  "error": "Missing scope",
  "required_scope": "leads:create"
}{
  "error": "Unsupported or protected fields",
  "fields": ["organization_id"]
}

Status codes

Code

Meaning

200

Read or update success.

201

Create success.

400

Invalid request, unsupported or protected fields, or invalid foreign key.

401

Missing, invalid, expired, or revoked API key.

403

Developer API module disabled, or key missing the required scope.

404

Unsupported resource, or record not in the key's organization.

405

Method not allowed (for example, DELETE).

429

Rate limit exceeded.

500

Server configuration or internal failure.

Protected fields

The following fields are rejected if included in create or update payloads. The backend owns them:

id organization_id created_at updated_at lead_number auth_secret access_token migration_metadata source_call_sid source_message_sid

Workflow value overrides

Note

Free-form workflow fields like status, sub_status, job_type, ad_source, customer_type, and priority accept caller-provided values even if they don't already exist in your organization's configuration. This is intentional — it lets you bring your own taxonomy from upstream systems without pre-configuring every value.

Cross-organization behavior

If a key from one organization touches data in another, here's what to expect:

Attempted action

Result

GET a record from another org

404

PATCH a record from another org

404

Filter by IDs that belong to another org

Empty data array

POST with a foreign client_id or lead_id

400

Best practices

  • Use the smallest scope set the integration actually needs.

  • Use a separate key per integration so revoking one doesn't break the rest.

  • Set an expiration date for temporary vendors, hackathon scripts, or short-lived experiments.

  • Rotate any key that has been pasted in chat, logs, screenshots, or support tickets.

  • Revoke the key as soon as the integration is retired.

  • Use lower rate limits for no-code platforms — they retry aggressively on failure.

  • Log on your side too. The per-key audit log only retains the latest 50 valid requests.

Support

Email [email protected] or message us in the in-app chat. We can help with enabling the API module, scoping decisions, sample requests, and troubleshooting 401 / 403 responses.

Did this answer your question?