Leads — Pitfalls
Trying to change the lead identifier
Symptom. POST /lead/{leadId} returns 403 INVALID_LEAD with "Cannot modify lead identifier field".
Cause. The workspace's lead identifier (default: phone) is immutable after creation.
Fix. Remove the identifier from the update body. If you genuinely need to change it (e.g. correct a typo), do it manually in the dashboard.
Treating a 200/201 as full success
Symptom. Some custom fields or initial actions are missing on the created or updated lead despite the API returning a successful status.
Cause. POST /lead, POST /lead/{leadId} and POST /lead/{leadId}/action all support partial success. Fields and actions that fail validation are dropped silently and reported in the remarks array; the rest succeed. Update is easy to miss here — it never returns a 400 for a bad field value.
Fix. Always inspect remarks and the per-action status array. Anything with status: IGNORED did not persist.
A valid field that never gets written
Symptom. A field with a correct API name and a correct value is missing from the lead afterwards. remarks says Field is hidden or is locked after creation.
Cause. Two workspace settings block writes without any error:
- Hidden fields. A field not in the default view's visible set is rejected on both create and update, even though it exists and the payload is well-formed.
lockAfterCreate. On update, a field configured this way is refused once it already holds a value.
Fix. Check isVisible on GET /custom-fields before integrating against a field, and make the field visible in Settings → Lead Fields if the API needs to write to it.
Sending search criteria as query parameters
Symptom. 404 or 405 from GET /lead/search?phone=....
Cause. Search is POST with a JSON body — not a GET. Only skip and limit are query parameters.
Fix. Issue a POST to /lead/search with the filters in the request body. See Search leads.
Creating a duplicate lead
Symptom. POST /lead returns 409 LEAD_ALREADY_EXISTS.
Cause. A lead with the same identifier value already exists in the workspace.
Fix. Either fall through to POST /lead/{leadId} to update the existing lead, or use the Async API which performs create-or-update automatically based on the identifier.
Pagination beyond the page cap
Symptom. 400 BAD_REQUEST with "Limit should be between 1 and 100".
Cause. limit on lead search and lead-actions inclusion is capped at 100.
Fix. Cap your page size at 100 and paginate via skip for larger result sets. The response includes total_count to compute how many pages remain.
Searching for unknown statuses
Symptom. fields.status filter returns no results even though leads with that status exist in the dashboard.
Cause. Status values are matched case-sensitively against the workspace's lead stage pipeline. Display labels can differ subtly from what the dashboard shows (extra whitespace, trailing characters).
Fix. Fetch the canonical labels via GET /lead-stage-pipeline and use those exact strings.
Using ISO 8601 dates
Symptom. Date filters (actions.performed_at, a date custom field) return zero results or wrong matches.
Cause. Date inputs accept Unix milliseconds or the workspace-configured date format string. ISO 8601 (e.g. "2026-01-01T10:30:00Z") is not parsed.
Fix. Use one of the supported formats. See Field Types → date.
Filtering leads by creation date
Symptom. {"fields": {"created_on": {"from": …}}} returns 400 INVALID_LEAD — Field created_on not found in the enterprise.
Cause. Everything inside fields is resolved against the workspace's own field definitions. There is no built-in created_on field, so lead search has no creation-date filter.
Fix. Filter on actions.performed_at instead, which does accept a { from, to } window, or add a date field to the workspace and set it at ingestion time.