Get Lead History
The lead's audit trail, newest first.
Endpoint
GET /v2/leads/{lead_id}/history
Path Parameters
| Parameter |
Type |
Required |
Description |
lead_id |
string |
Yes |
The lead. |
Query Parameters
| Parameter |
Type |
Required |
Description |
limit |
integer |
No |
Entries per page, 1–200. Defaults to 50. |
cursor |
string |
No |
next_cursor from the previous page. Omit for the first page. |
Request
curl --location '{base_url}/v2/leads/3f8b21c4-5d9e-4a17-b6c2-0e7154a9d833/history' \
--header 'Authorization: Bearer JWT_TOKEN'
Response
Success Response
Code: 200 OK
{
"data": [
{
"id": "90412",
"event_type": "STATUS_CHANGED",
"field": "status_id",
"old_value": "8d2c5e0a-41f7-4b3e-9a6d-2f1b7c4e9a01",
"new_value": "c4a91f37-0b6e-4d28-8f15-6e3a2b9d7c42",
"old_label": "New Lead",
"new_label": "Contacted",
"reason_id": "5e9a2c71-d84b-4f06-a3c9-0b7e1f6d2a55",
"reason_label": "Spoke with patient",
"subreason_id": "a7d3f5b9-2c18-4e6a-9b40-8f1c5e2d7b63",
"subreason_label": "Interested, deciding",
"notes": "Patient asked to be called back after 5pm.",
"actor_type": "USER",
"actor_user_id": "2094",
"actor_name": "Dana Whitfield",
"occurred_at": "2026-09-21T14:31:37Z"
}
],
"next_cursor": "90412"
}
Response Fields
| Field |
Type |
Description |
data |
array of object |
The page of entries, newest first. See Data. |
next_cursor |
string |
Send as cursor for the next page — it's the id of the last entry returned. Absent on the last page. |
Data
| Field |
Type |
Description |
id |
string |
The entry's id. |
event_type |
string |
What happened — e.g. CREATED, STATUS_CHANGED, OUTREACH_LOGGED, INTAKE_UPDATED, CONVERTED, DROPPED. New types are added over time; treat the list as open. |
field |
string or null |
The lead field the entry changed, when it records a field change. |
old_value |
string or null |
The value before the change. For STATUS_CHANGED: the previous status id when field is status_id, the previous stage when it's lead_status — a stage the pipeline moved on its own. |
new_value |
string or null |
The value after the change. For STATUS_CHANGED, the new status id or stage, as field says. |
old_label |
string or null |
For a status change, the name the previous status had at the time. null otherwise. |
new_label |
string or null |
For a status change, the name the new status had at the time. null otherwise. |
reason_id |
string or null |
The reason cited with a status change. |
reason_label |
string or null |
The reason's name at the time. |
subreason_id |
string or null |
The sub-reason cited with a status change. |
subreason_label |
string or null |
The sub-reason's name at the time. |
notes |
string or null |
Free text recorded with the change. |
actor_type |
string or null |
USER, or SYSTEM for entries the pipeline writes itself. |
actor_user_id |
string or null |
The user who caused the entry. null for SYSTEM entries. |
actor_name |
string or null |
The user's name as recorded at the time, so the trail survives a rename. |
occurred_at |
string (date-time) |
When the entry was recorded. |
Error Response
Code: 400 Bad Request
{
"code": "bad_request",
"message": "Validation failed",
"errors": [
{
"field": "patient_id",
"message": "must be a string"
}
]
}
Every error has the same shape — a machine-readable code, a message, and an errors[] array that's empty when there's nothing field-specific to report. The HTTP status is authoritative.
| Status |
code |
Meaning |
400 Bad Request |
bad_request |
The request body or parameters failed validation. errors[] names the offending fields. |
401 Unauthorized |
unauthorized |
The access token is missing, malformed or expired. |
403 Forbidden |
forbidden |
The request references a resource outside the token's organisation or clinic scope. |
404 Not Found |
not_found |
No such resource, or it's outside your scope. |
429 Too Many Requests |
rate_limited |
Rate limit exceeded. Back off and retry. |
500 Internal Server Error |
internal_error |
Unexpected server error. |
502 Bad Gateway |
upstream_error |
Upstream service failed or timed out. |
Notes
- Cursor-paginated: pass
next_cursor back as cursor. It's absent on the last page.
- A
STATUS_CHANGED entry is either a status change — field is status_id, with the old and new status ids and, in old_label and new_label, the names they had then — or a stage the pipeline moved on its own, such as intake starting — field is lead_status, with the old and new stages.
- The history records more than the webhooks emit: outreach, notes, eligibility and authorization are all here and fire no webhook, and intake progress fires
lead.updated only when it moves the lead out of NEW.