Available lang sa Ingles ang dokumentasyon ng produkto.
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
| Action | Method and path | Scope needed |
|---|---|---|
| List | GET /v1/leads | leads:read |
| Create | POST /v1/leads | leads:write |
| Retrieve | GET /v1/leads/{id} | leads:read |
| Update | PATCH /v1/leads/{id} | leads:write |
| Delete | DELETE /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 asL-0007. Esqase assigns it; you never send one.status: where the lead sits in its lifecycle. Leads created through the API start asACTIVE, and the other states (DRAFT,HIRED,NOT_HIRED,ARCHIVE) are set in the dashboard.value: how promising the lead is, one ofLOWEST,LOW,MEDIUM,HIGH,HIGHEST, ornull.isPrivate:truewhen only the lead's assignees and firm owners can see it.leadStageColumnId: the id of the pipeline stage the lead sits in, ornull.contact: an object with the linked contact'sidandname, ornull.practiceArea: an object with the practice area'sidandname, ornull.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):PERSONorCOMPANY. Defaults toPERSON.firstName,middleName,lastName,nickname(optional): up to 128 characters each. APERSONneeds at least afirstNameor alastName.prefix,suffix(optional): up to 32 characters each.companyName,tradeName(optional): up to 255 characters each. ACOMPANYneeds acompanyName.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, orHIGHEST.isPrivate(optional):trueorfalse. Defaults tofalse.
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 sendnullto clear it.leadStageColumnId: move the lead to a different pipeline stage.value:LOWEST,LOW,MEDIUM,HIGH,HIGHEST, ornullto clear.isPrivate:trueorfalse.
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
| Status | Code | When |
|---|---|---|
| 400 | invalid_request | A 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. |
| 400 | invalid_custom_field | Something in the customFields object could not be written. See Custom fields in the API. |
| 401 | unauthorized | The key is missing, wrong, revoked, or expired. |
| 403 | forbidden | The key is missing leads:read or leads:write, or the member who created it lacks the matching permission. |
| 404 | not_found | No lead in your firm has that id. |
| 409 | not_configured | The firm has no lead numbering settings yet. |
| 409 | conflict | The lead has lost its contact, so it cannot be updated. Open it in the dashboard. |
| 429 | rate_limited | Too 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 thecontact.idon 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
leadStageColumnIdout 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.