Skip to content

API reference

The Lead Source public API lets an external integration read your leads and where they came from, on your behalf, after you connect it to your Lead Source account. It is the same API the Lead Source apps for Zapier and Make use.

  • Base URL: https://app.leadsource.co
  • Format: JSON. Every response is application/json.
  • Authentication: OAuth 2.0 authorization code with PKCE (see below). Every data request carries an Authorization: Bearer <access_token> header.
  • Permissions: read-only. The only scope is leads:read.
  • Tenant scope: a connection is bound to one Lead Source account; if the connecting user is limited to specific websites, the API returns data only for those websites.

Lead Source is a confidential OAuth client with mandatory PKCE (S256). Registration of a client (client_id / client_secret / redirect URI) is arranged with Lead Source support.

Send the user to the authorize endpoint. They sign in and approve access, then get redirected back to your redirect_uri with a one-time code (and your state).

GET https://app.leadsource.co/api/oauth/authorize
?client_id=<your client id>
&redirect_uri=<your allow-listed redirect uri>
&response_type=code
&scope=leads:read
&state=<opaque csrf value>
&code_challenge=<base64url(sha256(code_verifier))>
&code_challenge_method=S256

code_challenge + code_challenge_method=S256 are required. An unknown client or a redirect URI that is not allow-listed is rejected with 400.

POST https://app.leadsource.co/api/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=<code from step 1>
&client_id=<your client id>
&client_secret=<your client secret>
&redirect_uri=<same redirect uri as step 1>
&code_verifier=<the original code verifier>

Response:

{
"access_token": "ls_oat_…",
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": "ls_ort_…",
"scope": "leads:read"
}

Access tokens expire (expires_in seconds). Exchange the refresh token for a new pair. Refresh tokens rotate: each refresh returns a new refresh_token — store it and discard the old one. Re-presenting a consumed refresh token revokes the whole token family.

POST https://app.leadsource.co/api/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=<current refresh token>
&client_id=<your client id>
&client_secret=<your client secret>

Send the access token on every data request:

Authorization: Bearer <access_token>

All endpoints require the Authorization: Bearer header and the leads:read scope.

Returns the connected account (used to label the connection and to check the token still works).

{
"account_id": "",
"account_name": "Acme Co (owner@acme.com)",
"scopes": ["leads:read"],
"connection_id": ""
}

Lists the active websites on the connected account, ordered by domain.

{ "data": [ { "id": "", "domain": "example.com" } ] }

Forms — GET /api/v1/sites/{siteId}/forms

Section titled “Forms — GET /api/v1/sites/{siteId}/forms”

Lists the genuine detected forms for one website (ordered by lead volume). {siteId} is a site id from the sites endpoint. A malformed or unknown site id returns an empty list.

{
"data": [
{ "id": "", "form_identifier": "jotform:2412…", "label": "/contact/ (42 leads)", "page_path": "/contact/", "lead_count": 42 }
]
}

The id here is the value to pass as detected_form_id when filtering leads.

New leads (polling) — GET /api/v1/triggers/leads

Section titled “New leads (polling) — GET /api/v1/triggers/leads”

Returns leads newest-first for polling integrations. It excludes spam, test, and deleted leads. Poll on your own schedule and de-duplicate on each lead’s id.

Query paramRequiredDescription
site_idnoOnly leads from this website (a site id). Omit for all sites.
detected_form_idnoOnly leads from this form (a form id). Omit for all forms.
limitnoPage size, 1100. Default 50.
cursornoOpaque cursor from a prior response’s next_cursor, for the next page.
{ "data": [ /* Lead objects, newest first */ ], "next_cursor": "" }

next_cursor is null on the last page. site_id / detected_form_id must be valid UUIDs, else 400 invalid_filter; a malformed cursor is 400 invalid_cursor.

Find a lead — GET /api/v1/actions/find-lead

Section titled “Find a lead — GET /api/v1/actions/find-lead”

Exact email lookup (case- and whitespace-insensitive). Returns 0 or 1 result.

GET /api/v1/actions/find-lead?email=person@example.com
{ "data": [ /* 0 or 1 Lead object */ ] }
FieldTypeNotes
idstring (uuid)Use as {siteId} and as the site_id filter.
domainstringThe website domain.
FieldTypeNotes
idstring (uuid)Use as the detected_form_id filter.
form_identifierstringRaw detected identifier (e.g. jotform:…).
labelstringHuman-readable label for a picker.
page_pathstringPage the form was seen on.
lead_countintegerReal lead count for the form.

Contact details are returned in full (never masked).

FieldTypeNotes
idstring (uuid)Stable unique id — de-duplicate polling on this.
created_atstringUTC ISO-8601 with milliseconds.
namestring / nullLead’s name.
emailstring / nullLead’s email (lowercased).
phonestring / nullLead’s phone, as captured.
sourcestringWhere they came from (display name).
source_typestring / nullSource category.
source_detailstring / nullExtra source detail.
source_confidencestring / nullAttribution confidence.
first_touch_sourcestring / nullFirst-touch source, as a JSON string.
form_namestring / nullThe form the lead came through.
site_domainstring / nullThe website the lead came from.
raw_sourcestring / nullRaw source data, as a JSON string (campaign tracking parameters, referrer, page).
fields_rawstring / nullThe submitted form fields, as a JSON string.

The New leads endpoint is keyset-paginated and newest-first. For a full backfill, follow next_cursor until it is null. For ongoing polling, request the newest page each interval and de-duplicate on id — the standard polling-trigger pattern.

Per connection (or per client / IP for the OAuth endpoints). Over the limit returns 429 rate_limited with a Retry-After header (seconds).

Endpoint(s)Limit
POST /api/oauth/token30 / minute / client, and 10 / minute / IP
GET /api/oauth/authorize20 / minute / IP
GET /api/v1/sites, …/forms60 / minute / connection
GET /api/v1/triggers/leads120 / minute and 600 / hour / connection

Errors use { "error": "<code>", "message": "<detail>" } with a matching HTTP status.

StatuserrorMeaning
400invalid_grantBad/expired/used code, redirect-uri mismatch, or PKCE mismatch.
400invalid_filter / invalid_cursorA filter id is not a UUID, or the cursor is malformed.
401invalid_token / token_expiredMissing/invalid access token, or it has expired (refresh it).
401invalid_clientUnknown client id or wrong client secret at the token step.
403insufficient_scopeThe token lacks the required scope.
429rate_limitedSlow down; retry after Retry-After seconds.