Skip to content

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.