eva crm Public API
Leads

List leads

GET /v1/leads — filter, page and keep a mirror in sync.

GET List leads

GET/v1/leads

Leads 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.

Query parameters
limitinteger · 1 to 100

Out of range is a 422, never silently clamped.

Default: 50
cursorstring

From a previous nextCursor. Opaque; never construct one.

orderstring

See paging for what each guarantees.

Valid values: createdAt:desc, createdAt:asc, updatedAt:asc
Default: createdAt:desc
createdSincestring · date or date-time

Read in your timezone unless it carries an offset.

createdBeforestring · date or date-time

Exclusive, so adjacent windows tile without overlap.

updatedSincestring · date or date-time

Everything changed since then. Implies order=updatedAt:asc.

referencestring · max 255

Exact match on the reference you sent on create.

externalRefstring · max 255

Exact match.

sourcestring · max 100

Exact match on the source label.

codestring · max 64

The CRM's lead number, such as L013898.

statusstring · list

One or several, comma-separated or repeated. All by default.

Valid values: open, sold, lost, on_hold
teststring

Whether test leads are in the list.

Valid values: exclude, include, only
Default: exclude
assignedTostring

A user's id or email, or none for leads nobody holds. See Users.

stagestring · list

A 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 · list

A workflow id or name, one or several. On its own, every lead in those workflows; with stage, only that stage within them.

daysInStagestring

Whole 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.

Responses
200
A page of leads, `hasMore`, and `nextCursor` to fetch the next one.
422
An unknown parameter, a value out of range, or a cursor that no longer matches the filters. `fields` names it.
Example request
curl "https://api.evacrm.co.uk/v1/leads?status=open&limit=50" \
  -H "Authorization: Bearer sk_..."
Example response · 200
{
  "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…"
}
Example · quotes sent with no reply for two weeks

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_..."
  • status is one of open, sold, lost, on_hold, derived from the lead's stage category and flags so the three can never contradict each other.
  • daysInStage is the whole days since stageChangedAt, the same measure the daysInStage filter uses, so a row always explains why it matched.
  • customer.consents is there so that you never contact someone who opted out using data we handed you. Honour it.
  • message is capped at 5,000 characters; messageTruncated says if more exists in the CRM.
  • Every timestamp carries your organisation's timezone offset; see dates and timezones.
  • There is no total. Page until hasMore is 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.

orderGuarantee
createdAt:descEvery 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:ascThe same exactly-once property from oldest forward. Misses edits to leads already passed. Use for a one-off backfill.
updatedAt:ascSync 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

GET/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.

Path parameters
idstring · uuidRequired

The lead's id from create or list.

Responses
200
The lead.
404
No such lead in your organisation, or the value is not an id.
Example request
curl https://api.evacrm.co.uk/v1/leads/3738d84c-61ff-4d96-9d4d-cd87adc27a5f \
  -H "Authorization: Bearer sk_..."
Example response · 200
{
  "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
}

On this page