Contacts
Read and create contacts in a workspace owned by the installer, and manage their tags, notes and Do-Not-Disturb.
Read and create contacts in a workspace owned by the installer, and manage their tags, notes and Do-Not-Disturb.
Every route takes Authorization: Bearer <ACCESS_TOKEN> unless the auth line says otherwise. Ids are numbers. Errors use the shape on the errors page.
| Route | Scope | What it does |
|---|---|---|
GET /v1/contacts | contacts.read | List contacts the installer can see, with optional filtering and pagination. |
GET /v1/contacts/{id} | contacts.read | Fetch a single contact by id. |
POST /v1/contacts | contacts.write | Create a contact in a workspace. |
PATCH /v1/contacts/{id} | contacts.write | Partially update a contact. |
DELETE /v1/contacts/{id} | contacts.write | Soft-delete a contact. |
POST /v1/contacts/check-imessage | contacts.read | Check whether a contact, phone number or iMessage email address is on iMessage. |
POST /v1/contacts/{id}/tags | contacts.write | Add one or more tags to a contact. |
DELETE /v1/contacts/{id}/tags | contacts.write | Remove one or more tags from a contact. |
GET /v1/contacts/{id}/notes | contacts.read | A contact's notes, oldest first. |
POST /v1/contacts/{id}/notes | contacts.write | Add a note to a contact. |
DELETE /v1/contacts/{id}/notes/{note_id} | contacts.write | Delete one note. |
GET /v1/contacts/{id}/dnd | contacts.read | A contact's Do-Not-Disturb settings. |
PATCH /v1/contacts/{id}/dnd | contacts.write | Change a contact's Do-Not-Disturb for one direction. |
List contacts
GET https://api.bluereacher.com/v1/contactsAuth: Bearer access token. Scope: contacts.read.
List contacts the installer can see, with optional filtering and pagination.
Returns the contacts in one workspace owned by the authenticated installer. workspace_id is required — listing across all workspaces in a single call is not supported.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id | query | number | yes | The workspace to list contacts from. Must belong to the installer. |
limit | query | number | no | Page size — integer clamped to [1, 200]; default 50. Out-of-range or non-numeric values are normalised, NOT rejected; the applied bounds are echoed in the response pagination object (limit, max_limit, requested_limit, normalized). |
page | query | number | no | 1-based page number; default 1. Clamped to >= 1 (page=0 becomes 1 and sets normalized). Rows are ordered by id descending, so with limit 200 and total 450 pages 1–3 return the full set. The applied page is echoed in pagination.page, with total_pages and the raw requested_page. |
Request
curl -X GET "https://api.bluereacher.com/v1/contacts?workspace_id=7&limit=50&page=1" \
-H "Authorization: Bearer <ACCESS_TOKEN>"Responses
A 200 reply (OK):
{
"success": true,
"data": [
{
"id": 1234,
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"phone_number": "+15551234567",
"company_name": "Analytical Engines Ltd",
"website": "https://analyticalengines.example.com",
"linkedin_profile": "https://linkedin.com/in/ada",
"summary": "Mathematician + first programmer. Interested in early CRM tooling.",
"details": {
"source": "newsletter",
"priority": "high"
},
"address": "12 Mayfair St",
"city": "London",
"state": "Greater London",
"country": "United Kingdom",
"timezone": "Europe/London",
"custom_fields": {
"lead_score": 92,
"owner": "gary"
},
"is_verified_sms": true,
"is_verified_imessage": false,
"last_contacted": "2026-05-20T11:02:09.000Z",
"notes": [
"Spoke at conference 2026-05-15"
],
"tags": [
"lead",
"priority"
],
"workspace_id": 7,
"user_id": 42,
"created_at": "2026-05-21T09:14:22.000Z",
"updated_at": "2026-05-21T09:14:22.000Z",
"deleted_at": null
}
],
"pagination": {
"total": 1,
"page": 1,
"limit": 50,
"max_limit": 200,
"total_pages": 1,
"requested_page": null,
"requested_limit": null,
"normalized": false
}
}| Status | When | Message |
|---|---|---|
| 200 | OK | |
| 400 | Missing required parameter | Missing required parameter: workspace_id |
| 401 | Missing or invalid token | Invalid or expired access token. |
| 403 | Missing scope | Missing scope(s): contacts.read. This integration was granted: … |
| 404 | workspace_id is not accessible to this token | Workspace not found or not accessible to this token |
Get contact
GET https://api.bluereacher.com/v1/contacts/{id}Auth: Bearer access token. Scope: contacts.read.
Fetch a single contact by id.
Returns the full contact row including tags, address fields, custom fields, and audit timestamps. The contact must be owned by the authenticated installer — otherwise a 404 is returned to avoid leaking IDs.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | number | yes | Contact id returned by List contacts or Create contact. |
Request
curl -X GET "https://api.bluereacher.com/v1/contacts/1234" \
-H "Authorization: Bearer <ACCESS_TOKEN>"Responses
A 200 reply (OK):
{
"success": true,
"data": {
"id": 1234,
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"phone_number": "+15551234567",
"company_name": "Analytical Engines Ltd",
"website": "https://analyticalengines.example.com",
"linkedin_profile": "https://linkedin.com/in/ada",
"summary": "Mathematician + first programmer. Interested in early CRM tooling.",
"details": {
"source": "newsletter",
"priority": "high"
},
"address": "12 Mayfair St",
"city": "London",
"state": "Greater London",
"country": "United Kingdom",
"timezone": "Europe/London",
"custom_fields": {
"lead_score": 92,
"owner": "gary"
},
"is_verified_sms": true,
"is_verified_imessage": false,
"last_contacted": "2026-05-20T11:02:09.000Z",
"notes": [
"Spoke at conference 2026-05-15"
],
"tags": [
"lead",
"priority"
],
"workspace_id": 7,
"user_id": 42,
"created_at": "2026-05-21T09:14:22.000Z",
"updated_at": "2026-05-21T09:14:22.000Z",
"deleted_at": null
}
}| Status | When | Message |
|---|---|---|
| 200 | OK | |
| 404 | Contact does not exist or belongs to a different installer | Contact not found |
| 401 | Missing or invalid token | Invalid or expired access token. |
Response fields
contact: The contact requested. Fields:
| Field | Type | Description |
|---|---|---|
id | number | Unique identifier of the contact. Example: 1234 |
workspace_id | number | The workspace this contact lives in. Example: 7 |
user_id | number | The installer this contact belongs to. Example: 42 |
first_name | string, nullable | Given name. Max 50 chars. Example: Ada |
last_name | string, nullable | Family name. Max 50 chars. Example: Lovelace |
email | string, nullable | Primary email. Unique within a workspace. Normalized to lowercase. Max 100 chars. Example: ada@example.com |
phone_number | string, nullable | Primary phone number. Unique within a workspace. Max 20 chars. Example: +15551234567 |
company_name | string, nullable | Company / organization name. Max 100 chars. Example: Analytical Engines Ltd |
website | string, nullable | Public website URL. Max 255 chars. Example: https://analyticalengines.example.com |
linkedin_profile | string, nullable | LinkedIn profile URL. Max 255 chars. Example: https://linkedin.com/in/ada |
summary | string (text), nullable | Free-form summary / bio. |
details | object, nullable | Loosely-typed details bag (JSON). Use this for app-specific metadata that doesn't fit a column. |
address | string, nullable | Street address. Max 255 chars. |
city | string, nullable | City. Max 100 chars. |
state | string, nullable | State / region. Max 100 chars. |
country | string, nullable | Country name. Max 100 chars. |
timezone | string, nullable | IANA timezone (e.g. Europe/London). Max 100 chars. |
custom_fields | object, nullable | Workspace-defined custom fields (JSON). |
tags | array<string>, nullable | Up to 20 short labels. |
notes | array<string>, nullable | Free-form notes. Accepts a string or an array on write; reads always return an array. |
is_verified_sms | boolean, nullable | Example: false |
is_verified_imessage | boolean, nullable | Phone number has been verified for iMessage. Example: false |
last_contacted | string (ISO 8601), nullable | When the contact last had a message exchange. |
created_at | string (ISO 8601) | When the contact was first created. Example: 2026-05-21T09:14:22.000Z |
updated_at | string (ISO 8601) | When the contact was last modified. Example: 2026-05-21T09:14:22.000Z |
deleted_at | string (ISO 8601), nullable | Set when the contact has been soft-deleted. null for active records. |
Create contact
POST https://api.bluereacher.com/v1/contactsAuth: Bearer access token. Scope: contacts.write.
Create a contact in a workspace.
workspace_id is required and must belong to the installer.
⚠️ At least one of email or phone_number is required. Sending both omitted returns 400 Either email or phone_number is required. The two fields each have their own uniqueness constraint inside a workspace, so a contact is always identifiable by at least one of them.
Every other field is optional — see the response Schema tab for the full row that comes back on success.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id | body | number | yes | Workspace to attach the contact to. Must belong to the installer. |
first_name | body | string | no | Given name. Max 50 chars. |
last_name | body | string | no | Family name. Max 50 chars. |
email | body | string | no | Conditionally required — at least one of email or phone_number must be provided. Normalized to lowercase. Max 100 chars. |
phone_number | body | string | no | Conditionally required — at least one of email or phone_number must be provided. Max 20 chars. |
company_name | body | string | no | Company / organization. Max 100 chars. |
website | body | string | no | Public website URL. Max 255 chars. |
linkedin_profile | body | string | no | LinkedIn profile URL. Max 255 chars. |
summary | body | string (text) | no | Free-form summary / bio. |
details | body | object | no | Loosely-typed JSON details bag. |
address | body | string | no | Street address. Max 255 chars. |
city | body | string | no | Max 100 chars. |
state | body | string | no | Max 100 chars. |
country | body | string | no | Max 100 chars. |
timezone | body | string | no | IANA timezone, e.g. Europe/London. |
custom_fields | body | object | no | Workspace-defined custom fields (JSON). |
tags | body | array<string> | no | Up to 20 strings, each max 64 chars. |
notes | body | string | array<string> | no | Free-form notes. Accepts a single string or an array of strings. |
is_verified_sms | body | boolean | no | |
is_verified_imessage | body | boolean | no | Phone has been verified for iMessage. |
last_contacted | body | string (ISO 8601) | no | When this contact was last contacted. |
Request
curl -X POST "https://api.bluereacher.com/v1/contacts" \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"workspace_id": 7,
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"phone_number": "+15551234567",
"company_name": "Analytical Engines Ltd",
"website": "https://analyticalengines.example.com",
"linkedin_profile": "https://linkedin.com/in/ada",
"summary": "Mathematician + first programmer. Interested in early CRM tooling.",
"details": {
"source": "newsletter",
"priority": "high"
},
"address": "12 Mayfair St",
"city": "London",
"state": "Greater London",
"country": "United Kingdom",
"timezone": "Europe/London",
"custom_fields": {
"lead_score": 92,
"owner": "gary"
},
"tags": [
"lead",
"priority",
"engineer"
],
"notes": [
"Spoke at conference 2026-05-15",
"Wants a quote on the Pro tier"
],
"is_verified_sms": true,
"is_verified_imessage": false,
"last_contacted": "2026-05-20T11:02:09.000Z"
}'Responses
A 201 reply (Created):
{
"success": true,
"data": {
"id": 1234,
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"phone_number": "+15551234567",
"company_name": "Analytical Engines Ltd",
"website": "https://analyticalengines.example.com",
"linkedin_profile": "https://linkedin.com/in/ada",
"summary": "Mathematician + first programmer. Interested in early CRM tooling.",
"details": {
"source": "newsletter",
"priority": "high"
},
"address": "12 Mayfair St",
"city": "London",
"state": "Greater London",
"country": "United Kingdom",
"timezone": "Europe/London",
"custom_fields": {
"lead_score": 92,
"owner": "gary"
},
"is_verified_sms": true,
"is_verified_imessage": false,
"last_contacted": "2026-05-20T11:02:09.000Z",
"notes": [
"Spoke at conference 2026-05-15"
],
"tags": [
"lead",
"priority"
],
"workspace_id": 7,
"user_id": 42,
"created_at": "2026-05-21T09:14:22.000Z",
"updated_at": "2026-05-21T09:14:22.000Z",
"deleted_at": null
}
}| Status | When | Message |
|---|---|---|
| 201 | Created | |
| 400 | Missing required fields | Either email or phone_number is required |
| 404 | workspace_id not accessible to this token | Workspace not found or not accessible to this token |
| 409 | Duplicate contact in this workspace | A contact with this email or phone number already exists in this workspace. |
Response fields
contact: The newly created contact. Fields:
| Field | Type | Description |
|---|---|---|
id | number | Unique identifier of the contact. Example: 1234 |
workspace_id | number | The workspace this contact lives in. Example: 7 |
user_id | number | The installer this contact belongs to. Example: 42 |
first_name | string, nullable | Given name. Max 50 chars. Example: Ada |
last_name | string, nullable | Family name. Max 50 chars. Example: Lovelace |
email | string, nullable | Primary email. Unique within a workspace. Normalized to lowercase. Max 100 chars. Example: ada@example.com |
phone_number | string, nullable | Primary phone number. Unique within a workspace. Max 20 chars. Example: +15551234567 |
company_name | string, nullable | Company / organization name. Max 100 chars. Example: Analytical Engines Ltd |
website | string, nullable | Public website URL. Max 255 chars. Example: https://analyticalengines.example.com |
linkedin_profile | string, nullable | LinkedIn profile URL. Max 255 chars. Example: https://linkedin.com/in/ada |
summary | string (text), nullable | Free-form summary / bio. |
details | object, nullable | Loosely-typed details bag (JSON). Use this for app-specific metadata that doesn't fit a column. |
address | string, nullable | Street address. Max 255 chars. |
city | string, nullable | City. Max 100 chars. |
state | string, nullable | State / region. Max 100 chars. |
country | string, nullable | Country name. Max 100 chars. |
timezone | string, nullable | IANA timezone (e.g. Europe/London). Max 100 chars. |
custom_fields | object, nullable | Workspace-defined custom fields (JSON). |
tags | array<string>, nullable | Up to 20 short labels. |
notes | array<string>, nullable | Free-form notes. Accepts a string or an array on write; reads always return an array. |
is_verified_sms | boolean, nullable | Example: false |
is_verified_imessage | boolean, nullable | Phone number has been verified for iMessage. Example: false |
last_contacted | string (ISO 8601), nullable | When the contact last had a message exchange. |
created_at | string (ISO 8601) | When the contact was first created. Example: 2026-05-21T09:14:22.000Z |
updated_at | string (ISO 8601) | When the contact was last modified. Example: 2026-05-21T09:14:22.000Z |
deleted_at | string (ISO 8601), nullable | Set when the contact has been soft-deleted. null for active records. |
Update contact
PATCH https://api.bluereacher.com/v1/contacts/{id}Auth: Bearer access token. Scope: contacts.write.
Partially update a contact.
Send any subset of the writable fields. Fields you omit are left untouched. Send an explicit null (or empty string) to clear a field. Moving the contact to a different workspace is allowed, but the new workspace_id must also belong to the installer.
Writable fields: the full set listed below — same fields that Create Contact accepts (except neither email nor phone_number is conditionally required on PATCH, since the contact already has at least one identifier from create-time).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | number | yes | Contact id. |
workspace_id | body | number | no | Move the contact to a different workspace. Must belong to the installer. |
first_name | body | string | no | Max 50. |
last_name | body | string | no | Max 50. |
email | body | string | no | Normalized to lowercase. Max 100. |
phone_number | body | string | no | Max 20. |
company_name | body | string | no | Max 100. |
address | body | string | no | Max 255. |
city | body | string | no | Max 100. |
state | body | string | no | Max 100. |
country | body | string | no | Max 100. |
tags | body | array<string> | no | Replaces the existing tags. Pass null or [] to clear. |
website | body | string | no | Public website URL. Max 255 chars. Send null to clear. |
linkedin_profile | body | string | no | LinkedIn profile URL. Max 255 chars. Send null to clear. |
summary | body | string (text) | no | Free-form summary / bio. Send null to clear. |
details | body | object | no | Loosely-typed JSON details bag. Send null to clear. |
timezone | body | string | no | IANA timezone, e.g. Europe/London. Max 100 chars. |
custom_fields | body | object | no | Workspace-defined custom fields (JSON). Send null to clear. |
notes | body | string | array<string> | no | Free-form notes. Accepts a single string or an array of strings. |
is_verified_sms | body | boolean | no | |
is_verified_imessage | body | boolean | no | Phone has been verified for iMessage. |
last_contacted | body | string (ISO 8601) | no | When this contact was last contacted. |
Request
curl -X PATCH "https://api.bluereacher.com/v1/contacts/1234" \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Augusta",
"tags": [
"lead",
"priority",
"engineer"
]
}'Responses
A 200 reply (Updated):
{
"success": true,
"data": {
"id": 1234,
"first_name": "Augusta",
"last_name": "Lovelace",
"email": "ada@example.com",
"phone_number": "+15551234567",
"company_name": "Analytical Engines Ltd",
"website": "https://analyticalengines.example.com",
"linkedin_profile": "https://linkedin.com/in/ada",
"summary": "Mathematician + first programmer. Interested in early CRM tooling.",
"details": {
"source": "newsletter",
"priority": "high"
},
"address": "12 Mayfair St",
"city": "London",
"state": "Greater London",
"country": "United Kingdom",
"timezone": "Europe/London",
"custom_fields": {
"lead_score": 92,
"owner": "gary"
},
"is_verified_sms": true,
"is_verified_imessage": false,
"last_contacted": "2026-05-20T11:02:09.000Z",
"notes": [
"Spoke at conference 2026-05-15"
],
"tags": [
"lead",
"priority",
"engineer"
],
"workspace_id": 7,
"user_id": 42,
"created_at": "2026-05-21T09:14:22.000Z",
"updated_at": "2026-05-21T09:14:22.000Z",
"deleted_at": null
}
}| Status | When | Message |
|---|---|---|
| 200 | Updated | |
| 400 | No editable fields provided | No editable fields provided. |
| 404 | Contact does not exist or is not owned by the installer | Contact not found |
| 409 | New email or phone collides with another contact in the same workspace | Another contact with this email or phone number already exists in this workspace. |
Response fields
contact: The updated contact. Fields:
| Field | Type | Description |
|---|---|---|
id | number | Unique identifier of the contact. Example: 1234 |
workspace_id | number | The workspace this contact lives in. Example: 7 |
user_id | number | The installer this contact belongs to. Example: 42 |
first_name | string, nullable | Given name. Max 50 chars. Example: Ada |
last_name | string, nullable | Family name. Max 50 chars. Example: Lovelace |
email | string, nullable | Primary email. Unique within a workspace. Normalized to lowercase. Max 100 chars. Example: ada@example.com |
phone_number | string, nullable | Primary phone number. Unique within a workspace. Max 20 chars. Example: +15551234567 |
company_name | string, nullable | Company / organization name. Max 100 chars. Example: Analytical Engines Ltd |
website | string, nullable | Public website URL. Max 255 chars. Example: https://analyticalengines.example.com |
linkedin_profile | string, nullable | LinkedIn profile URL. Max 255 chars. Example: https://linkedin.com/in/ada |
summary | string (text), nullable | Free-form summary / bio. |
details | object, nullable | Loosely-typed details bag (JSON). Use this for app-specific metadata that doesn't fit a column. |
address | string, nullable | Street address. Max 255 chars. |
city | string, nullable | City. Max 100 chars. |
state | string, nullable | State / region. Max 100 chars. |
country | string, nullable | Country name. Max 100 chars. |
timezone | string, nullable | IANA timezone (e.g. Europe/London). Max 100 chars. |
custom_fields | object, nullable | Workspace-defined custom fields (JSON). |
tags | array<string>, nullable | Up to 20 short labels. |
notes | array<string>, nullable | Free-form notes. Accepts a string or an array on write; reads always return an array. |
is_verified_sms | boolean, nullable | Example: false |
is_verified_imessage | boolean, nullable | Phone number has been verified for iMessage. Example: false |
last_contacted | string (ISO 8601), nullable | When the contact last had a message exchange. |
created_at | string (ISO 8601) | When the contact was first created. Example: 2026-05-21T09:14:22.000Z |
updated_at | string (ISO 8601) | When the contact was last modified. Example: 2026-05-21T09:14:22.000Z |
deleted_at | string (ISO 8601), nullable | Set when the contact has been soft-deleted. null for active records. |
Delete contact
DELETE https://api.bluereacher.com/v1/contacts/{id}Auth: Bearer access token. Scope: contacts.write.
Soft-delete a contact.
The contact is soft-deleted — it stops appearing in API results and behaves exactly like one that never existed: a subsequent GET / PATCH / DELETE on its id returns 404 Not Found. Contact Blue Reacher support if you need a contact permanently removed.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | number | yes | Contact id. |
Request
curl -X DELETE "https://api.bluereacher.com/v1/contacts/1234" \
-H "Authorization: Bearer <ACCESS_TOKEN>"Responses
A 200 reply (Deleted):
{
"success": true,
"message": "Contact deleted.",
"data": {
"id": 1234,
"deleted": true
}
}| Status | When | Message |
|---|---|---|
| 200 | Deleted | |
| 404 | Contact does not exist or is not owned by the installer | Contact not found |
Check iMessage
POST https://api.bluereacher.com/v1/contacts/check-imessageAuth: Bearer access token. Scope: contacts.read.
Limits: 10 checks / minute per line
Check whether a contact, phone number or iMessage email address is on iMessage.
Asks Apple, through the workspace's line, whether the handle is registered for iMessage — the same check as Check iMessage on a app.bluereacher.com contact. Give a contact_id (its phone, else its email, is checked) or a phone (with country code) or an email. The answer is recorded, so the app.bluereacher.com and later sends use it.
Needs a line with Advanced Mode on in the workspace (else 400 NO_ADVANCED_MODE_LINE). Apple rate-limits these lookups: each line takes 10 checks a minute (429 RATE_LIMITED beyond that). A handle Apple has not seen recently can take 10–20 s to answer; after 26 s the request is a 504 LINE_TIMEOUT — try again.
silenced and focus_status say whether the recipient has notifications silenced by a Focus, when the line could tell (null otherwise) — see Notify anyway.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
workspace_id | body | number | yes | The workspace whose line does the check. Must belong to the installer. |
contact_id | body | number | no | A contact in the workspace. Provide this, phone or email. |
phone | body | string | no | A phone number with country code. An iMessage email address is accepted here too. |
email | body | string | no | An iMessage email address. |
Request
curl -X POST "https://api.bluereacher.com/v1/contacts/check-imessage" \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"workspace_id": 7,
"contact_id": 1234
}'Responses
A 200 reply (OK):
{
"success": true,
"data": {
"handle": "+61412345678",
"available": true,
"status": "available",
"silenced": false,
"focus_status": null,
"contact_id": 1234
}
}| Status | When | Message |
|---|---|---|
| 200 | OK | |
| 400 | No contact_id, phone or email | Provide contact_id, phone or email. |
| 400 | Phone without a country code | phone must include the country code, e.g. +14155550123 |
| 400 | No line with Advanced Mode on the workspace | No line with Advanced Mode is connected to this workspace. iMessage checks need a line with Advanced Mode on. |
| 401 | Missing or invalid token | Invalid or expired access token. |
| 403 | Missing scope | Missing scope(s): contacts.read. This integration was granted: … |
| 404 | Workspace or contact not found | Contact not found in this workspace |
| 429 | More than 10 checks in a minute on this line | Too many checks in the last minute. Try again shortly. |
| 503 | The line is not connected | The line isn't connected right now |
| 504 | The line did not answer in time | The line didn't answer in time. Try again. |
Response fields
check: Apple's answer for the handle. Fields:
| Field | Type | Description |
|---|---|---|
handle | string | The phone or email checked. Example: +61412345678 |
available | boolean | true when the handle is on iMessage. Example: true |
status | string, nullable | Apple's raw answer, when the line reports one. Example: available |
silenced | boolean, nullable | true when the recipient has notifications silenced by a Focus; null when unknown. |
focus_status | string, nullable | The recipient's Focus state as Messages reports it; null when unknown. |
contact_id | number, nullable | The contact checked, when contact_id was given. |
Add tags
POST https://api.bluereacher.com/v1/contacts/{id}/tagsAuth: Bearer access token. Scope: contacts.write.
Add one or more tags to a contact.
Tags already on the contact are kept; adding one it has is a no-op. Up to 50 tags per call, 100 characters each. Adding a tag can start workflows that trigger on it, as in the dashboard.
AI agent tags. A contact can carry one AI agent tag. Adding a different one answers 409 agent_tag_conflict; send agent_tag_resolution "replace" to swap it or "skip" to keep the old one.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | number | yes | The contact id. |
tags | body | array<string> | yes | Tag names. |
agent_tag_resolution | body | enum | no | Only when the request adds an AI agent tag and the contact has a different one. |
Request
curl -X POST "https://api.bluereacher.com/v1/contacts/1234/tags" \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"tags": [
"vip",
"demo-booked"
]
}'Responses
A 200 reply (OK — the contact's tags now):
{
"success": true,
"message": "Tags added",
"data": {
"contact_id": 1234,
"tags": [
"lead",
"vip",
"demo-booked"
]
}
}| Status | When | Message |
|---|---|---|
| 200 | OK — the contact's tags now | |
| 400 | No tags | tags is required — an array of tag names. |
| 401 | Missing or invalid token | Invalid or expired access token. |
| 403 | Missing scope | Missing scope(s): contacts.write. This integration was granted: … |
| 404 | Contact does not exist or is not owned by the installer (same response for both) | Contact not found or not accessible to this token |
| 409 | A different AI agent tag is already on the contact | This contact already has a different AI agent tag (a contact can carry one). Send agent_tag_resolution "replace" to swap it, or "skip" to keep it. |
Remove tags
DELETE https://api.bluereacher.com/v1/contacts/{id}/tagsAuth: Bearer access token. Scope: contacts.write.
Remove one or more tags from a contact.
Send tags in the JSON body, or as a comma-separated tags query parameter. Tags the contact does not have are ignored.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | number | yes | The contact id. |
tags | body | array<string> | yes | Tag names (or ?tags=a,b). |
Request
curl -X DELETE "https://api.bluereacher.com/v1/contacts/1234/tags" \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"tags": [
"demo-booked"
]
}'Responses
A 200 reply (OK):
{
"success": true,
"message": "Tags removed",
"data": {
"contact_id": 1234,
"tags": [
"lead",
"vip"
]
}
}| Status | When | Message |
|---|---|---|
| 200 | OK | |
| 401 | Missing or invalid token | Invalid or expired access token. |
| 403 | Missing scope | Missing scope(s): contacts.write. This integration was granted: … |
| 404 | Contact does not exist or is not owned by the installer (same response for both) | Contact not found or not accessible to this token |
List notes
GET https://api.bluereacher.com/v1/contacts/{id}/notesAuth: Bearer access token. Scope: contacts.read.
A contact's notes, oldest first.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | number | yes | The contact id. |
Request
curl -X GET "https://api.bluereacher.com/v1/contacts/1234/notes" \
-H "Authorization: Bearer <ACCESS_TOKEN>"Responses
A 200 reply (OK):
{
"success": true,
"data": {
"contact_id": 1234,
"notes": [
{
"id": "1761555000000",
"text": "Prefers iMessage after 5pm",
"created_at": "2026-10-27T08:50:00.000Z"
}
]
}
}| Status | When | Message |
|---|---|---|
| 200 | OK | |
| 401 | Missing or invalid token | Invalid or expired access token. |
| 403 | Missing scope | Missing scope(s): contacts.read. This integration was granted: … |
| 404 | Contact does not exist or is not owned by the installer (same response for both) | Contact not found or not accessible to this token |
Add note
POST https://api.bluereacher.com/v1/contacts/{id}/notesAuth: Bearer access token. Scope: contacts.write.
Add a note to a contact.
A note is up to 2000 characters. All of a contact's notes together are capped at 5000 characters; a note that would go over is refused with 409 rather than cut.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | number | yes | The contact id. |
text | body | string | yes | The note. |
Request
curl -X POST "https://api.bluereacher.com/v1/contacts/1234/notes" \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"text": "Prefers iMessage after 5pm"
}'Responses
A 201 reply (Created):
{
"success": true,
"message": "Note added",
"data": {
"id": "1761555000000",
"text": "Prefers iMessage after 5pm",
"created_at": "2026-10-27T08:50:00.000Z"
}
}| Status | When | Message |
|---|---|---|
| 201 | Created | |
| 400 | No text | text is required. |
| 401 | Missing or invalid token | Invalid or expired access token. |
| 403 | Missing scope | Missing scope(s): contacts.write. This integration was granted: … |
| 404 | Contact does not exist or is not owned by the installer (same response for both) | Contact not found or not accessible to this token |
| 409 | The contact's notes are full | This contact's notes are full (5000 characters in all). Delete an older note or shorten this one. |
Delete note
DELETE https://api.bluereacher.com/v1/contacts/{id}/notes/{note_id}Auth: Bearer access token. Scope: contacts.write.
Delete one note.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | number | yes | The contact id. |
note_id | path | string | yes | The note id from List notes. |
Request
curl -X DELETE "https://api.bluereacher.com/v1/contacts/1234/notes/1761555000000" \
-H "Authorization: Bearer <ACCESS_TOKEN>"Responses
A 200 reply (OK):
{
"success": true,
"message": "Note deleted"
}| Status | When | Message |
|---|---|---|
| 200 | OK | |
| 401 | Missing or invalid token | Invalid or expired access token. |
| 403 | Missing scope | Missing scope(s): contacts.write. This integration was granted: … |
| 404 | Note does not exist or is not owned by the installer (same response for both) | Note not found or not accessible to this token |
Get DND
GET https://api.bluereacher.com/v1/contacts/{id}/dndAuth: Bearer access token. Scope: contacts.read.
A contact's Do-Not-Disturb settings.
Outbound DND stops sends to the contact on those channels — a send answers 409 with reason: "dnd"; inbound DND stops the AI agent and automations from reacting to the contact's messages there. Same settings as the contact panel and the "Set Contact DND" automation action.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | number | yes | The contact id. |
Request
curl -X GET "https://api.bluereacher.com/v1/contacts/1234/dnd" \
-H "Authorization: Bearer <ACCESS_TOKEN>"Responses
A 200 reply (OK):
{
"success": true,
"data": {
"contact_id": 1234,
"workspace_id": 7,
"dnd": {
"outbound": {
"all": false,
"channels": []
},
"inbound": {
"all": false,
"channels": []
}
}
}
}| Status | When | Message |
|---|---|---|
| 200 | OK | |
| 401 | Missing or invalid token | Invalid or expired access token. |
| 403 | Missing scope | Missing scope(s): contacts.read. This integration was granted: … |
| 404 | Contact does not exist or is not owned by the installer (same response for both) | Contact not found or not accessible to this token |
Set DND
PATCH https://api.bluereacher.com/v1/contacts/{id}/dndAuth: Bearer access token. Scope: contacts.write.
Change a contact's Do-Not-Disturb for one direction.
One call changes one direction. enable_all / disable_all set or clear DND on every channel; enable_specific adds the named channels (and narrows an "all channels" DND to just those); disable_specific removes them. Automations that trigger on Contact Updated run when the settings actually change, as in the dashboard. The response is the contact's full DND afterwards.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | number | yes | The contact id. |
direction | body | enum | yes | Which direction to change. |
status | body | enum | yes | What to do. |
channels | body | array<string> | no |
Request
curl -X PATCH "https://api.bluereacher.com/v1/contacts/1234/dnd" \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"direction": "outbound",
"status": "enable_specific",
"channels": []
}'Responses
A 200 reply (OK — the contact's DND now):
{
"success": true,
"message": "Contact DND updated",
"data": {
"contact_id": 1234,
"workspace_id": 7,
"dnd": {
"outbound": {
"all": false,
"channels": []
},
"inbound": {
"all": false,
"channels": []
}
}
}
}| Status | When | Message |
|---|---|---|
| 200 | OK — the contact's DND now | |
| 400 | Bad direction, status or channel | channels is required for enable_specific. |
| 401 | Missing or invalid token | Invalid or expired access token. |
| 403 | Missing scope | Missing scope(s): contacts.write. This integration was granted: … |
| 404 | Contact does not exist or is not owned by the installer (same response for both) | Contact not found or not accessible to this token |

