Skip to main content
POST

Create or update a contact

Method: POST | Endpoint: /v1/contacts Description: Create a contact, or match an existing one, by phone number, and add tags. On an existing contact the optional fields are only filled where they are still empty: values a rep or an earlier call set are never overwritten here. To overwrite, use Update a contact. Authentication: API key in the Authorization header: Authorization: Bearer ib_pk_<64 hex>. Create keys in the dashboard under Settings → API access.

Request Body

Fields not listed above are rejected with 422 validation_error, and the message names the field. Every value is checked before anything is saved, so a rejected call changes nothing.

Responses

201 Created
A new contact was created. A number whose only match is a deleted contact also creates a new one, with a new id; if that number had opted out, the new contact starts with opted_out: true. tags lists the contact’s tag names, sorted. 200 OK
The number matched an existing contact: empty fields were filled from the request, and the tags were added. vip matched the existing VIP tag. 401 Unauthorized
Other message values: Authorization Bearer token required, API key required (use Authorization: Bearer ib_pk_…), Invalid API key format, Customer account not found. 403 Forbidden
Your iBubbl account is suspended or cancelled (message is then Account is cancelled). Every API-key endpoint returns this. 422 Unprocessable Entity
Other error values:
  • validation_error for a bad shape: phone not E.164 (phone must be E.164 format (e.g. +14155551234)), a field too long, or more than 20 tags.
  • invalid_email: 'sam@example' is not an email address
  • invalid_tag: A tag name needs 1–64 characters and no invisible characters (got '')
  • invalid_timezone: timezone outside the supported list.
  • duplicate_tag: a tag name collides with an existing tag in a way iBubbl can’t resolve. Rare; change the name.
500 Internal Server Error
Only if a tag is deleted in the dashboard twice while this call is adding it. Retry the request.

Authorizations

Authorization
string
header
required

Bearer API key. Format: Authorization: Bearer ib_pk_<64 hex> (70 characters). Generate keys from Dashboard → Settings → API Access.

Body

application/json

Fields not listed here are rejected with 422 validation_error.

phone
string
required

Phone number in E.164 format (e.g. +14155551234). The unique key for contacts — upserting an existing phone returns the same contact ID.

Example:

"+14155551234"

first_name
string | null

Contact's first name. Only applied if the existing record's first_name is null — rep-edited values are sticky and won't be overwritten by automation. To change it, use PATCH /v1/contacts/{id}.

Maximum string length: 128
Example:

"Sam"

last_name
string | null

Contact's last name. Same sticky-update semantics as first_name.

Maximum string length: 128
Example:

"Rivera"

email
string | null

Email address. Must look like one (name@domain.tld, no spaces), or the call returns 422 invalid_email. Same sticky-update semantics as first_name.

Maximum string length: 254
Example:

"sam@example.com"

city
string | null

City. Trimmed; blank is treated as not sent. Same sticky-update semantics as first_name.

Maximum string length: 128
Example:

"Brooklyn"

state
string | null

State, province or region. Trimmed, and a 2-letter code is upper-cased (ny → NY). Same sticky-update semantics as first_name.

Maximum string length: 128
Example:

"NY"

timezone
string | null

IANA timezone for working-hours scheduling. Must be one of the supported allowlist (US/Canada timezones, plus common international). Sends to this contact respect the timezone for outside-hours scheduling. Same sticky-update semantics as names.

Example:

"America/Los_Angeles"

tags
string[] | null

Tag names to add, at most 20. Only ever adds: tags the contact already has are kept, and none are removed. A name matches an existing tag trimmed and case-insensitively (vip reuses VIP); a new name creates the tag. Each name is 1–64 characters with no invisible characters, or the call returns 422 invalid_tag before anything is saved. A newly added tag can start workflows with a "Tag added" trigger.

Maximum array length: 20
Example:

Response

Existing contact updated.

id
string
required

Unique contact identifier (UUID).

phone
string
required

Contact's phone number as first seen. Usually E.164; a contact first created by an inbound SMS can carry the sender's bare national form (e.g. 4155551234). Upserting any form of the same number returns this contact.

opted_out
boolean
required

True if this person has opted out: a STOP reply, a rep in the dashboard, a Do Not Disturb in your connected GoHighLevel account, or POST /v1/contacts/{id}/opt-out. When true, every send to this contact returns 422 contact_opted_out. An opt-out can't be undone, and it belongs to the number: a contact deleted and created again is still opted out.

created_at
string
required

ISO 8601 UTC timestamp the contact was created.

first_name
string | null
last_name
string | null
email
string | null
city
string | null
state
string | null
timezone
string | null

IANA timezone used for this contact's sending hours, or null if not set yet. When it is null and the number is a US or Canadian one, iBubbl infers the zone from the area code and saves it here when the contact's first message is processed; otherwise your account's timezone is used. Once set, an upsert never replaces it, so pass timezone on the first upsert, before you send, or change it with PATCH.

tags
string[]

The contact's tag names, sorted case-insensitively.

opted_out_at
string | null

ISO 8601 UTC timestamp of the opt-out, or null.

blocked
boolean
default:false

True if you blocked this contact (dashboard or POST /v1/contacts/{id}/block). Sends to them return 422 contact_blocked until you unblock them. Unlike an opt-out, a block is yours to undo, and it fires no webhook.

blocked_at
string | null

ISO 8601 UTC timestamp of the block, or null.