Documentación

La documentación del producto está disponible solo en inglés.

Leads endpoints

A lead is a prospective client in your intake pipeline. These endpoints push prospects from an outside source into Esqase, then read and update them.

All paths sit under the base address https://api.esqase.com, and every request needs an API key. See Authentication and scopes if you have not made your first call yet.

The endpoints

ActionMethod and pathScope needed
ListGET /v1/leadsleads:read
CreatePOST /v1/leadsleads:write
RetrieveGET /v1/leads/{id}leads:read
UpdatePATCH /v1/leads/{id}leads:write
DeleteDELETE /v1/leads/{id}leads:write

The lead object

A single-record read, and a create or update response, also carry customFields and unsupportedCustomFields, described in Custom fields in the API. List entries never carry those two properties.

  • id: the id Esqase assigns. Use it in every other path.
  • publicId: the human-readable lead number your firm's numbering settings produce, such as L-0007. Esqase assigns it; you never send one.
  • status: where the lead sits in its lifecycle. Leads created through the API start as ACTIVE, and the other states (DRAFT, HIRED, NOT_HIRED, ARCHIVE) are set in the dashboard.
  • value: how promising the lead is, one of LOWEST, LOW, MEDIUM, HIGH, HIGHEST, or null.
  • isPrivate: true when only the lead's assignees and firm owners can see it.
  • leadStageColumnId: the id of the pipeline stage the lead sits in, or null.
  • contact: an object with the linked contact's id and name, or null.
  • practiceArea: an object with the practice area's id and name, or null.
  • createdAt, updatedAt: timestamps.

Important: Creating a lead also creates its contact. You do not point a lead at an existing contact. The prospect details you send become a brand-new contact record, and the lead is attached to it. This matches the New lead dialog in the app. See Managing leads.

List leads

GET /v1/leads returns your leads a page at a time, newest first. Two query parameters control the page:

  • limit: how many records to return. Minimum 1, maximum 100, default 20.
  • offset: how many records to skip before the page starts. Minimum 0, default 0.
curl "https://api.esqase.com/v1/leads?limit=1&offset=0" \
  -H "Authorization: Bearer $ESQASE_API_KEY"
{
  "data": [
    {
      "id": "d4e5f6a7-0001-4a1b-9c2d-1234567890ab",
      "publicId": "L-0007",
      "status": "ACTIVE",
      "value": "HIGH",
      "isPrivate": false,
      "leadStageColumnId": "d4e5f6a7-9000-4a1b-9c2d-1234567890ab",
      "contact": {
        "id": "a1b2c3d4-0009-4a1b-9c2d-1234567890ab",
        "name": "Jane Doe"
      },
      "practiceArea": {
        "id": "b2c3d4e5-1000-4a1b-9c2d-1234567890ab",
        "name": "Family law"
      },
      "createdAt": "2026-07-01T16:05:00.000Z",
      "updatedAt": "2026-07-01T16:05:00.000Z"
    }
  ],
  "pagination": { "limit": 1, "offset": 0, "total": 24 }
}

Create a lead

POST /v1/leads creates the prospect's contact, then the lead attached to it, and returns 201 Created with the stored lead.

The prospect's details use the same name fields as a contact:

  • type (optional): PERSON or COMPANY. Defaults to PERSON.
  • firstName, middleName, lastName, nickname (optional): up to 128 characters each. A PERSON needs at least a firstName or a lastName.
  • prefix, suffix (optional): up to 32 characters each.
  • companyName, tradeName (optional): up to 255 characters each. A COMPANY needs a companyName.
  • companyType (optional): up to 64 characters.

Then the lead's own fields:

  • email (optional): a single email address, up to 255 characters. Note the singular name: leads take one email, not a list.
  • phone (optional): a single phone number, up to 255 characters.
  • practiceAreaId (optional): the practice area the prospect is interested in.
  • leadStageColumnId (optional): the pipeline stage the lead starts in. Leave it out and Esqase uses your firm's first lead stage. If your firm has no lead stages at all, the request is rejected with 400.
  • value (optional): LOWEST, LOW, MEDIUM, HIGH, or HIGHEST.
  • isPrivate (optional): true or false. Defaults to false.
curl -X POST https://api.esqase.com/v1/leads \
  -H "Authorization: Bearer $ESQASE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "PERSON",
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jane@example.com",
    "phone": "+13105551234",
    "practiceAreaId": "b2c3d4e5-1000-4a1b-9c2d-1234567890ab",
    "value": "HIGH"
  }'

The response includes the contact the lead just created, so you can link the two on your side:

{
  "data": {
    "id": "d4e5f6a7-0001-4a1b-9c2d-1234567890ab",
    "publicId": "L-0007",
    "status": "ACTIVE",
    "value": "HIGH",
    "isPrivate": false,
    "leadStageColumnId": "d4e5f6a7-9000-4a1b-9c2d-1234567890ab",
    "contact": {
      "id": "a1b2c3d4-0009-4a1b-9c2d-1234567890ab",
      "name": "Jane Doe"
    },
    "practiceArea": {
      "id": "b2c3d4e5-1000-4a1b-9c2d-1234567890ab",
      "name": "Family law"
    },
    "customFields": {},
    "unsupportedCustomFields": [],
    "createdAt": "2026-07-01T16:05:00.000Z",
    "updatedAt": "2026-07-01T16:05:00.000Z"
  }
}

Important: If your firm has not finished its lead numbering setup, POST /v1/leads returns 409 with the code not_configured. An owner or administrator fixes that once in Settings, then Firm, and every later call works.

Retrieve a lead

GET /v1/leads/{id} returns one lead in the shape above, plus the two custom field properties described in Custom fields in the API. List entries never carry those two properties.

curl https://api.esqase.com/v1/leads/d4e5f6a7-0001-4a1b-9c2d-1234567890ab \
  -H "Authorization: Bearer $ESQASE_API_KEY"

If no lead in your firm has that id, you get 404 with the code not_found.

Update a lead

PATCH /v1/leads/{id} changes the fields you send and leaves the rest alone. Every field is optional.

  • practiceAreaId: move the lead to a different practice area, or send null to clear it.
  • leadStageColumnId: move the lead to a different pipeline stage.
  • value: LOWEST, LOW, MEDIUM, HIGH, HIGHEST, or null to clear.
  • isPrivate: true or false.

The prospect's name, email, and phone are not editable here. They live on the lead's contact, so update them with PATCH /v1/contacts/{id} using the contact.id from the lead. See Contacts endpoints.

curl -X PATCH https://api.esqase.com/v1/leads/d4e5f6a7-0001-4a1b-9c2d-1234567890ab \
  -H "Authorization: Bearer $ESQASE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "value": "HIGHEST",
    "leadStageColumnId": "d4e5f6a7-9001-4a1b-9c2d-1234567890ab"
  }'

The response is the reloaded lead, so it reflects the change:

{
  "data": {
    "id": "d4e5f6a7-0001-4a1b-9c2d-1234567890ab",
    "publicId": "L-0007",
    "status": "ACTIVE",
    "value": "HIGHEST",
    "isPrivate": false,
    "leadStageColumnId": "d4e5f6a7-9001-4a1b-9c2d-1234567890ab",
    "contact": {
      "id": "a1b2c3d4-0009-4a1b-9c2d-1234567890ab",
      "name": "Jane Doe"
    },
    "practiceArea": {
      "id": "b2c3d4e5-1000-4a1b-9c2d-1234567890ab",
      "name": "Family law"
    },
    "customFields": {},
    "unsupportedCustomFields": [],
    "createdAt": "2026-07-01T16:05:00.000Z",
    "updatedAt": "2026-07-02T08:20:00.000Z"
  }
}

Delete a lead

DELETE /v1/leads/{id} is a soft delete: the lead is archived out of the active pipeline rather than erased, exactly as deleting it in the dashboard does. The contact the lead created is left in place.

curl -X DELETE https://api.esqase.com/v1/leads/d4e5f6a7-0001-4a1b-9c2d-1234567890ab \
  -H "Authorization: Bearer $ESQASE_API_KEY"
{
  "data": {
    "id": "d4e5f6a7-0001-4a1b-9c2d-1234567890ab",
    "deleted": true
  }
}

Custom fields

Leads accept your firm's lead custom fields through an optional customFields object on create and update, and single-record reads return the values back. Lead custom fields are written on the lead, never on the contact it created. See Custom fields in the API for the full rules.

curl -X POST https://api.esqase.com/v1/leads \
  -H "Authorization: Bearer $ESQASE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jane@example.com",
    "customFields": { "intake_channel": "phone" }
  }'

Full examples in JavaScript and Python

Both examples read the key from an environment variable, create a lead from an intake payload, then raise its value.

JavaScript (fetch)

const BASE = "https://api.esqase.com/v1";
const headers = {
  Authorization: `Bearer ${process.env.ESQASE_API_KEY}`,
  "Content-Type": "application/json",
};

// Create the lead (and, with it, the prospect's contact).
const createResponse = await fetch(`${BASE}/leads`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    type: "PERSON",
    firstName: "Jane",
    lastName: "Doe",
    email: "jane@example.com",
    phone: "+13105551234",
    practiceAreaId: "b2c3d4e5-1000-4a1b-9c2d-1234567890ab",
    value: "HIGH",
  }),
});
const { data: lead } = await createResponse.json();
console.log(`Created lead ${lead.publicId} for contact ${lead.contact.id}`);

// Raise its value after a promising call.
await fetch(`${BASE}/leads/${lead.id}`, {
  method: "PATCH",
  headers,
  body: JSON.stringify({ value: "HIGHEST" }),
});

Python (requests)

import os
import requests

BASE = "https://api.esqase.com/v1"
headers = {
    "Authorization": f"Bearer {os.environ['ESQASE_API_KEY']}",
    "Content-Type": "application/json",
}

# Create the lead (and, with it, the prospect's contact).
lead = requests.post(
    f"{BASE}/leads",
    headers=headers,
    json={
        "type": "PERSON",
        "firstName": "Jane",
        "lastName": "Doe",
        "email": "jane@example.com",
        "phone": "+13105551234",
        "practiceAreaId": "b2c3d4e5-1000-4a1b-9c2d-1234567890ab",
        "value": "HIGH",
    },
).json()["data"]
print(f"Created lead {lead['publicId']} for contact {lead['contact']['id']}")

# Raise its value after a promising call.
requests.patch(
    f"{BASE}/leads/{lead['id']}",
    headers=headers,
    json={"value": "HIGHEST"},
)

Errors

StatusCodeWhen
400invalid_requestA field is the wrong type or too long, a person has no first or last name, a company has no companyName, or your firm has no lead stage and you sent no leadStageColumnId.
400invalid_custom_fieldSomething in the customFields object could not be written. See Custom fields in the API.
401unauthorizedThe key is missing, wrong, revoked, or expired.
403forbiddenThe key is missing leads:read or leads:write, or the member who created it lacks the matching permission.
404not_foundNo lead in your firm has that id.
409not_configuredThe firm has no lead numbering settings yet.
409conflictThe lead has lost its contact, so it cannot be updated. Open it in the dashboard.
429rate_limitedToo many requests. Wait the Retry-After seconds.

The full list of status codes is in Authentication and scopes.

Common questions

  • Can I attach a lead to a contact I already have? No. Every lead creates its own contact from the details you send.
  • How do I change the prospect's email? Update the lead's contact with PATCH /v1/contacts/{id}, using the contact.id on the lead.
  • Can I convert a lead into a matter through the API? No. Conversion happens in the dashboard. See Managing leads.
  • Where do lead stage ids come from? Read a lead you already have, or set your pipeline up in the app. The API has no lead-stage endpoint, and leaving leadStageColumnId out puts the lead in the first stage.
  • Can I mark a lead as hired through the API? No. The update endpoint changes practice area, stage, value, and the private flag only.