curl --request PATCH \
--url https://api.paigeme.dev/v1/contacts/{id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "<string>",
"tags": [
"<string>"
],
"attributes": {}
}
'const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({name: '<string>', tags: ['<string>'], attributes: {}})
};
fetch('https://api.paigeme.dev/v1/contacts/{id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.paigeme.dev/v1/contacts/{id}"
payload = {
"name": "<string>",
"tags": ["<string>"],
"attributes": {}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text){
"success": true,
"data": {
"contact": {
"phone": "<string>",
"bsuid": "<string>",
"name": "<string>",
"id": "<unknown>",
"tags": "<unknown>",
"attributes": "<unknown>"
}
}
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}Update a contact name, tags or custom attributes
{id} is the contact phone (E.164) by default, or its BSUID with ?identifierType=bsuid. Tags accept a full-replacement array or an { add, remove } delta; attributes merge (a null value deletes the key).
At least one of name, tags or attributes is required — enforced by a cross-field rule the schema below cannot express, so an empty body is 400 invalid_request. An { add, remove } tag delta must itself carry a non-empty add or remove array.
Tag names are normalised at write time: trimmed, lower-cased, and truncated to 64 characters. Tag matching everywhere else (segments, broadcast audiences) is exact and case-sensitive, so VIP and vip are the SAME tag and are both stored as vip. Each tag array is capped at 25 entries; a longer array is 400 invalid_request.
400 TAGS_COLUMN_MISSING — the request named tags but this project’s contacts table has no tags column (a lean self-hosted backend; tags JSONB is required of a compliant one). Nothing is written, not even a name sent in the same request. A request that does not mention tags is unaffected.
Side effect: a write that touches tags publishes a contacts_changed event to the project’s dashboard subscribers, so an open Paige dashboard reflects it without a refresh. The response is unchanged, and a name- or attributes-only write publishes nothing.
Required scope: contacts:write
curl --request PATCH \
--url https://api.paigeme.dev/v1/contacts/{id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "<string>",
"tags": [
"<string>"
],
"attributes": {}
}
'const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({name: '<string>', tags: ['<string>'], attributes: {}})
};
fetch('https://api.paigeme.dev/v1/contacts/{id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.paigeme.dev/v1/contacts/{id}"
payload = {
"name": "<string>",
"tags": ["<string>"],
"attributes": {}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text){
"success": true,
"data": {
"contact": {
"phone": "<string>",
"bsuid": "<string>",
"name": "<string>",
"id": "<unknown>",
"tags": "<unknown>",
"attributes": "<unknown>"
}
}
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Invalid request body",
"details": {}
},
"request_id": "<string>"
}Authorizations
Project API key issued in Settings → API keys. Send it as Authorization: Bearer pk_live_….
Headers
Target project id, for an MCP OAuth bearer (mcp_at_…) attached to more than one project — get ids from GET /v1/projects. Matched case-insensitively.
Omit it and a READ falls back to the connection's default project; a mutation (any non-GET) on a connection with 2+ projects is rejected with 400 project_required — a write is never defaulted to a guessed project. A connection with exactly one project never needs the header.
Scopes are checked against the SELECTED project only, never a union across the connection.
For a pk_ API key the header selects nothing — one key is one project's context — but it IS validated: omit it and the key's own project is used, send it and it must name that project, otherwise the call is rejected with 403 project_not_attached (a blank value is 400 invalid_project_header, as above).
Path Parameters
Query Parameters
phone, bsuid Body
Response
Success.
true Was this page helpful?
