Update Customer
Update an existing customer in Customerscore.io.
For interactive API documentation, see the Swagger reference (opens in a new tab).
Endpoint
| Method | URL |
|---|---|
| PUT | https://api.customerscore.io/api/v1/customer/{customerExternalId} |
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
customerExternalId | string | Yes | Customer external ID |
Request Body
All fields in the request body are optional. Only include the fields you want to update.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Customer name |
is_churned | boolean | No | Flag indicating if the customer has churned |
is_unsubscribed | boolean | No | Flag indicating if the customer is unsubscribed |
churned_date | string | No | Date when the customer churned in ISO8601 format (YYYY-MM-DD) |
owner_email | string | No | Owner email address. If provided, updates the customer owner to the user with this email |
parent_external_id | string | null | No | external_id of the parent company. See Setting the parent company |
attributes | object | No | Attribute values keyed by property codename. See Updating attributes |
Setting the parent company
parent_external_id places the customer under another customer in the company hierarchy — the parent/subsidiary tree shown on the Hierarchy tab of a customer's detail. It holds the external_id of the parent, not its name or internal ID.
{
"parent_external_id": "acme-group"
}| Value sent | Result |
|---|---|
| field omitted | The current parent company is left untouched |
"some-external-id" | That customer becomes the parent company |
null or "" | The parent company is removed |
The request fails with 400 and nothing is written when the link cannot be used:
parent_external_idmatches no customer in your account- it is the customer itself
- it would create a cycle (A under B while B is already under A)
- the resulting chain would be deeper than 10 levels
Good to know
- Only companies participate. A contact is never accepted as a parent company, even when it shares an
external_idwith a customer. - A hierarchy synced from a CRM wins. If your account syncs company hierarchy from HubSpot, the next sync overwrites what you set here — exactly like an attribute fed by a data source.
- To link many customers at once, use
parent_external_idon the customers upload endpoint instead of one request per customer.
Updating attributes
Attributes are addressed by their codename (the identifier you set when creating the attribute in Settings → Attributes), not by name or ID.
{
"attributes": {
"plan": "enterprise",
"renewal_date": "2027-03-31",
"seats": 250
}
}Which attributes can be updated
Attributes can be updated regardless of their data source — the API always takes precedence. This includes attributes fed by another source (HubSpot, Stripe, a file import, …). The attribute keeps its data source, so the next sync from that source overwrites the value again.
These are rejected:
- Metrics — metrics are time-series values and are written via the customers upload endpoint
- Calculated attributes — they are derived from other attributes and are recalculated automatically
- Contact properties — use the contact endpoint instead
All-or-nothing
Validation covers the whole request. If any codename is unknown or any value does not match its attribute type, the request fails with 400 and nothing is written — neither the other attributes nor the core fields in the same request.
Value formats
| Attribute type | Accepted JSON value |
|---|---|
STRING | string |
NUMBER | number (not a numeric string) |
BOOL | boolean (not "true" / "false") |
DATE | string, ISO 8601 YYYY-MM-DD, or a Unix timestamp in seconds or milliseconds (the time is dropped, only the day is kept) |
TIMESTAMP | Unix timestamp in seconds or milliseconds (number or numeric string), or an ISO 8601 string such as 2026-12-22T10:30:00Z; stored with hours and minutes in your account's timezone (an ISO string without an offset is read as already being in that timezone) |
URL | string |
EMAIL | string, a valid email address |
Send null to clear a value. The attribute keeps existing on the customer with an empty value; it is not removed.
Good to know
- The next import overwrites a manually set value. This endpoint edits the current value; it does not pin it. When the attribute's data source (if other than API) syncs again, whatever that source reports wins.
- Health and fit scores are not recalculated. The new value is used from the next data snapshot onwards.
- The change is recorded in the customer's attribute change log and can trigger Attribute change alerts on the next alert run.
nameandattributes.namerefer to the same attribute, so sending both with different values is rejected.parent_external_idis validated before anything else is written, so a rejected link leaves the rest of the request unapplied too.
Example Request
curl -X PUT "https://api.customerscore.io/api/v1/customer/12345" \
-H "Content-Type: application/json" \
-H "customerscore-key: your-generated-key" \
-d '{
"name": "Acme Corporation",
"owner_email": "[email protected]",
"attributes": {
"plan": "enterprise",
"renewal_date": "2027-03-31",
"seats": 250
}
}'Response Codes
| Code | Description |
|---|---|
| 200 | The customer has been successfully updated |
| 400 | Bad request - malformed body, unknown or ineligible attribute codename, a value of the wrong type, or an unusable parent_external_id |
| 401 | Unauthorized - Missing customerscore-key in the header |
| 403 | Forbidden - Invalid key |
| 404 | Customer not found |
| 405 | Method Not Allowed - Only PUT method is allowed |
| 406 | Not Acceptable |
| 490 | Unconfigured scoring - the account has no scoring profile set up yet |
| 491 | Account blocked |