Skip to main content

Overview

Contacts are the people you message. A contact holds a phone number, optional profile fields, tags, custom fields, your own external_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
Endpoints on this page

Authentication

Every request carries your API key as a bearer token.
Keys are workspace scoped. A contact created with one key is readable and writable by every key in the same workspace. 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 has opted_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, and sms
  • across every send path: single sends, sequences, campaigns, and retries
  • across every API key in the workspace
A send call to an opted-out number returns 403 with code contact_opted_out and the message is never queued. You are not billed for it. How a contact becomes opted out
  1. 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 a contact.opted_out webhook fires.
  2. Manually, through the API. Call POST /set-opt-out.
  3. Manually, in the dashboard. A team member can toggle it on the contact record.
Suppression outlives the contact record The opt-out is stored on a workspace suppression list keyed by phone number, not only on the contact row. Two consequences:
  • 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_out already set to true.
That is deliberate. It means a bad CRM sync cannot resurrect somebody who asked you to stop. Turning opt-out back off 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.
Auth. Bearer API key in the 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
Example response
Errors 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 own external_id.
Auth. Bearer API key in the 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
Example response
Errors

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.
Auth. Bearer API key in the 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:
Example response
Paging through every match
Filtering with 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.
Auth. Bearer API key in the 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
Example response
Errors

Delete a contact

Permanently removes the contact record. This cannot be undone.
Auth. Bearer API key in the 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
Example response
Errors 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.
Auth. Bearer API key in the 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
Example response
Importing more than 1000 rows Chunk the list and keep the reports.
Opt-out during import Imported rows whose numbers are already on the suppression list are created with 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.
Auth. Bearer API key in the 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
Example response
Clearing an opt-out
Errors 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_id or to plus a channel of imessage, rcs, or sms.
  • Subscribe to the contact.opted_out webhook so your CRM learns about stop replies without polling.
  • Pull sendable audiences with list-contacts filtered on opted_out=false before every campaign run.