Overview
Contacts are the people you message. A contact holds a phone number, optional profile fields, tags, custom fields, your ownexternal_id, and an opted_out flag.
Use these endpoints to keep your contact list in sync with your CRM, filter recipients for a campaign, and record opt-outs. Contacts are stored per workspace and shared by every API key in that workspace.
Contacts are not bound to a line_id or a channel. You pick the sending line and the channel (imessage, rcs, or sms) on the send call, not on the contact record.
Base URL
Authentication
Every request carries your API key as a bearer token.GET requests send no body, so the Content-Type header is optional there and ignored if present.
A missing or malformed key returns 401 with code unauthorized. A key that has been revoked returns 401 with code invalid_api_key.
Opt-out is enforced platform wide at send time
This is the most important behavior on this page. Read it before you build anything. When a contact hasopted_out: true, BlueReacher rejects any send addressed to that phone number before the message leaves the platform. The block is not advisory and it is not per campaign. It applies:
- across every line in your workspace, including lines you add later
- across every channel:
imessage,rcs, andsms - across every send path: single sends, sequences, campaigns, and retries
- across every API key in the workspace
403 with code contact_opted_out and the message is never queued. You are not billed for it.
How a contact becomes opted out
- Automatically, from a reply. When an inbound reply arrives, BlueReacher checks the trimmed message body against the stop keyword list:
STOP,STOPALL,UNSUBSCRIBE,END,QUIT,CANCEL,REMOVE,OPT OUT,OPTOUT. Matching is case insensitive and applies when the keyword is the entire message. The flag flips within seconds of the reply landing, and acontact.opted_outwebhook fires. - Manually, through the API. Call
POST /set-opt-out. - Manually, in the dashboard. A team member can toggle it on the contact record.
- Deleting a contact does not clear its opt-out. Send attempts to that number stay blocked.
- Re-creating the same phone number returns a new contact with
opted_outalready set totrue.
POST /set-opt-out with opted_out: false clears the suppression, and it requires a reason string describing the consent you collected. Store that reason honestly. BlueReacher lines need no A2P registration, so nothing in the carrier stack is tracking consent on your behalf. This suppression list is your record.
The contact object
Phone normalization. Send E.164. A 10 digit US number with no country code is normalized to
+1 and stored in E.164. Anything else that cannot be parsed returns 400 with code invalid_phone.
Merge tags. Wrap a field name in double braces in your message content. A missing value renders as an empty string, so guard your copy accordingly.
Create a contact
Creates one contact. Use this for single record writes such as a form submission or a CRM webhook. For imports of more than a handful of records, use/bulk-create-contacts.
Authorization header. The contact is created in the workspace that owns the key.
Request fields
opted_out is rejected here. Sending it returns 422 with code validation_failed. A new contact inherits opted_out: true automatically if its phone number is already on the workspace suppression list.
Response fields
Example request
A
409 is a normal outcome for CRM syncs. Catch it, read error.details.contact_id, and call /update-contact instead of retrying the create.
Retrieve a contact
Fetches one contact. Look it up by BlueReacher id, by phone number, or by your ownexternal_id.
Authorization header. You can only read contacts in your own workspace.
Request fields (query string)
Supply exactly one identifier. Sending two or more returns
400 with code invalid_request.
Response fields
The full contact object. See The contact object for every field and its type.
Example request
List contacts
Returns a page of contacts, newest first by default. Combine search, tag filters, and the opt-out filter to build a send list.Authorization header. Results are scoped to the key’s workspace.
Request fields (query string)
Response fields
Example request
Every sendable contact tagged both
saas and q3-outbound:
opted_out=false keeps opted-out people out of your send loop, which saves you a round trip. The platform still blocks them at send time if one slips through.
Errors
Update a contact
Applies a partial update. Only the fields you send change. Everything you leave out stays as it is.Authorization header.
Request fields
opted_out cannot be changed here. Sending it returns 422 with code validation_failed. Use /set-opt-out so every consent change lands on the suppression list with a source and a timestamp.
Response fields
The full updated contact object, same shape as The contact object. updated_at reflects the change.
Example request
Delete a contact
Permanently removes the contact record. This cannot be undone.Authorization header. The request carries a JSON body.
Message history for the number is retained for reporting and is no longer linked to a contact record. The opt-out is retained too. If the contact was opted out, its number stays on the workspace suppression list and sends to it stay blocked, whether or not you re-create the contact later.
Request fields
Response fields
Example request
Deletes are idempotent from your side in practice: a second call on the same id returns
404, which you can treat as success.
Bulk create contacts
Imports up to 1000 contacts in one request and reports exactly which rows were created, which were duplicates, and which failed validation. Nothing is silently dropped.Authorization header.
The request is processed synchronously and returns when the batch is finished, typically under 5 seconds for 1000 rows. Rows are independent: a bad row does not roll back the good ones.
Request fields
A row counts as a duplicate when its
phone or external_id already exists in the workspace, or when an earlier row in the same payload used the same phone or external_id. The first occurrence wins and later occurrences are reported as duplicates.
Response fields
Example request
opted_out: true. Your import cannot clear an opt-out. That is the whole point of the suppression list.
Errors
Row level problems never fail the whole request. A
200 with a non empty errors array is the normal way validation failures come back, so check summary.failed on every import.
Set opt-out
Sets or clears the opt-out flag for a contact. This is the only endpoint that writes the flag through the API.Authorization header. The change applies across the whole workspace immediately.
Use it to mirror unsubscribes from your own system, from a landing page, or from an email tool. The effect is instant: sends already queued for that number are cancelled before dispatch.
Request fields
Response fields
Example request
Calling with the state a contact already has is a no-op and returns
200 with the current record. opted_out_at is not refreshed when the state is unchanged.
Errors
Every error uses the same envelope.
Treat
4xx codes as final. Retrying an identical request will produce the identical error. Retry 429 after the stated delay and 500 with exponential backoff.
Rate limits
Every response carries the current state of your budget:
X-RateLimit-Reset is a Unix timestamp in seconds. A 429 also returns details.retry_after in seconds. Prefer bulk-create-contacts over loops of create-contact: one bulk call of 1000 rows costs a single request against your budget.
Next steps
- Send to a contact with the messaging endpoints, using
contact_idortoplus achannelofimessage,rcs, orsms. - Subscribe to the
contact.opted_outwebhook so your CRM learns about stop replies without polling. - Pull sendable audiences with
list-contactsfiltered onopted_out=falsebefore every campaign run.