---
title: "Create or update contact"
method: POST
path: "/api/v1/contacts"
tags: ["contacts"]
---

# Create or update contact

`POST /api/v1/contacts`

## Request

- Base URL: `https://app.blueticked.com`
- URL: `https://app.blueticked.com/api/v1/contacts`
- Auth: HTTP bearer

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

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.

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `business_id` string, uuid, nullable
  - `phone` string: Phone in international form (+27821234567) or South African national form (082 123 4567); spaces and dashes are ignored. A number without a leading + or 00 is read as South African, so write a foreign number with its +. Stored and returned as E.164.
  - `email` string
  - `first_name` string
  - `last_name` string
  - `tags` string[]
  - `custom_fields` object
  - `whatsapp_consent` boolean
  - `sms_consent` boolean
  - `email_consent` boolean

## Response `201`

Success

- object
  - `id` string, uuid, required
  - `business_id` string, uuid, nullable, required
  - `first_name` string, nullable, required
  - `last_name` string, nullable, required
  - `phone` string, nullable, required
  - `email` string, email, nullable, required
  - `tags` string[], required
  - `custom_fields` object, required
  - `channels` object, required
    - `has_whatsapp` boolean, required
    - `whatsapp_opted_in_at` string, date-time, nullable, required
    - `has_sms` boolean, required
    - `sms_opted_in_at` string, date-time, nullable, required
    - `has_email` boolean, required
    - `email_opted_in_at` string, date-time, nullable, required
    - `email_optin_status` string, required
  - `suppressed` boolean, required
  - `created_at` string, date-time, required
  - `updated_at` string, date-time, required
  - `request_id` string, required

## Other responses

- `400`: Validation failed
- `401`: Unauthorized: missing or invalid API key
- `402`: Insufficient wallet balance
- `403`: Forbidden: scope, plan limit, or suspension
- `404`: Not found
- `409`: Conflict (idempotency-key reuse or resource in use)
- `413`: Request body above the endpoint's size limit
- `422`: Understood but refused: the request is well-formed, the action is not permitted for this resource
- `429`: Rate limited
- `500`: Internal error
- `502`: An upstream provider refused or failed the request
- `503`: Channel or send queue unavailable

---

[API](https://skmtc.dev/blueticked/apis/blueticked-api.md) · [All operations](https://skmtc.dev/blueticked/apis/blueticked-api/llms.txt) · [OpenAPI document](https://skmtc.dev/blueticked/apis/blueticked-api/revisions/07132284f4ff?raw)
