Contacts API
Profiles, custom attributes, consent and opt-outs. 9 ops: list_contacts, get_contact, upsert_contact, delete_contact, record_consent, set_contact_opt_out, track_contact_event, import_contacts, export_contacts.
Profiles, custom attributes, consent and opt-outs.
list_contacts
readList contacts · POST /api/v1/list_contacts · scope contacts:read or GET …?input=
Contacts, newest first: search q (name, phone digits, email), filter by tag or by segment (a segment id or builtin:<name>). Returns contacts and total.
curl -X POST https://getwabi.dev/api/v1/list_contacts \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi list_contacts{
"type": "object",
"properties": {
"q": {
"type": "string",
"maxLength": 100
},
"tag": {
"type": "string",
"maxLength": 50
},
"segment": {
"type": "string",
"maxLength": 64
},
"limit": {
"default": 50,
"description": "Page size, at most 200.",
"type": "integer",
"minimum": 1,
"maximum": 200
},
"offset": {
"default": 0,
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
}
}get_contact
readGet a contact · POST /api/v1/get_contact · scope contacts:read or GET …?input=
A contact's profile by id, wa_id or external_id: identity, attributes, language, timezone, and the separate states (messaging: free_form / template_only / blocked with the window; consent per purpose; engagement; support; campaign eligibility), consent history, conversations and recent business events.
curl -X POST https://getwabi.dev/api/v1/get_contact \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi get_contact{
"type": "object",
"properties": {
"contact": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Contact id."
},
"wa_id": {
"description": "WhatsApp id or phone number in international format.",
"type": "string",
"maxLength": 32
},
"external_id": {
"description": "Your own user id.",
"type": "string",
"minLength": 1,
"maxLength": 200
}
}
}upsert_contact
writeCreate or update a contact · POST /api/v1/upsert_contact · scope contacts:write
Finds the contact by wa_id (phone), external_id or email and updates it, or creates it. Attributes merge (null removes a key); tags replace the list. Use it to sync customers from your CRM or app. Returns the contact and whether it was created.
curl -X POST https://getwabi.dev/api/v1/upsert_contact \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi upsert_contact{
"type": "object",
"properties": {
"contact": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Contact id."
},
"wa_id": {
"description": "WhatsApp id or phone number in international format.",
"type": "string",
"maxLength": 32
},
"external_id": {
"description": "Your own user id.",
"type": "string",
"minLength": 1,
"maxLength": 200
},
"name": {
"anyOf": [
{
"type": "string",
"maxLength": 200
},
{
"type": "null"
}
]
},
"email": {
"anyOf": [
{
"type": "string",
"maxLength": 320,
"format": "email",
"pattern": "^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
},
{
"type": "null"
}
]
},
"locale": {
"description": "Preferred language, BCP 47 (es, pt-BR).",
"anyOf": [
{
"type": "string",
"minLength": 2,
"maxLength": 35
},
{
"type": "null"
}
]
},
"timezone": {
"description": "IANA timezone, e.g. America/Mexico_City.",
"anyOf": [
{
"type": "string",
"minLength": 1,
"maxLength": 64
},
{
"type": "null"
}
]
},
"attributes": {
"description": "Custom attributes to merge; null removes one.",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"anyOf": [
{
"type": "string",
"maxLength": 2000
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "null"
}
]
}
},
"tags": {
"maxItems": 50,
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 50
}
}
}
}Delete a contact · POST /api/v1/delete_contact · scope contacts:write
Removes a contact from lists, segments and campaigns (a data subject request). Their message history stays for your records. Admins only.
curl -X POST https://getwabi.dev/api/v1/delete_contact \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"contact":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}'npx @meterapp/wabi delete_contact --json '{"contact":"6f1c2e0a-4b7d-4c2e-9a51-0d3b8e7f9a10"}'{
"type": "object",
"properties": {
"contact": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Contact id."
}
},
"required": [
"contact"
]
}record_consent
writeRecord consent · POST /api/v1/record_consent · scope contacts:write
Records that a contact granted or revoked consent for a purpose (marketing, order_updates…), with its source and evidence (where and how it was collected). Append-only: the latest record per purpose is the state. Campaigns need granted consent for their purpose. Returns the record and the contact's consent state.
curl -X POST https://getwabi.dev/api/v1/record_consent \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"purpose":"purpose","status":"granted"}'npx @meterapp/wabi record_consent --json '{"purpose":"purpose","status":"granted"}'{
"type": "object",
"properties": {
"contact": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Contact id."
},
"wa_id": {
"description": "WhatsApp id or phone number in international format.",
"type": "string",
"maxLength": 32
},
"external_id": {
"description": "Your own user id.",
"type": "string",
"minLength": 1,
"maxLength": 200
},
"purpose": {
"type": "string",
"pattern": "^[a-z][a-z0-9_]{0,63}$",
"description": "What the consent covers: marketing, order_updates, appointment_reminders…"
},
"status": {
"type": "string",
"enum": [
"granted",
"revoked"
]
},
"source": {
"default": "api",
"type": "string",
"enum": [
"import",
"api",
"dashboard",
"inbound_keyword",
"flow",
"agent",
"integration"
]
},
"evidence": {
"default": {},
"description": "e.g. { form: 'checkout', ip: '…', text_shown: '…' }.",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
}
},
"required": [
"purpose",
"status"
]
}set_contact_opt_out
writeOpt a contact out or back in · POST /api/v1/set_contact_opt_out · scope contacts:write
opted_out true: nothing more is sent to this contact (every purpose they granted is revoked), as when they reply STOP. false: they may receive messages again (as when they reply START); marketing still needs fresh consent. Emits contact.opted_out / contact.opted_in.
curl -X POST https://getwabi.dev/api/v1/set_contact_opt_out \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"opted_out":false}'npx @meterapp/wabi set_contact_opt_out --json '{"opted_out":false}'{
"type": "object",
"properties": {
"contact": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Contact id."
},
"wa_id": {
"description": "WhatsApp id or phone number in international format.",
"type": "string",
"maxLength": 32
},
"external_id": {
"description": "Your own user id.",
"type": "string",
"minLength": 1,
"maxLength": 200
},
"opted_out": {
"type": "boolean"
},
"evidence": {
"default": {},
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
}
},
"required": [
"opted_out"
]
}track_contact_event
writeTrack a business event · POST /api/v1/track_contact_event · scope contacts:write
Records something that happened to a customer in your business (booking_created, payment_failed, order.shipped…), for segments (event.<name> counts) and automations. Creates the contact when wa_id is new. Pass idempotency_key to record an event once. Returns the event id.
curl -X POST https://getwabi.dev/api/v1/track_contact_event \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"name":"Support"}'npx @meterapp/wabi track_contact_event --json '{"name":"Support"}'{
"type": "object",
"properties": {
"contact": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$",
"description": "Contact id."
},
"wa_id": {
"description": "WhatsApp id or phone number in international format.",
"type": "string",
"maxLength": 32
},
"external_id": {
"description": "Your own user id.",
"type": "string",
"minLength": 1,
"maxLength": 200
},
"name": {
"type": "string",
"pattern": "^[a-z][a-z0-9_.]{0,63}$",
"description": "snake_case event name."
},
"properties": {
"default": {},
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"occurred_at": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
},
"idempotency_key": {
"type": "string",
"minLength": 1,
"maxLength": 200
}
},
"required": [
"name"
]
}import_contacts
writeImport contacts (CSV) · POST /api/v1/import_contacts · scope contacts:write
Imports up to 5000 contacts from CSV text. Columns: wa_id or phone (international format), name, email, locale, timezone, external_id, tags (; separated), opted_out (yes/no), consent_<purpose> (yes/no), and any other column becomes an attribute. consent applies one consent record (purpose, status, evidence: where and when it was collected) to every row without its own consent column. dry_run counts what would happen. Returns created, updated, skipped, consents recorded and per-row errors.
curl -X POST https://getwabi.dev/api/v1/import_contacts \ -H "Authorization: Bearer wabi_…" \ -H "Content-Type: application/json" \ -d '{"csv":"csv"}'npx @meterapp/wabi import_contacts --json '{"csv":"csv"}'{
"type": "object",
"properties": {
"csv": {
"type": "string",
"minLength": 1,
"maxLength": 5000000
},
"consent": {
"type": "object",
"properties": {
"purpose": {
"type": "string",
"pattern": "^[a-z][a-z0-9_]{0,63}$",
"description": "What the consent covers: marketing, order_updates, appointment_reminders…"
},
"status": {
"type": "string",
"enum": [
"granted",
"revoked"
]
},
"evidence": {
"default": {},
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
}
},
"required": [
"purpose",
"status"
]
},
"update_existing": {
"default": true,
"type": "boolean"
},
"tags": {
"description": "Tags added to every imported contact.",
"maxItems": 10,
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 50
}
},
"dry_run": {
"default": false,
"type": "boolean"
}
},
"required": [
"csv"
]
}export_contacts
readExport contacts (CSV) · POST /api/v1/export_contacts · scope contacts:read or GET …?input=
Exports contacts as CSV (or JSON rows): all of them or one segment's members, with attributes, tags, opt-out and each consent purpose's state. From the API, MCP and CLI this needs the Scale plan (data export API); the dashboard exports on every plan.
curl -X POST https://getwabi.dev/api/v1/export_contacts \ -H "Authorization: Bearer wabi_…"npx @meterapp/wabi export_contacts{
"type": "object",
"properties": {
"segment": {
"type": "string",
"maxLength": 64
},
"format": {
"default": "csv",
"type": "string",
"enum": [
"csv",
"json"
]
}
}
}