Developers
Contacts and consent
Keep Pixl's contacts in step with your own system: who they are, your own fields, and whether they agreed to hear from you on WhatsApp.
Create a contact
POST/contactsScope: contacts:write
Body
first_name, last_namestring- The person's name.
phonestring- With the country code:
+919876543210. A number without one is kept as you sent it, and can't be called until its country is known. emailstring- Their email address.
calling_regionstring- The number's country as two letters (
IN), whenphonehas no country code. lifecycle_stagestringnew_lead(when left out),contacted,qualified,customerorlost.notesstring- Free text your team sees on the contact.
custom_fieldsobject- Your own fields by key:
{ "patient_id": "ext_123" }(see Custom fields below).
Send at least a name, an email or a phone number.
One contact per number
curl -X POST "https://api.pixlagents.com/api/v1/contacts" \
-H "Authorization: Bearer $PIXL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Asha",
"last_name": "Rao",
"phone": "+919876543210",
"email": "asha@example.com",
"lifecycle_stage": "new_lead",
"custom_fields": {
"patient_id": "ext_123"
}
}'{
"id": "contact_321",
"display_name": "Asha Rao",
"first_name": "Asha",
"last_name": "Rao",
"phone": "+919876543210",
"email": "asha@example.com",
"lifecycle_stage": "new_lead",
"opted_out": false,
"last_conversation_id": null,
"custom_fields": {
"patient_id": "ext_123"
},
"created_at": "2026-10-26T04:58:00"
}Update a contact
PUT/contacts/{contact_id}Scope: contacts:write
Send only the fields you want to change: first_name, last_name, email, phone, lifecycle_stage, notes, custom_fields. The rest stay as they are. In custom_fields, a key you leave out keeps its value and null clears it.
curl -X PUT "https://api.pixlagents.com/api/v1/contacts/contact_321" \
-H "Authorization: Bearer $PIXL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"lifecycle_stage": "customer"
}'Find contacts
GET/contactsScope: contacts:read
Query
searchstring- Part of a name, an email or a phone number.
include_channel_autoboolean- Contacts Pixl made when someone messaged you, who aren't a lead yet, are left out unless this is
true. Send it when you look someone up by number. lifecycle_stagestring- Only contacts at this stage.
tagstring- Only contacts with this tag.
created_from, created_todate-time- Only contacts created in this range.
skip, limitinteger- Paging. limit is up to 100, and 50 when left out.
curl -G "https://api.pixlagents.com/api/v1/contacts" \
-H "Authorization: Bearer $PIXL_API_KEY" \
--data-urlencode 'search=+919876543210' \
--data-urlencode 'include_channel_auto=true'{
"contacts": [
{
"id": "contact_321",
"display_name": "Asha Rao",
"phone": "+919876543210",
"opted_out": false,
"last_conversation_id": "conv_987"
}
],
"total": 1
}One contact in full: GET /contacts/{contact_id}. Fields worth knowing:
opted_out,opted_out_atandopted_out_scope: whether they stopped WhatsApp messages, and whether that wasallor onlypromotions.call_dnc: they are on your do-not-call list.last_conversation_id: their latest conversation, to read or reply in.
Contacts that changed
To keep your own copy in step, ask GET /contacts for what changed since a time. With updated_since or cursor, the list is in order of the last change, oldest first, and pages with a cursor.
Query
updated_sincedate-time- Contacts changed at or after this time. Needed on the first page.
cursorstring- The previous page's
next_cursor. It takes the place ofupdated_since. limitinteger- Up to 100 a page.
include_channel_autobooleantrueto include people who only messaged you and aren't leads.
curl -G "https://api.pixlagents.com/api/v1/contacts" \
-H "Authorization: Bearer $PIXL_API_KEY" \
--data-urlencode 'updated_since=2026-10-27T00:00:00Z' \
--data-urlencode 'include_channel_auto=true'{
"contacts": [
{
"id": "contact_321",
"display_name": "Asha Rao",
"phone": "+919876543210",
"custom_fields": {
"patient_id": "ext_123"
},
"updated_at": "2026-10-27T05:10:00"
}
],
"total": 1,
"next_cursor": "eyJ1IjoiMjAyNi0xMC0yNyJ9",
"has_more": false
}next_cursoris always there. Whenhas_moreisfalse, keep the last one and ask again with it later.- A change appears about a minute after it's saved, so a page never skips one still being written.
- What counts as a change: the contact's own fields and their custom fields. Tags, lists and opt-outs don't.
- Deleted contacts aren't in it.
- Without
updated_sinceandcursor, the list works as above, andnext_cursorandhas_morearenull.
Custom fields
Custom fields are your own fields on a contact: a patient id, a treatment, a branch. Create them in Pixl first, then read their keys:
GET/custom-fieldsScope: contacts:read
curl -G "https://api.pixlagents.com/api/v1/custom-fields" \
-H "Authorization: Bearer $PIXL_API_KEY" \
--data-urlencode 'entity_type=contact'[
{
"field_key": "patient_id",
"name": "Patient ID",
"field_type": "text",
"options": null
},
{
"field_key": "branch",
"name": "Branch",
"field_type": "dropdown",
"options": [
"Andheri",
"Bandra"
]
}
]Set values
The simplest way is custom_fields on a contact you create or update (above). It's strict: an unknown key, a value of the wrong type or a value not among a dropdown's options is a 422 that names the field. Dates are YYYY-MM-DD. Every contact you read carries its custom_fields too. Or set them on their own:
PUT/custom-fields/values/{entity_type}/{entity_id}Scope: contacts:write
Use contact as entity_type and the contact's id. Values are by field key; types are text, number, date, boolean, dropdown and url.
curl -X PUT "https://api.pixlagents.com/api/v1/custom-fields/values/contact/contact_321" \
-H "Authorization: Bearer $PIXL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"values": {
"patient_id": "ext_123",
"branch": "Andheri"
}
}'{
"updated": true
}This one ignores unknown keys
null value. Read the values back with GET /custom-fields/values/{entity_type}/{entity_id} to check what was saved, or use custom_fields on the contact, which refuses them.Opt-ins
Opt-ins record that a person agreed to hear from you on WhatsApp. Pixl reads them before every send (see WhatsApp consent). They are kept per business: find your business id with GET /workspaces/{workspace_id}/businesses, and use the one with is_default: true unless you run more than one.
POST/businesses/{business_id}/optin-records/importScope: contacts:write
Body
recordsobject[]Required- Up to 10,000 people, each
{ "phone_e164": "+919876543210" }, with an optionalsource_detailobject: where and when they agreed. sourcestring- Where these came from, up to 50 characters: your system's name.
csvwhen left out. categorystringpromotionsorupdates(order and appointment updates). Left out, the opt-in covers both.
curl -X POST "https://api.pixlagents.com/api/v1/businesses/business_123/optin-records/import" \
-H "Authorization: Bearer $PIXL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": "clinic-crm",
"category": "updates",
"records": [
{
"phone_e164": "+919876543210",
"source_detail": {
"form": "booking",
"agreed_at": "2026-10-25T11:20:00Z"
}
}
]
}'{
"imported": 1,
"skipped": 0,
"errors": []
}- Importing the same number again updates its opt-in rather than adding another.
- Numbers that can't be read are skipped and listed in
errors; the rest still import. - Over 10,000 records in one request is refused with a
413. Split the list. - The key's owner must be an owner or admin of the workspace.
An import lifts an earlier opt-out
Opt-outs
When someone tells you, outside Pixl, that they want no more messages, add them to the opt-out list. They then get nothing you start on WhatsApp. An opt-out at one of your businesses counts for all of them.
POST/businesses/{business_id}/optout-recordsScope: contacts:write
Body
phone_e164stringRequired- Their number, with the country code.
notestring- Why, for your team: “asked on a call”.
curl -X POST "https://api.pixlagents.com/api/v1/businesses/business_123/optout-records" \
-H "Authorization: Bearer $PIXL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_e164": "+919876543210",
"note": "Asked at the front desk."
}'{
"id": "record_654",
"withdrawn_at": "2026-10-26T06:10:00"
}Like an import, this needs a key made by an owner or admin. To read the list, use GET /businesses/{business_id}/optout-records (search, skip, limit up to 200). People who text STOP on WhatsApp are added for you, and a webhook can tell your system each time with contact.opted_out.
