Skip to content

Leads

Base URL: https://api.klozeo.com/api/v1 Auth: X-API-Key: sk_live_... required on all requests.

Lead IDs use the prefix cl_ (e.g., cl_01234567-89ab-cdef-0123-456789abcdef). Always include the full ID with the prefix in API requests.

FieldTypeNotes
idstringFormat: cl_<uuid>. Read-only.
namestringRequired
sourcestringRequired
emailstringOptional
phonestringOptional
citystringOptional
websitestringOptional
ratingnumberOptional. Useful in scoring expressions.
tagsstring[]Optional array
scorenumber0–100. Computed via scoring rules.
source_idstringOptional. External ID used for deduplication.
statusstringPipeline status. One of: new, contacted, qualified, disqualified, converted. Default: new.
created_atintegerUnix seconds. Read-only.
updated_atintegerUnix seconds. Updated only when a structural field changes.
last_interaction_atintegerUnix seconds. Updated on every push or merge.

All timestamps are Unix seconds (not milliseconds). For JavaScript: new Date(timestamp * 1000).


GET /leads
ParameterTypeDefaultDescription
filterstringRepeatable. Format: logic.operator.field.value. Example: filter=and.eq.city.Paris. See Filtering guide →
sort_bystringField to sort by
sort_orderstringASCASC or DESC
limitinteger50Max results (max: 1000)
cursorstringOpaque pagination token from previous response

Response 200 OK:

{
"leads": [{ "id": "cl_...", "name": "Acme", "source": "website", "score": 87, "..." }],
"next_cursor": "eyJpZCI6ImNsXzAxMjM...",
"has_more": true,
"count": 50
}

POST /leads

Required fields: name, source. All other fields are optional.

{
"name": "Acme Corporation",
"source": "website",
"email": "[email protected]",
"city": "San Francisco",
"rating": 4.5,
"tags": ["enterprise"],
"attributes": [
{ "name": "industry", "type": "text", "value": "Software" },
{ "name": "employees", "type": "number", "value": 500 }
]
}

Automatic deduplication runs before every insert. The response varies depending on the outcome:

// New lead created (201 Created)
{ "id": "cl_...", "message": "Lead created successfully", "created_at": 1703520000 }
// Duplicate detected and merged (200 OK)
{ "id": "cl_existing...", "message": "Duplicate detected, existing lead updated", "created_at": 1703520000, "duplicate": true }
// Low-confidence match — new lead created with a hint (201 Created)
{ "id": "cl_new...", "message": "Lead created successfully", "potential_duplicate_id": "cl_similar..." }

Deduplication priority:

PriorityCriterionConfidenceAction
1email match (case-insensitive)HighMerge → 200 + "duplicate": true
2source_id match (exact)HighMerge → 200 + "duplicate": true
3phone + name similarity > 80%MediumMerge → 200 + "duplicate": true
4name + city matchLowCreate + "potential_duplicate_id"

Merge strategy: Last Touch Wins — non-empty incoming fields overwrite existing. last_interaction_at is always bumped.

Error 403 when the free plan lead limit (100) is reached.

Error 409 Conflict when a unique-index violation on email or source_id cannot be resolved by merge (rare race after INSERT):

{ "error": "conflict", "message": "A lead with this email or source_id already exists", "code": "conflict" }

GET /leads/{id}

Response 200 OK: Full lead object (see schema above).

Error 404 if the lead does not exist or belongs to a different account.


PUT /leads/{id}

Partial update — only include fields to change. Returns the updated lead.

{ "rating": 4.8, "city": "New York", "tags": ["enterprise", "updated"] }

Error 404 if the lead does not exist or belongs to a different account.

Error 409 Conflict if the update would set an email or source_id already owned by another lead:

{ "error": "conflict", "message": "A lead with this email or source_id already exists", "code": "conflict" }

DELETE /leads/{id}

Response 204 No Content.

Error 404 if the lead does not exist or belongs to a different account.


GET /leads/{id}/changes?limit=50

Audit trail for a lead (newest first). Default limit 50, max 100. See Deduplication → Audit trail.

{
"changes": [
{
"id": "uuid-...",
"lead_id": "cl_...",
"user_id": "...",
"actor_id": "...",
"action": "merged",
"before": {},
"after": {},
"reason": "dedup:email",
"created_at": 1703520000
}
],
"count": 1
}

action is one of created, updated, merged, deleted.


POST /leads/batch

Up to 100 leads (Free) or 500 leads (Pro) per request. Exceeding the limit returns 400 Bad Request — the entire request is rejected and no leads are created. An empty leads array also returns 400.

Deduplication runs per item (same logic as POST /leads). Merges count as success with duplicate: true and do not consume a leads slot.

{
"leads": [
{ "name": "Lead 1", "source": "import", "city": "Athens" },
{ "name": "Lead 2", "source": "import", "city": "London" }
]
}

Response 201 Created (all succeeded) or 207 Multi-Status (partial failure):

207 Multi-Status means at least one lead failed. Always check the errors array, even when the request returned 207 and not a 4xx/5xx. Entries in created were saved successfully.

{
"created": [{ "index": 0, "id": "cl_...", "created_at": 1703520000 }],
"errors": [{ "index": 1, "message": "name is required" }],
"total": 2, "success": 1, "failed": 1
}

PUT /leads/batch

Apply the same partial update to multiple leads. 200 OK if all succeed, 207 Multi-Status if any fail — walk results[]. Empty ids400.

{
"ids": ["cl_aaa...", "cl_bbb..."],
"data": { "source": "LinkedIn" }
}

DELETE /leads/batch

Same status rules as batch update (200 / 207 + results[]). Empty ids400.

{ "ids": ["cl_aaa...", "cl_bbb..."] }

POST /leads/{id}/score

Recalculates and persists the score for a single lead based on current scoring rules.


POST /leads/score/batch

Recalculates scores for all leads in your account.


GET /leads/export?format=csv

See the Export reference for full details.


StatusCodeDescription
400bad_requestInvalid request body or parameters (e.g., missing required fields, empty batch arrays, batch limit exceeded)
401unauthorizedMissing or invalid API key
403leads_limit_reached / forbiddenFree plan lead limit, viewer on a mutation, or non-owner on /api-keys
207Multi-Status — batch partial failure (inspect body)
409conflictUnique email or source_id on POST /leads or PUT /leads/{id}
404not_foundLead not found or belongs to a different account
429rate_limit_exceededRate limit exceeded — see Rate Limits
500internal_errorInternal server error