BluetickedDeveloper docs

Contacts

6 endpoints, each with examples that open in the console prefilled.

All resourcesGuide: Contacts
POST/api/v1/contactscontacts:writeTry in console

Create or update contact

Create a contact or update the existing contact matched by phone or email.

Body parameters
business_idstring
phonestring
emailstring
first_namestring
whatsapp_consentboolean
sms_consentboolean
email_consentboolean
last_namestring
tagsstring[]
  • A phone is taken in international form (+27821234567) or South African national form (082 123 4567), and is stored and returned as E.164. A number without a leading + is read as South African, so write a foreign number with its +.
  • Supplying a phone number or email stores the identifier but does not grant marketing permission. New contacts default to ineligible on every channel.
  • Set whatsapp_consent, sms_consent, or email_consent to true only when the recipient explicitly agreed to marketing on that channel. Each assertion requires its matching phone or email in the same request.
  • Email consent follows the selected business's confirmation policy. When double opt-in is enabled, channels.email_optin_status is pending until the recipient confirms.
  • Pass business_id to assign the contact to a managed business.
  • The channel flags sit under channels and follow consent, not the identifiers: read contact.channels.has_whatsapp. A phone alone leaves has_whatsapp and has_sms false until whatsapp_consent: true or sms_consent: true records permission, and changing a phone or email clears them.
  • custom_fields values are validated against the workspace's field definitions (Contacts → Fields); unknown keys are rejected. Define the field first, then send it: "custom_fields": { "plan": "Gold" }.
  • The created/updated contact is returned flat (fields at the top level), matching GET /api/v1/contacts/{id}.
  • The per-channel opted_in_at fields are timestamps for confirmed marketing permission, or null when permission is absent. email_optin_status distinguishes pending confirmation from confirmed permission.
  • Creating a contact or adding a tag can enrol it into a workspace-configured journey that later sends a message. contacts:write is the trigger boundary; the journey (authored in your workspace, run asynchronously) is the send authority, so this scope does not, and need not, grant per-channel send rights.
curl -X POST "https://app.blueticked.com/api/v1/contacts" \
  -H "Authorization: Bearer $BLUETICKED_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: change-me-per-request-0001" \
  -d '{
  "business_id": "22222222-2222-4222-9222-222222222222",
  "phone": "+27800000001",
  "email": "sam@example.com",
  "first_name": "Sam",
  "whatsapp_consent": true,
  "sms_consent": true,
  "email_consent": true,
  "last_name": "Mokoena",
  "tags": [
    "vip",
    "launch"
  ]
}'
Response
{
  "id": "ff506e53-e2f0-4d92-884a-dab77d628bad",
  "business_id": "22222222-2222-4222-9222-222222222222",
  "first_name": "Sam",
  "last_name": "Mokoena",
  "phone": "+27800000001",
  "email": "sam@example.com",
  "tags": [
    "vip",
    "launch"
  ],
  "custom_fields": {
    "plan": "Gold",
    "next_checkup": "2026-08-01"
  },
  "channels": {
    "has_whatsapp": true,
    "whatsapp_opted_in_at": "2026-05-22T10:30:00.000Z",
    "has_sms": true,
    "sms_opted_in_at": "2026-05-22T10:30:00.000Z",
    "has_email": true,
    "email_opted_in_at": "2026-05-22T10:30:00.000Z",
    "email_optin_status": "confirmed"
  },
  "suppressed": false,
  "created_at": "2026-05-22T10:30:00.000Z",
  "updated_at": "2026-05-22T10:30:00.000Z",
  "request_id": "req_b2e3d610e10f1b07468721ee98f0e1d2"
}
GET/api/v1/contactscontacts:readTry in console

List contacts

Cursor-paginated contact list with tag, group and free-text filters. A contact tagged test-send (the tag the dashboard's test send puts on the contact it creates for you) is left out, as it is from the app's lists and audiences.

Query parameters
tagstring
group_idstring
qstring
  • Filter with ?tag=, ?group_id= and ?q= (e.g. ?tag=vip&q=sam). Repeat tag= for AND semantics: ?tag=vip&tag=launch returns contacts carrying both.
  • group_id filters to one contact group's members; combine freely with tag= and q=.
  • q= is a best-effort substring match over first/last name, email and phone, not a ranked search.
  • Filter by ?business_id= when syncing one managed business at a time.
  • Page with ?limit= (max 200, default 50) and ?cursor= (the previous page's next_cursor).
  • Always page with next_cursor: the compound (created_at, id) cursor never skips rows that share a timestamp.
curl -X GET "https://app.blueticked.com/api/v1/contacts?tag=vip&q=sam" \
  -H "Authorization: Bearer $BLUETICKED_API_KEY"
Response
{
  "data": [
    {
      "id": "ff506e53-e2f0-4d92-884a-dab77d628bad",
      "business_id": "22222222-2222-4222-9222-222222222222",
      "first_name": "Sam",
      "last_name": "Mokoena",
      "phone": "+27800000001",
      "email": null,
      "tags": [
        "vip"
      ],
      "channels": {
        "has_whatsapp": true,
        "whatsapp_opted_in_at": "2026-05-22T10:30:00.000Z",
        "has_sms": true,
        "sms_opted_in_at": "2026-05-22T10:30:00.000Z",
        "has_email": false,
        "email_opted_in_at": null,
        "email_optin_status": "confirmed"
      },
      "custom_fields": {},
      "suppressed": false,
      "created_at": "2026-05-22T10:30:00.000Z",
      "updated_at": "2026-05-22T10:30:00.000Z"
    }
  ],
  "next_cursor": "MjAyNi0wNS0yMlQxMDozMDowMHxjb25fMTIz",
  "has_more": true,
  "request_id": "req_b2e3d610e10f1b07468721ee98f0e1d2"
}
GET/api/v1/contacts/{id}contacts:readTry in console

Get a contact

Fetch a single contact by id.

  • Returns 404 not_found if the contact does not exist in your workspace.
curl -X GET "https://app.blueticked.com/api/v1/contacts/{id}" \
  -H "Authorization: Bearer $BLUETICKED_API_KEY"
Response
{
  "custom_fields": {},
  "id": "ff506e53-e2f0-4d92-884a-dab77d628bad",
  "business_id": "22222222-2222-4222-9222-222222222222",
  "first_name": "Sam",
  "last_name": "Mokoena",
  "phone": "+27800000001",
  "email": null,
  "tags": [
    "vip"
  ],
  "channels": {
    "has_whatsapp": true,
    "whatsapp_opted_in_at": "2026-05-22T10:30:00.000Z",
    "has_sms": true,
    "sms_opted_in_at": "2026-05-22T10:30:00.000Z",
    "has_email": false,
    "email_opted_in_at": null,
    "email_optin_status": "confirmed"
  },
  "suppressed": false,
  "created_at": "2026-05-22T10:30:00.000Z",
  "updated_at": "2026-05-22T10:30:00.000Z",
  "request_id": "req_b2e3d610e10f1b07468721ee98f0e1d2"
}
PATCH/api/v1/contacts/{id}contacts:writeTry in console

Update a contact

Partially update a contact's profile, tags, or suppressed flag.

Body parameters
business_idstring
first_namestring
tagsstring[]
suppressedboolean
  • Send only the fields you want to change, and at least one field: an empty body answers 400 validation_failed. Changing or clearing phone/email clears consent for the old identifier.
  • Set a matching *_consent field to true to record fresh permission for the effective identifier. Email consent still follows the business's double opt-in policy.
curl -X PATCH "https://app.blueticked.com/api/v1/contacts/{id}" \
  -H "Authorization: Bearer $BLUETICKED_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "business_id": "22222222-2222-4222-9222-222222222222",
  "first_name": "Samuel",
  "tags": [
    "vip",
    "renewed"
  ],
  "suppressed": false
}'
Response
{
  "custom_fields": {},
  "id": "ff506e53-e2f0-4d92-884a-dab77d628bad",
  "business_id": "22222222-2222-4222-9222-222222222222",
  "first_name": "Samuel",
  "last_name": "Mokoena",
  "phone": "+27800000001",
  "email": null,
  "tags": [
    "vip",
    "renewed"
  ],
  "channels": {
    "has_whatsapp": true,
    "whatsapp_opted_in_at": "2026-05-22T10:30:00.000Z",
    "has_sms": true,
    "sms_opted_in_at": "2026-05-22T10:30:00.000Z",
    "has_email": false,
    "email_opted_in_at": null,
    "email_optin_status": "confirmed"
  },
  "suppressed": false,
  "created_at": "2026-05-22T10:30:00.000Z",
  "updated_at": "2026-05-22T11:00:00.000Z",
  "request_id": "req_b2e3d610e10f1b07468721ee98f0e1d2"
}
DELETE/api/v1/contacts/{id}contacts:writeTry in console

Delete a contact

Soft-delete a contact so it drops out of lists, audiences and sends.

  • Soft delete: the contact leaves every list and audience immediately, and the retention sweep hard-purges it after 90 days.
  • This is not POPIA erasure: it does not anonymise the record. Use POST /api/v1/contacts/{id}/erase for a data-subject erasure request.
curl -X DELETE "https://app.blueticked.com/api/v1/contacts/{id}" \
  -H "Authorization: Bearer $BLUETICKED_API_KEY"
Response
{
  "id": "ff506e53-e2f0-4d92-884a-dab77d628bad",
  "deleted": true,
  "request_id": "req_b2e3d610e10f1b07468721ee98f0e1d2"
}
POST/api/v1/contacts/{id}/erasecontacts:writeTry in console

Erase a contact (POPIA)

Anonymise a contact and its personal data for a POPIA erasure request.

  • Irreversible: personal data is scrubbed in place and cannot be recovered. The row is anonymised, not deleted, so counts and foreign keys survive.
  • Idempotent: re-erasing an already-erased contact returns the original erased_at with already_erased: true (and zeroed counts).
  • Works on soft-deleted contacts too (delete-then-erase is a valid order); counts reports how many related records were redacted per table (including active journey enrolments exited under journeyEnrolments, under leads the person's rows of the Leads tables, linked to them or naming them by email or number, and under commercePaymentEvents the payment events of their orders, cleared to their amounts, ids, statuses and dates).
  • When work on the contact is still in progress (an Ask Blu answer about them still running, or a very large backlog to clear), erasure stops before anything is stamped and answers 503 service_unavailable with details.reason erasure_retrying and a Retry-After header: send the same request again after that many seconds. If it still answers 503 after several tries, contact support.
curl -X POST "https://app.blueticked.com/api/v1/contacts/{id}/erase" \
  -H "Authorization: Bearer $BLUETICKED_API_KEY"
Response
{
  "id": "ff506e53-e2f0-4d92-884a-dab77d628bad",
  "erased_at": "2026-07-01T08:00:00.000Z",
  "already_erased": false,
  "counts": {
    "conversations": 3,
    "conversationMessages": 41,
    "messages": 12,
    "flowSubmissions": 0,
    "flowPostSubmitEvents": 0,
    "leads": 1,
    "appointments": 0,
    "whatsappOrders": 0,
    "commerceOrders": 1,
    "commercePaymentEvents": 2,
    "webhookDeliveries": 0,
    "externalIntegrationDeliveries": 0,
    "contactNotes": 2,
    "contactDocuments": 0,
    "agentRuns": 0,
    "journeyEnrolments": 1,
    "whatsappCalls": 0,
    "tickets": 0,
    "ticketComments": 0
  },
  "request_id": "req_b2e3d610e10f1b07468721ee98f0e1d2"
}