Quickstart
Five minutes, four steps, one endpoint. At the end you will have a real person in the CRM and you will know what happens if you send them again.
1. Get a token
Sign in to the CRM and go to Settings → API tokens. Create one, pick the location it belongs to, and copy it: the plain text is shown once and never again.
If your account has more than one location, binding the token matters more than it looks — Authenticating requests explains why in one paragraph.
2. Send the lead
curl --request POST "https://klozzo.com/api/leads" \
--header "Authorization: Bearer {YOUR_API_TOKEN}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data '{
"email": "ana.lopez@example.com",
"phone": "+525555555555",
"first_name": "Ana",
"last_name": "López",
"source": "landing-verano"
}'
Send what you have. email or phone is enough — everything else is
optional, and nothing you send is required to exist beforehand.
3. Read what came back
{
"data": {
"contact": {
"ulid": "01M0RPTKZTY0KY011772XSRRWX",
"first_name": "Ana",
"email": "ana.lopez@example.com",
"phone_e164": "+525555555555"
},
"contact_created": true,
"touch_id": "01M0RPTM0B9KJH7VK5B2MTWYT3",
"applied": {
"first_name": "Ana",
"email": "ana.lopez@example.com",
"phone": "+525555555555",
"source": "landing-verano"
},
"skipped": {},
"unmapped_keys": []
}
}
Four things worth keeping:
contact.ulidis the id you store on your side. It is what the CRM's own URLs use.contact_createdtells you whether this was a new person or one you had already seen.appliedis what actually landed, field by field.skippedis what did not, and why.touch_idis this arrival. A person has one contact and as many touches as times they raised their hand.
4. See it in the CRM
Open Contacts and it is at the top of the list, or go straight to it:
https://klozzo.com/contacts/01M0RPTKZTY0KY011772XSRRWX
That is the same ulid the response gave you.
Send it again
This is the part that surprises people, so try it now — post the same person a second time, with a different last name:
{
"data": {
"contact_created": false,
"touch_id": "01M0RPV0S01SJN5WGCRDVRDNM3",
"applied": { "last_name": "Nuevo" },
"skipped": {
"first_name": "already_set",
"email": "already_set",
"source": "immutable"
}
}
}
Still 200. One contact, two touches. The new last name landed because the
field was empty; the rest was left alone, and skipped says exactly why —
already_set for what you had already filled, immutable for what is written
once and locked, like where they came from.
This is the whole reason /api/leads exists. The same two calls against
POST /api/contacts would answer 422 the second time and drop everything you
sent. Which endpoint do I use? has the table.
The error you will hit first
A phone number without a country code:
{
"message": "Falta la lada del país. Manda el número como «+525555555555», o añade «phone_country» (por ejemplo «MX»). Sin lada no podemos enviarle WhatsApp ni llamarle.",
"errors": {
"phone": ["Falta la lada del país. Manda el número como «+525555555555», o añade «phone_country» (por ejemplo «MX»). Sin lada no podemos enviarle WhatsApp ni llamarle."]
}
}
Two ways to fix it, and either works:
- Send the number in E.164:
"phone": "+525555555555". - Or send the country beside it:
"phone": "5555555555"and"phone_country": "MX".
The CRM refuses to guess the country. A wrong guess is a WhatsApp message to a stranger, and there is no way to take that back.
Error messages come back in Spanish by default, because the people who read them inside the CRM are. Send
Accept-Language: enfor English where a translation exists — and never match on the text: match on the status code and the field name. See Errors.
The other 422 you may see says the CRM cannot tell who this is: you sent
neither an email nor a phone.
Where to go next
- Which endpoint do I use? — leads or contacts, and what it costs to pick wrong.
- Authenticating requests — which location a token acts on.
- Lead capture — every field this endpoint accepts.