Open Leads API
Connect your own systems to Open Leads: get an API key, then add and update leads, add notes and tasks, start workflows, and receive instant webhooks when leads come in or change.
On this page
Open Leads API
The Open Leads API lets your own systems, and tools like Zapier, add and update leads, add notes and tasks, start workflows, and be told the moment something happens to a lead. This page covers every address the API answers, what to send and what comes back.
| Base address | https://app.openleads.com/api/v1/ |
|---|---|
| Format | JSON in and out (UTF-8) |
| Authentication | An API key in the X-API-Key header |
| Rate limit | 120 requests a minute per key |
| Version | 1 |
Contents
- Getting an API key
- Authentication
- Requests, responses and time zones
- Rate limits
- Errors
- The lead object
- Account endpoints: about, me, fields, statuses, types, users, forms, workflows
- Leads: list and search, get, create, update
- Notes, tasks and workflows on a lead
- Events
- Webhooks (instant notifications)
- Using Open Leads with Zapier
- Security
Getting an API key
API keys are made by an administrator of the account:
- Go to Settings > Apps & API > API keys.
- Click New API key, give it a name you will recognize (for example the system that will use it), and create it.
- Copy the key right away. It starts with
olk_and is shown only once. Open Leads keeps only a fingerprint of it, so a lost key cannot be shown again; make a new one instead.
A key works on the account it was made in, with the access of the administrator who made it. It stops working if it is revoked, if that person is no longer an administrator of the account or loses access to it, or if the account is no longer active. Revoke a key at any time on the same page; anything using it stops at once.
Everything a key adds or changes is recorded on the lead's history (for example "added lead through the API (key: Website)") and on the Activity tab of Settings > Apps & API.
Authentication
Send the key with every request, in either header:
X-API-Key: olk_your_key_here Authorization: Bearer olk_your_key_here
The API only answers over https. A quick test that also tells you which account and person the key belongs to:
curl https://app.openleads.com/api/v1/me/ \ -H "X-API-Key: olk_your_key_here"
Requests, responses and time zones
- Every address ends in a slash, for example
/api/v1/leads/. An address without the slash is answered with a redirect, and most clients turn a POST into a GET when they follow one, so the request would not do what you meant. - Send request bodies as JSON with
Content-Type: application/json. Ordinary form posts (application/x-www-form-urlencoded) are accepted too. A body that says it is JSON but is not valid JSON is refused (bad_json). - Answers are always JSON. A single item is an object; a list is an array.
- Dates and times come back in ISO 8601 with their offset, for example
2026-10-18T17:32:41-04:00, in the time zone of the person who made the key. Times you send (a task's due time, a date field) are read in that same time zone unless they carry their own offset. - Updates can be sent as POST, PUT or PATCH; they all work the same way.
Rate limits
Each key can make 120 requests a minute. Past that, requests are refused with status 429, error code rate_limited and a Retry-After: 60 header. Wait that many seconds and try again. To bring in many leads at once, send them one after another at a steady pace, or use the spreadsheet import under Leads > Import.
Errors
A request that does not work is answered with a 4xx or 5xx status and a body like this. fields is there when particular values were the problem, keyed by the field you sent.
{
"error": {
"code": "invalid",
"message": "The lead was not added. email: Not a valid email address.",
"fields": { "email": "Not a valid email address." }
}
}
| Status | Code | Meaning |
|---|---|---|
| 400 | bad_json |
The body is not valid JSON. |
| 401 | no_key |
No API key was sent. |
| 401 | bad_key |
The key is not valid, was revoked, its account is not active, or the person who made it no longer has administrator access. The message says which. |
| 403 | https_required |
The request was not made over https. |
| 403 | lead_limit |
The account has reached the number of leads its plan allows. |
| 403 | no_access |
The person who made the key may not do this. |
| 404 | not_found, lead_not_found |
No such address, or no such lead (or subscription) in this account. |
| 405 | method_not_allowed |
The address does not take that method (for example DELETE on a lead). |
| 422 | invalid, empty_lead, bad_on_duplicate, bad_form, bad_workflow, bad_phone, bad_event, bad_url |
Something you sent cannot be used. The message and fields say what. |
| 429 | rate_limited |
More than 120 requests a minute with this key. Wait 60 seconds. |
| 500 | server_error |
Something went wrong on our side. Try again in a minute; if it keeps happening, contact support. |
The lead object
Every endpoint that returns a lead returns it in this shape. Webhooks and Zapier receive the same shape.
{
"id": 24218416,
"url": "https://app.openleads.com/leads/24218416/",
"created_at": "2026-10-18T17:32:41-04:00",
"updated_at": "2026-10-18T17:40:02-04:00",
"source": "API",
"form_id": null,
"first_name": "Jane",
"middle_name": "",
"last_name": "Smith",
"title": "",
"suffix": "",
"company": "Smith Roofing",
"job_title": "Owner",
"email": "jane@example.com",
"phone": "555-555-0100",
"phone_type": "Mobile",
"phone2": "", "phone2_type": "", "phone3": "", "phone3_type": "",
"address": "12 Main St", "city": "Boston", "state": "MA", "zip": "02108", "country": "US",
"alt_address": "", "alt_city": "", "alt_state": "", "alt_zip": "", "alt_country": "",
"value": 2500,
"full_name": "Jane Smith",
"status_id": 101,
"status": "Qualified",
"type_ids": [12],
"types": "Website",
"assigned_user_ids": [14389],
"assigned_to": "Joe Stenhouse",
"opted_out": false,
"archived": false,
"custom_fields": { "Roof type": "Shingle" },
"f_3041": "Shingle",
"extra": { "Campaign name": "Fall promo" }
}
| Field | What it is |
|---|---|
id, url |
The lead's number, and its page in Open Leads. |
created_at, updated_at |
When the lead was added and last changed. |
source, form_id |
How the lead came in (for example Form, Email, Import, Internal, API, Zapier, Facebook) and, for a form lead, which form. |
| Standard fields | first_name, middle_name, last_name, title, suffix, company, job_title, email, phone, phone_type, phone2, phone2_type, phone3, phone3_type, address, city, state, zip, country, alt_address, alt_city, alt_state, alt_zip, alt_country, value. Text fields are strings (empty when not filled in); value is a number or null. Phone types are Main, Work, Home, Fax, Mobile or Other. |
full_name |
First and last name together. |
status_id, status |
The lead's status (its stage in your pipeline), or null. |
type_ids, types |
The lead's types, as a list of ids and as names separated by commas. |
assigned_user_ids, assigned_to |
The people assigned to the lead, as ids and as names. |
opted_out |
True when the lead's email address has unsubscribed from your emails. |
archived |
True when the lead is archived. |
custom_fields, f_ |
Your account's own fields, both by name and by key (f_ and the field's id; see GET /fields/). A checkbox is Yes or No; a checkbox group is its ticked choices separated by commas. |
extra |
Extra information kept on the lead as label and value pairs (anything that came in with the lead but has no field of its own). |
Account endpoints
GET /api/v1/
About the API, and which account the key works on.
{ "name": "OpenLeads API", "version": 1, "account_id": 6859 }
GET /api/v1/me/
The account, the person who made the key, and the key itself. Good for checking a key works.
{
"account": { "id": 6859, "name": "Smith Roofing", "code": "smithroofing" },
"user": { "id": 16559, "name": "Jane Smith", "email": "jane@example.com" },
"key": { "id": 12, "name": "Website" },
"label": "Smith Roofing (Website)"
}
GET /api/v1/fields/
Every field a lead can have: the standard fields first, then your account's own fields. Add ?form_id= with a form's id to get only the fields on that form, with the ones the form requires marked.
[
{ "key": "first_name", "label": "First name", "type": "string", "group": "standard" },
{ "key": "phone_type", "label": "Phone type", "type": "string", "group": "standard",
"choices": ["Main", "Work", "Home", "Fax", "Mobile", "Other"] },
{ "key": "f_3041", "label": "Roof type", "type": "string", "group": "custom",
"field_type": "select", "choices": ["Shingle", "Metal", "Flat"] },
{ "key": "f_3042", "label": "Install date", "type": "datetime", "group": "custom",
"field_type": "date", "helpText": "A date, for example 2026-10-17 or 10/17/2026." }
]
type is string, text (several lines), number, datetime or boolean. choices lists the allowed values of a dropdown or radio field; list_choices lists the choices of a checkbox group (send one or more). required appears, set to true, on a field the form requires.
GET /api/v1/statuses/ · /types/ · /users/ · /forms/ · /workflows/
The account's statuses, lead types, active users, forms and workflows, so you can find the ids to send.
GET /statuses/ → [ { "id": 101, "name": "Qualified", "won": false, "lost": false } ]
GET /types/ → [ { "id": 12, "name": "Website", "color": "#286984" } ]
GET /users/ → [ { "id": 14389, "name": "Joe Stenhouse", "email": "joe@example.com" } ]
GET /forms/ → [ { "id": 15408, "name": "Website contact form" } ]
GET /workflows/ → [ { "id": 34340, "name": "New lead follow-up" } ]
Leads
GET /api/v1/leads/ — list and search
Returns an array of leads, newest first. Deleted leads are never returned; archived leads are left out unless you ask for them.
| Parameter | What it does |
|---|---|
limit |
How many to return: 25 unless you say, at most 100. |
since_id |
Only leads with a higher id, oldest first. To pick up every new lead, keep the highest id you have seen and ask again with it. |
email |
Leads with exactly this email address. |
phone |
Leads with this phone number in any of their three phone fields. Formatting is ignored; at least 7 digits; the last 10 digits are compared. |
q |
Leads whose name, email or company contains this text. |
status_id |
Leads with this status. |
include_archived |
1 to include archived leads. |
curl "https://app.openleads.com/api/v1/leads/?email=jane@example.com" \ -H "X-API-Key: olk_your_key_here"
GET /api/v1/leads/{id}/ — one lead
Returns the lead. A lead that does not exist in this account, or was deleted, is a 404 lead_not_found.
POST /api/v1/leads/ — add a lead
Send any of the lead's fields. A lead needs at least one of first_name, last_name, email, phone or company.
curl -X POST https://app.openleads.com/api/v1/leads/ \
-H "X-API-Key: olk_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Jane",
"last_name": "Smith",
"email": "jane@example.com",
"phone": "555-555-0100",
"phone_type": "Mobile",
"status": "New",
"types": ["Website"],
"assign_to": "joe@example.com",
"custom_fields": { "Roof type": "Shingle" },
"extra": { "Campaign name": "Fall promo" },
"on_duplicate": "update",
"utm_source": "google",
"utm_medium": "cpc",
"utm_campaign": "fall-promo"
}'
| You can send | How it works |
|---|---|
| Standard fields | Any of the standard fields listed under The lead object. Email addresses are stored in lower case. value takes a number (a currency sign or commas are ignored). |
f_ or custom_fields |
Your own fields, by key ("f_3041": "Shingle") or by name ("custom_fields": {"Roof type": "Shingle"}). A checkbox takes yes/no, true/false or 1/0. A checkbox group takes a list or values separated by commas. A number field takes a number. A date field takes any common date (2026-10-17, 10/17/2026, October 17 2026). |
status or status_id |
The status, by name or id. |
types or type_ids |
One or more types, by name or id, as a list or separated by commas. |
assign_to or assign_user_ids |
One or more active users of the account, by id or email address, as a list or separated by commas. |
extra |
An object of label and value pairs kept on the lead as extra information (labels up to 100 characters, values up to 5,000). |
form_id |
Send the lead in through one of your forms, exactly as if someone had filled it in: the form's duplicate grouping, assignment, workflows and autoresponder email all happen, and the lead's source is the form, so form reporting counts it. Only the fields on that form are used (see GET /fields/?form_id=); status, types, assignment and extra you send are applied on top. The form must be one that adds new leads, not an update form. When an email address had unsubscribed, it stays unsubscribed. |
workflow_id |
Start this workflow on the new lead. |
on_duplicate |
What to do when a lead with the same email address is already in the account (not archived or deleted): create adds a new lead anyway (the default), update updates the newest existing lead with what you sent, skip leaves it alone and returns it. |
utm_source, utm_medium, utm_campaign, utm_term, utm_content, referrer, landing_page |
Where the lead came from, for Open Leads Reporting. |
A new lead is answered with status 201 and the lead, plus "duplicate": false and "action": "created". With on_duplicate set to update or skip and a match found, the answer is status 200, the existing lead, "duplicate": true and "action": "updated" or "skipped". Leads added directly have the source API (or Zapier when Zapier adds them).
POST /api/v1/leads/{id}/ — update a lead
PUT and PATCH work too. Send only what should change; fields you leave out stay as they are.
- Standard and custom fields take the same values as when adding a lead. Sending an empty string empties a field.
status/status_idchanges the status; an empty value clears it.typesandassign_toare added to what the lead already has; nothing is taken away.extrapairs are added to the lead's extra information (a label that is already there gets the new value).workflow_idstarts that workflow on the lead after the change is saved.
curl -X PATCH https://app.openleads.com/api/v1/leads/24218416/ \
-H "X-API-Key: olk_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "status": "Qualified", "value": 2500, "company": "Smith Roofing" }'
The answer is the updated lead with "action": "updated".
Notes, tasks and workflows on a lead
POST /api/v1/leads/{id}/notes/ — add a note
Adds a note (comment) to the lead's history. Send note as plain text; HTML tags are removed.
{ "note": "Called back, wants a quote by Friday." }
→ 201 { "lead_id": 24218416, "note_id": 95393501, "note": "Called back, wants a quote by Friday.",
"created_at": "2026-10-18T17:49:00-04:00" }
POST /api/v1/leads/{id}/tasks/ — add a task
name |
Required. Up to 100 characters. |
description |
Optional. |
type |
Call, Email, Meeting or Other. |
due |
When it is due, for example 2026-10-20T10:00:00-04:00 or 2026-10-20 10:00. Leave it out for a task with no due time. |
assign_user_id |
Who the task is for, by user id or email address. Left out: the person who made the key. |
{ "name": "Send the quote", "type": "Email", "due": "2026-10-23T09:00:00-04:00" }
→ 201 { "lead_id": 24218416, "task_id": 551203, "name": "Send the quote",
"due": "2026-10-23T09:00:00-04:00", "assigned_user_id": 14389 }
POST /api/v1/leads/{id}/workflows/ — start a workflow
Starts one of your workflows on the lead, exactly as starting it in Open Leads does (its status, types, users, tasks and messages all apply).
{ "workflow_id": 34340 }
→ 200 the lead, plus "workflow_id": 34340 and "workflow": "New lead follow-up"
Events
Open Leads reports three kinds of event:
lead.created |
A new lead came in, from any source: a form, Email In, the API, added by hand. Leads brought in with a spreadsheet import are left out unless you ask for them. |
lead.status_changed |
A lead was given a new status, by a person, a workflow or a bulk change. |
lead.assigned |
Someone was assigned to a lead. |
GET /api/v1/events/
The most recent events of one kind, newest first, in the same shape webhooks send. Useful for testing and for seeing what a webhook will receive.
event |
Required: lead.created, lead.status_changed or lead.assigned. |
form_id |
lead.created only: leads from this form. |
include_imports |
lead.created only: 1 to include imported leads. |
status_id |
lead.status_changed only: changes to this status. |
user_id |
lead.assigned only: assignments to this person. |
limit |
3 unless you say, at most 25. |
What an event looks like
The lead (as in The lead object) with these added:
| Field | Events | What it is |
|---|---|---|
event, event_id, occurred_at |
all | Which event, its own unique id, and when it happened. |
lead_id |
all | The lead's id. Use this one: for status changes and assignments, id is the event's id (the same lead can change status many times). |
changed_by_user_id, changed_by |
all | Who did it, when it was a person. |
previous_status_id, previous_status, new_status_id, new_status |
lead.status_changed | The status before and after. |
assigned_user_id, assigned_user, assigned_user_email |
lead.assigned | The person who was assigned. |
{
"id": "status-88213",
"lead_id": 24218416,
"event": "lead.status_changed",
"event_id": "status-88213",
"occurred_at": "2026-10-18T18:01:00-04:00",
"changed_by_user_id": 14389,
"changed_by": "Joe Stenhouse",
"previous_status_id": 100,
"previous_status": "Contacted",
"new_status_id": 101,
"new_status": "Qualified",
"first_name": "Jane",
"...": "the rest of the lead"
}
Webhooks (instant notifications)
Subscribe an https address to an event and Open Leads POSTs each event to it, usually within a minute of it happening. This is how Zapier's Open Leads triggers work.
POST /api/v1/hooks/ — subscribe
{
"event": "lead.status_changed",
"target_url": "https://example.com/openleads/status",
"filters": { "status_id": 101 }
}
→ 201 { "id": 17, "event": "lead.status_changed",
"target_url": "https://example.com/openleads/status", "filters": { "status_id": 101 } }
target_urlmust be an https address on the public internet.filtersis optional and takes the same filters as GET /events/:form_idandinclude_importsfor lead.created,status_idfor lead.status_changed,user_idfor lead.assigned.- Events that happened before you subscribed are not sent.
GET /api/v1/hooks/ — list subscriptions
The account's subscriptions, with when each was last sent to and any problem.
DELETE /api/v1/hooks/{id}/ — unsubscribe
Answers { "id": 17, "deleted": true }. (A POST to the same address does the same, for clients that cannot send DELETE.)
What your address receives
- A POST with the event as a JSON body (see What an event looks like), and the headers
Content-Type: application/json,X-OpenLeads-Event: lead.status_changedandUser-Agent: OpenLeads-Webhooks/1.0. - Answer with any 2xx status within 6 seconds. Redirects are not followed.
- Answer
410 Goneto end the subscription; Open Leads stops sending to it. - After 50 failed deliveries in a row the subscription is turned off. A subscription also stops when its key is revoked or the person who made the key loses access.
- Use
event_idto recognize an event you have already handled. - Subscriptions, when each last sent and any problems are shown under Settings > Apps & API > Zapier > Zaps running, where an administrator can also stop one.
Using Open Leads with Zapier
The Open Leads app on Zapier is built on this API, so you can connect Open Leads to thousands of apps without writing code. Connect it with an API key: in Zapier choose Open Leads, and paste a key from Settings > Apps & API > API keys when Zapier asks you to sign in.
| In Zapier | Uses |
|---|---|
| Trigger: New Lead (optionally only from one form) | Webhook on lead.created |
| Trigger: Lead Status Changed (optionally to one status) | Webhook on lead.status_changed |
| Trigger: Lead Assigned (optionally to one person) | Webhook on lead.assigned |
| Action: Create Lead (directly or through one of your forms) | POST /leads/ |
| Action: Update Lead | POST /leads/{id}/ |
| Action: Add Note to Lead | POST /leads/{id}/notes/ |
| Action: Create Task | POST /leads/{id}/tasks/ |
| Action: Start Workflow on Lead | POST /leads/{id}/workflows/ |
| Search: Find Lead (by email, phone or name), and Find or Create Lead | GET /leads/, then POST /leads/ |
Leads Zapier adds directly get the source Zapier. Leads Zapier sends through one of your forms keep the form as their source, so form reporting still counts them, and show via Zapier next to it.
Security
- Treat an API key like a password: it can read and change your leads. Keep it on your server, never in a web page or app that other people can open.
- Make a separate key for each system that uses the API, so you can revoke one without stopping the others.
- All traffic is over https. Keys are stored only as a fingerprint, and webhooks are only sent to public https addresses.
- Every change made with a key is recorded on the lead's history and on the Activity tab of Settings > Apps & API, with the key's name.
Questions about the API? Contact Open Leads support from the Help Center.