List leads
GET /v1/leads — filter, page and keep a mirror in sync.
GET List leads
/v1/leadsLeads in your organisation, newest first, with cursor paging. Unknown parameters are a 422 naming each one: a silently ignored filter is how an integration confidently mirrors the wrong rows.
limitinteger · 1 to 100Out of range is a 422, never silently clamped.
50cursorstringFrom a previous nextCursor. Opaque; never construct one.
orderstringSee paging for what each guarantees.
createdAt:desc, createdAt:asc, updatedAt:asccreatedAt:desccreatedSincestring · date or date-timeRead in your timezone unless it carries an offset.
createdBeforestring · date or date-timeExclusive, so adjacent windows tile without overlap.
updatedSincestring · date or date-timeEverything changed since then. Implies order=updatedAt:asc.
referencestring · max 255Exact match on the reference you sent on create.
externalRefstring · max 255Exact match.
sourcestring · max 100Exact match on the source label.
codestring · max 64The CRM's lead number, such as L013898.
statusstring · listOne or several, comma-separated or repeated. All by default.
open, sold, lost, on_holdteststringWhether test leads are in the list.
exclude, include, onlyexcludeassignedTostringA user's id or email, or none for leads nobody holds. See Users.
stagestring · listA stage id or name, one or several. A name matches every workflow that has it, so
stage=Quote Sent covers that stage in each of your lead workflows; add workflow to narrow
it. Case-insensitive; an unknown name is a 422. List workflows
for the names and ids.
workflowstring · listA workflow id or name, one or several. On its own, every lead in those workflows; with
stage, only that stage within them.
daysInStagestringWhole days the lead has been in its current stage. A bare number is at least, so 14 is
two weeks or more. Prefix for the rest: eq:14, gt:14, gte:14, lt:14, lte:14.
Counted from stageChangedAt, or createdAt when the stage has never changed, as of each
request.
curl "https://api.evacrm.co.uk/v1/leads?status=open&limit=50" \
-H "Authorization: Bearer sk_..."{
"object": "list",
"data": [
{
"id": "1e4e3f90-4384-42ce-90c3-540db5343964",
"code": "L013898",
"reference": "web-form-8812",
"externalRef": null,
"source": "public-api",
"status": "open",
"stage": { "id": "90ee4027-…", "name": "Unassigned & Unappointed" },
"workflow": { "id": "…", "name": "Leads" },
"stageChangedAt": "2026-09-02T11:14:03.512+01:00",
"daysInStage": 2,
"assigned": true,
"assignee": { "id": "cd9356b7-…", "name": "Tuan Dinh", "email": "tuan@example.com" },
"assignedTeam": null,
"converted": false,
"appointmentCompleted": false,
"test": false,
"message": "Please call after 5pm",
"messageTruncated": false,
"leadType": "Windows",
"mainInterest": null,
"productTypes": [],
"property": {
"type": null, "category": null, "planningRequired": null, "planningType": null,
"other": null, "yearBuilt": null
},
"marketing": { "source": "Website", "campaign": null },
"customer": {
"id": "7b1d…", "code": "CUS007378",
"firstName": "Jane", "lastName": "Smith", "fullName": "Jane Smith", "companyName": null,
"consents": {
"doNotContact": false, "noMarketing": false,
"marketingEmail": true, "marketingSms": false, "marketingPost": false
}
},
"contact": {
"id": "…", "email": "jane@example.com", "phoneNumber": null, "mobile": "07700900123",
"address": "1 High Street", "address2": null, "address3": null,
"town": "Reading", "county": null, "postcode": "RG1 1AA", "country": "United Kingdom"
},
"createdAt": "2026-09-02T11:14:03.512+01:00",
"updatedAt": "2026-09-02T11:14:03.512+01:00",
"soldAt": null, "lostAt": null, "onHoldAt": null,
"expectedCloseDate": null,
"importedAt": null
}
],
"hasMore": true,
"nextCursor": "eyJ2IjoxLCJvIjoi…"
}The stage's name is what the CRM shows; it is matched in every lead workflow that has it.
curl "https://api.evacrm.co.uk/v1/leads?stage=Quote%20Sent&daysInStage=14&status=open" \
-H "Authorization: Bearer sk_..."statusis one ofopen,sold,lost,on_hold, derived from the lead's stage category and flags so the three can never contradict each other.daysInStageis the whole days sincestageChangedAt, the same measure thedaysInStagefilter uses, so a row always explains why it matched.customer.consentsis there so that you never contact someone who opted out using data we handed you. Honour it.messageis capped at 5,000 characters;messageTruncatedsays if more exists in the CRM.- Every timestamp carries your organisation's timezone offset; see dates and timezones.
- There is no
total. Page untilhasMoreis false.
Paging
Follow nextCursor until hasMore is false. Cursors are keyset, not offset, so a page never
skips or repeats a row even while leads are being created. Changing filters or order
mid-crawl invalidates the cursor (422); limit can change freely.
order | Guarantee |
|---|---|
createdAt:desc | Every lead that existed when you started, exactly once. Leads created during the crawl are ahead of your start point; start a fresh crawl to see them. |
createdAt:asc | The same exactly-once property from oldest forward. Misses edits to leads already passed. Use for a one-off backfill. |
updatedAt:asc | Sync mode. Any edit moves a lead ahead of the cursor, so changes are not missed, but a lead you have seen can be re-emitted. Upsert by id, never append. |
updatedAt:desc is deliberately not offered; paging backwards down a column that changes under
you skips rows permanently.
Keeping a mirror
Poll GET /v1/leads?updatedSince=<watermark> (which implies order=updatedAt:asc), upsert by
id, and advance the watermark to the last updatedAt you processed. Timestamps are written by
application processes rather than in commit order, so a long-running bulk import in the CRM can
commit rows stamped slightly earlier than your watermark. Resume from watermark − 60s and
tolerate re-emits, and run a createdAt:asc backfill occasionally.
What is not returned
Deliberately withheld: numeric ids, all pricing (list, net, quoted, discounts, deposits),
advancedData (it carries the price calculation), installation instructions (routinely contain
gate codes and similar), lost reasons, and geolocation. Staff identities are limited to the
assignee. If your use case needs any of these, talk to us rather than working around it.
GET Get a lead
/v1/leads/{id}One lead by its id, in exactly the shape the list returns. To look one up by your own
reference or its CRM code, use the list: ?reference=web-form-8812 or ?code=L013899.
idstring · uuidRequiredThe lead's id from create or list.
curl https://api.evacrm.co.uk/v1/leads/3738d84c-61ff-4d96-9d4d-cd87adc27a5f \
-H "Authorization: Bearer sk_..."{
"id": "3738d84c-61ff-4d96-9d4d-cd87adc27a5f",
"code": "L013899",
"reference": "web-form-8812",
"externalRef": null,
"source": "public-api",
"status": "open",
"stage": {
"id": "90ee4027-…",
"name": "Unassigned & Unappointed"
},
"workflow": {
"id": "…",
"name": "Leads"
},
"stageChangedAt": "2026-09-02T11:14:03.512+01:00",
"daysInStage": 2,
"assigned": true,
"assignee": {
"id": "cd9356b7-…",
"name": "Tuan Dinh",
"email": "tuan@example.com"
},
"assignedTeam": null,
"converted": false,
"appointmentCompleted": false,
"test": false,
"message": "Please call after 5pm",
"messageTruncated": false,
"leadType": "Windows",
"mainInterest": null,
"productTypes": [],
"property": {
"type": null,
"category": null,
"planningRequired": null,
"planningType": null,
"other": null,
"yearBuilt": null
},
"marketing": {
"source": "Website",
"campaign": null
},
"customer": {
"id": "7b1d…",
"code": "CUS007378",
"firstName": "Jane",
"lastName": "Smith",
"fullName": "Jane Smith",
"companyName": null,
"consents": {
"doNotContact": false,
"noMarketing": false,
"marketingEmail": true,
"marketingSms": false,
"marketingPost": false
}
},
"contact": {
"id": "…",
"email": "jane@example.com",
"phoneNumber": null,
"mobile": "07700900123",
"address": "1 High Street",
"address2": null,
"address3": null,
"town": "Reading",
"county": null,
"postcode": "RG1 1AA",
"country": "United Kingdom"
},
"createdAt": "2026-09-02T11:14:03.512+01:00",
"updatedAt": "2026-09-02T11:14:03.512+01:00",
"soldAt": null,
"lostAt": null,
"onHoldAt": null,
"expectedCloseDate": null,
"importedAt": null
}