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 300expires_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 |
|
|
|
Clients |
|
|
|
Appointments |
|
|
|
Tasks |
|
|
|
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 |
|
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 |
| Number of rows. Default 50, max 1000 (auto-clamped). |
| Row offset for pagination. Default 0, max 10000. |
| ISO date or date-time. Filters on |
| ISO date or date-time. Filters on |
| Simple search. Looks up |
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, |
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 |
|
|
|
|
Filter by IDs that belong to another org | Empty |
|
|
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.
