curl --request POST \
--url https://api.blujay.io/v1/contacts/import \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"phone_number": "+15555550123",
"email": "ava@example.com",
"name": "Ava Chen"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({phone_number: '+15555550123', email: 'ava@example.com', name: 'Ava Chen'})
};
fetch('https://api.blujay.io/v1/contacts/import', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.blujay.io/v1/contacts/import"
payload = {
"phone_number": "+15555550123",
"email": "ava@example.com",
"name": "Ava Chen"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.blujay.io/v1/contacts/import"
payload := strings.NewReader("{\n \"phone_number\": \"+15555550123\",\n \"email\": \"ava@example.com\",\n \"name\": \"Ava Chen\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}require 'uri'
require 'net/http'
url = URI("https://api.blujay.io/v1/contacts/import")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"phone_number\": \"+15555550123\",\n \"email\": \"ava@example.com\",\n \"name\": \"Ava Chen\"\n}"
response = http.request(request)
puts response.read_body<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.blujay.io/v1/contacts/import",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'phone_number' => '+15555550123',
'email' => 'ava@example.com',
'name' => 'Ava Chen'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}{
"status": "success",
"trace_id": "trc_8f2a1c0e4b7d",
"timestamp": "2026-10-14T17:01:00.000Z",
"data": {
"contact_id": "contact_abc123",
"tags": [
"priority"
],
"stage": "Active",
"assigned_team_member": {
"id": "member_abc123",
"email": "sam@example.com",
"name": "Sam Rivera"
},
"opted_out": true,
"messaging_allowed": true,
"created_at": "2026-10-14T17:01:00.000Z",
"updated_at": "2026-10-14T17:01:00.000Z",
"phone_number": "+15555550123",
"email": "ava@example.com",
"name": "Ava Chen",
"opted_out_at": "2026-10-14T17:01:00.000Z",
"opted_out_source": "api",
"created": true,
"updated": true,
"tag_added": true,
"stage_changed": true,
"assignment_changed": true
},
"client_request_id": "<string>"
}{
"status": "success",
"trace_id": "trc_8f2a1c0e4b7d",
"timestamp": "2026-10-14T17:01:00.000Z",
"data": {
"contact_id": "contact_abc123",
"tags": [
"priority"
],
"stage": "Active",
"assigned_team_member": {
"id": "member_abc123",
"email": "sam@example.com",
"name": "Sam Rivera"
},
"opted_out": true,
"messaging_allowed": true,
"created_at": "2026-10-14T17:01:00.000Z",
"updated_at": "2026-10-14T17:01:00.000Z",
"phone_number": "+15555550123",
"email": "ava@example.com",
"name": "Ava Chen",
"opted_out_at": "2026-10-14T17:01:00.000Z",
"opted_out_source": "api",
"created": true,
"updated": true,
"tag_added": true,
"stage_changed": true,
"assignment_changed": true
},
"client_request_id": "<string>"
}{
"status": "error",
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"type": "validation",
"http_status": 400,
"retryable": false,
"doc_url": "https://docs.blujay.io/errors/validation-error",
"details": []
},
"trace_id": "trc_8f2a1c0e4b7d",
"timestamp": "2026-10-14T00:00:00.000Z"
}{
"status": "error",
"error": {
"code": "CONTACT_MATCH_CONFLICT",
"message": "Phone number and email match different contacts",
"type": "validation",
"http_status": 409,
"retryable": false,
"doc_url": "https://docs.blujay.io/errors/contact-match-conflict",
"details": []
},
"trace_id": "trc_8f2a1c0e4b7d",
"timestamp": "2026-10-14T00:00:00.000Z"
}Create or update a contact
Creates or updates a contact by phone number. phone_number is required; email and name are optional.
- If a contact has this phone number, Blujay updates it (
200), including its email and name when you send them. - If no contact has this phone number, Blujay creates one (
201). - If you send an email that already belongs to a different contact, Blujay returns
409 CONTACT_MATCH_CONFLICTand changes nothing. This happens even when no contact has the phone number.
The response’s created and updated flags tell you which happened. Like create, import doesn’t send a webhook or start workflows that run on new contacts.
curl --request POST \
--url https://api.blujay.io/v1/contacts/import \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"phone_number": "+15555550123",
"email": "ava@example.com",
"name": "Ava Chen"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({phone_number: '+15555550123', email: 'ava@example.com', name: 'Ava Chen'})
};
fetch('https://api.blujay.io/v1/contacts/import', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.blujay.io/v1/contacts/import"
payload = {
"phone_number": "+15555550123",
"email": "ava@example.com",
"name": "Ava Chen"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.blujay.io/v1/contacts/import"
payload := strings.NewReader("{\n \"phone_number\": \"+15555550123\",\n \"email\": \"ava@example.com\",\n \"name\": \"Ava Chen\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}require 'uri'
require 'net/http'
url = URI("https://api.blujay.io/v1/contacts/import")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"phone_number\": \"+15555550123\",\n \"email\": \"ava@example.com\",\n \"name\": \"Ava Chen\"\n}"
response = http.request(request)
puts response.read_body<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.blujay.io/v1/contacts/import",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'phone_number' => '+15555550123',
'email' => 'ava@example.com',
'name' => 'Ava Chen'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}{
"status": "success",
"trace_id": "trc_8f2a1c0e4b7d",
"timestamp": "2026-10-14T17:01:00.000Z",
"data": {
"contact_id": "contact_abc123",
"tags": [
"priority"
],
"stage": "Active",
"assigned_team_member": {
"id": "member_abc123",
"email": "sam@example.com",
"name": "Sam Rivera"
},
"opted_out": true,
"messaging_allowed": true,
"created_at": "2026-10-14T17:01:00.000Z",
"updated_at": "2026-10-14T17:01:00.000Z",
"phone_number": "+15555550123",
"email": "ava@example.com",
"name": "Ava Chen",
"opted_out_at": "2026-10-14T17:01:00.000Z",
"opted_out_source": "api",
"created": true,
"updated": true,
"tag_added": true,
"stage_changed": true,
"assignment_changed": true
},
"client_request_id": "<string>"
}{
"status": "success",
"trace_id": "trc_8f2a1c0e4b7d",
"timestamp": "2026-10-14T17:01:00.000Z",
"data": {
"contact_id": "contact_abc123",
"tags": [
"priority"
],
"stage": "Active",
"assigned_team_member": {
"id": "member_abc123",
"email": "sam@example.com",
"name": "Sam Rivera"
},
"opted_out": true,
"messaging_allowed": true,
"created_at": "2026-10-14T17:01:00.000Z",
"updated_at": "2026-10-14T17:01:00.000Z",
"phone_number": "+15555550123",
"email": "ava@example.com",
"name": "Ava Chen",
"opted_out_at": "2026-10-14T17:01:00.000Z",
"opted_out_source": "api",
"created": true,
"updated": true,
"tag_added": true,
"stage_changed": true,
"assignment_changed": true
},
"client_request_id": "<string>"
}{
"status": "error",
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"type": "validation",
"http_status": 400,
"retryable": false,
"doc_url": "https://docs.blujay.io/errors/validation-error",
"details": []
},
"trace_id": "trc_8f2a1c0e4b7d",
"timestamp": "2026-10-14T00:00:00.000Z"
}{
"status": "error",
"error": {
"code": "CONTACT_MATCH_CONFLICT",
"message": "Phone number and email match different contacts",
"type": "validation",
"http_status": 409,
"retryable": false,
"doc_url": "https://docs.blujay.io/errors/contact-match-conflict",
"details": []
},
"trace_id": "trc_8f2a1c0e4b7d",
"timestamp": "2026-10-14T00:00:00.000Z"
}Authorizations
Your workspace API key, sent as Authorization: Bearer blu_.... Workspace admins can find it in the Blujay app under API.
An API key has full access to your workspace and works on every endpoint. There is no test mode: every key sends real messages and changes real data, so keep it secret and use it only from your server.
Each API key can make 600 read requests and 120 write requests per minute, plus 60 attachment requests per minute. Over a limit you get 429 RATE_LIMIT_EXCEEDED with Retry-After.
Body
Response
An existing contact was updated.
Wraps every successful response. The result is in data.
success ^trc_[A-Za-z0-9_-]+$"trc_8f2a1c0e4b7d"
"2026-10-14T17:01:00.000Z"
Show child attributes
Show child attributes
Your X-Request-ID header value, echoed back when you send one. Not included on the send responses (POST /v1/chats, POST /v1/chats/{chat_id}/messages, POST /v1/messages/{message_id}/retry); keep your own mapping from your request ID to the returned message ID there.