POST creates a person, their company and the tag that puts them into a campaign or workflow. Or it merges into the lead you already have, if you send the same person twice.
Where
Settings→API & Integrations
Your role needs
Update access to Lead intake API
update-companies. Admin have it by default.Authenticated by a workspace API key, not by a signed-in session. Creating a key needs the same permission as the rest of the API settings tab.
Status codes
A dry run that would have created a lead still returns
200, not 201. The status code alone can’t tell you what happened. Read meta.status in the body instead; it’s the one field every code path sets.
POST /leads/batch doesn’t follow this table. Once the request itself validates it always returns 200, with a per-row outcome inside the body, so one bad row can’t fail the rest. See POST /leads/batch.Request body
name is the only always-required field. Alongside it, send at least one way to reach the person.
Everything else (
job_title, description, gender, language, decision_maker, links, location, tags, tag_ids, a company block, and custom_fields) is optional and, when present, lands in the same request.
custom_fields lands in the same place a workflow’s collect-data block writes to, so a value you send is readable inside a workflow immediately; see Custom fields and collected data. Each key is slugified before it is stored (Plan tier becomes plan_tier), and any value that is itself an object or an array is dropped rather than flattened. The full list, grouped and described field by field, is on the lead fields reference.
Matching and duplicates
Before the lead or its company is touched, we look for an existing lead in your workspace using the strongest identifier first:
A name is never part of matching. Two people who happen to share one are two people, and merging them would be unrecoverable. Matching never reaches outside the workspace the key belongs to.
on_duplicate decides what happens when a match is found:
On
update, a supplied field wins over what’s on file. But only if you actually sent it; anything you leave out is left alone. name only overwrites when it’s genuinely different, not just re-cased. The lead’s stage is never rolled back to Untouched, even if it had already moved further along your pipeline.
A rejected duplicate (on_duplicate: "error") gets its own response shape, not the usual envelope:
409 Conflict
How the company is resolved
A lead you already have keeps the company it already belongs to. For a person we haven’t seen before, the company is resolved in this order:company.id, when you send one and it belongs to your workspace. An id we can’t find fails with422.- A domain:
company.websitefirst, and only if you didn’t send a usable one, the domain of the person’s email, and then only when it isn’t a shared free-mail domain. The domain is matched against the websites of companies already on file; if nothing matches, a company is created from it. Once a domain is in play the steps below are never reached. company.name, matched case-insensitively against companies on file, and created if nothing matches.- Nothing usable: a placeholder company named after the person, so the lead always has one.
enrich: false skips the scan; the company is still created. meta.company.enrichment_queued reports whether an analysis was queued.
Tags
tags (names) and tag_ids both work, and can be combined. Names are matched case-insensitively, so Inbound finds a tag already called inbound rather than creating a second one. An unrecognized name is created on the fly unless you send create_missing_tags: false, in which case the call fails with 422 and lists the tag names that do exist. An unknown tag_id always fails, regardless of that switch.
Every tag is applied to both the lead and its company: tagging the lead is what lets a workflow enroll it, and tagging the company is what a campaign matches on when it decides who’s next. Send a tag and both happen in the same call. Tags are added to whatever a lead already has, never replacing them. An integration that syncs one tag can’t strip tags a person or another automation put there.
Sending no tag at all is valid, but the lead is stored and never contacted until one is added. meta.warnings says so.
On
on_duplicate: "skip", no tag is attached and meta.tags_applied comes back empty, because the existing lead is left exactly as it was. A brand-new name in tags is still created in your workspace for next time, and still appears in meta.tags_created, even though this call never uses it.Dry run
dry_run: true runs the real thing inside a database transaction that always rolls back, so every check (column limits, matching, company resolution) behaves exactly as it would live. Nothing it writes survives the call.
A dry run never fires a queued job, even though everything else runs for real inside the rollback. Tag application, the website analysis and the trigger a new lead normally fires are all explicitly skipped. A queued job can’t be undone by rolling back a transaction, so a preview would otherwise have real side effects.
null on a dry run (data.id, data.company.id, meta.lead_id, meta.company.id, and the id on each entry in tags_applied), so a preview response can never be mistaken for a saved record. One field to read carefully: meta.company.enrichment_queued still reads true on a preview. It tells you what a real call would queue, not what this one did.
Response envelope
The body is always{ data, meta }. data is the lead in full, the same shape GET /leads/{id} returns:
meta is the part worth reading closely. It’s the answer to “I posted a lead and nothing seems to have happened”:
A successful create
null.
Automations
Creating a lead through this endpoint fires the same New Lead Ready trigger a workflow uses to react to a brand-new lead, even though, unlike a lead found through search, it’s born already analyzed rather than working through an enrichment pipeline first. Only a genuinecreated outcome fires it: an update or a skip does not.
Tags are the other way in, and they work on an update too. Attaching a tag the lead didn’t already carry fires the Tag Added trigger, whatever put it there. So re-posting a person with a tag they don’t have yet can start a workflow, even though meta.status reads updated. Re-sending a tag they already carry changes nothing and fires nothing.
Creating a lead through this endpoint doesn’t spend credits. You’re handing over data you already have, not asking us to find it.
Retrying safely
Add anIdempotency-Key header and a retried call with the exact same body returns the original response instead of creating a second lead: the difference between a network timeout being harmless and it silently duplicating your pipeline. Reusing the same key with a different body is rejected outright. Full mechanics are on Idempotency, deduplication and rate limits.
Related
Lead fields reference
Every field this request accepts, grouped and described one by one.
POST /leads/batch
Send many leads in one call and read the outcome of each row on its own.
Idempotency, deduplication and rate limits
Retry safely, and see what happens when you send too fast.
Errors reference
Every status the API returns and what to change before retrying.