Customer upload
Import customer data into Customerscore.io via the Customers API endpoint.
For interactive API documentation, see the Swagger reference (opens in a new tab).
Endpoint
| Method | URL |
|---|---|
| POST | https://api.customerscore.io/api/v1/customers |
Request Body
The customers field is mandatory in the JSON body. Each customer object within the customers array must include the following attributes:
| Field | Type | Required | Description |
|---|---|---|---|
external_id | string | Yes | Identifier for the customer, max 255 characters |
name | string | Yes | Name of the customer, max 255 characters |
owner_email | string | No | Owner's email address, max 255 characters |
parent_external_id | string | No | external_id of the parent company, max 255 characters. See Parent company field |
attributes | object | No | Customer attributes object |
metrics | array | No | Array of metric objects (up to 200 items) |
Example Request
curl -X POST "https://api.customerscore.io/api/v1/customers" \
-H "Content-Type: application/json" \
-H "customerscore-key: your-generated-key" \
-d '{
"customers": [
{
"external_id": "12345",
"name": "Knapp-Cruz Inc.",
"owner_email": "[email protected]",
"attributes": {
"crm_link": "https://your-crm.com/12345",
"contact_email": "[email protected]",
"renewal_date": "2025-05-25"
},
"metrics": [
{
"date": "2024-04-29",
"resolved_tickets": 39
}
]
}
]
}'Full Request Body Example
{
"customers": [
{
"external_id": "12345",
"name": "Knapp-Cruz Inc.",
"owner_email": "[email protected]",
"parent_external_id": "67890",
"attributes": {
"crm_link": "https://example.com",
"contact_email": "[email protected]",
"renewal_date": "2024-05-25",
"country_name": "Czech Republic",
"customers_count": 99,
"has_avatar": true
},
"metrics": [
{
"date": "2024-04-29",
"country": "Czechia",
"has_picture": true,
"resolved_tickets": 39,
"created_at": "2024-08-15"
}
]
}
]
}Batch Size
We strongly recommend sending customers in batches rather than one by one. Each request can include up to 100 customers, and the request body is limited to 5 MB.
For best performance and reliability, we recommend:
- 50–100 customers per request
- Request body size below 5 MB
Batching reduces network overhead and helps you stay within the rate limit. A request with more than 100 customers is rejected with 400 Bad Request and none of its customers are imported — split larger datasets into multiple requests.
Owner Email Field
If owner_email is provided, the customer will be automatically linked to the user in your account (if such a user exists).
Parent Company Field
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.
{
"customers": [
{ "external_id": "acme-group", "name": "Acme Group" },
{ "external_id": "acme-cz", "name": "Acme Czechia", "parent_external_id": "acme-group" },
{ "external_id": "acme-sk", "name": "Acme Slovakia", "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 |
Good to know
- The parent may be created by the same request. Links are resolved after all customers in the payload have been created, so a group and its subsidiaries can be uploaded in one call — in any order.
- Links are applied on a best-effort basis. The upload is processed asynchronously, so a link that cannot be used does not fail the request. It is skipped and recorded as a warning on the import, visible under Settings → Imports (opens in a new tab). This happens when:
parent_external_idmatches no customer in your account- the customer points at itself
- the link would create a cycle (A under B while B is already under A)
- the resulting chain would be deeper than 10 levels
- Only companies participate. Contacts are never picked up as a parent or a child, even when they share 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.
Attributes Field
The attributes field is an object that contains useful information about a given customer. You can add:
- System attributes:
crm_link,contact_email,renewal_date - Custom attributes: Configured in the Customerscore.io application under Attributes (opens in a new tab)
Example Attributes Object
{
"crm_link": "https://your-crm-link.com/12345",
"contact_email": "[email protected]",
"renewal_date": "2025-05-25",
"country_name": "Czech Republic",
"customers_count": 99,
"has_avatar": true
}Supported Value Types
| Type | Example |
|---|---|
| String | "abcd" |
| Number | 1234 |
| Boolean | true or false |
| Date | "2024-08-14" |
Each key must precisely match the codename of the attribute configured in Customerscore.io. Keys are case-sensitive. Only matching keys will be stored and used for score calculation.
Metrics Field
The metrics field contains product-usage data that feeds into the health score calculation. It can hold up to 200 items.
| Field | Type | Required | Description |
|---|---|---|---|
date | string | Yes | Date for the metric, must be unique in the array |
Example Metrics Array
[
{
"date": "2024-04-29",
"country": "Czechia",
"has_picture": true,
"resolved_tickets": 39,
"created_at": "2024-08-15"
}
]Each key must precisely match the codename of the metric configured in Metrics (opens in a new tab). Keys are case-sensitive.
Data Source Precedence
Attributes and metrics are stored regardless of their data source — the API always takes precedence. This includes properties fed by another source (HubSpot, Stripe, a file import, …). Such a property keeps its data source, so the next sync from that source overwrites the value again. A property with no data source yet is assigned the API as its data source.
Values for calculated properties are ignored — they are derived from other values and recalculated automatically. Each ignored key is recorded as a warning on the import, visible under Settings → Imports (opens in a new tab).
Processing
Upon successful data import, internal processing will occur, including a scoring process that may take up to 30 minutes. After processing is complete, all users in the account will receive a notification regarding the newly imported customer data.
If the API is your account's source of truth, every external_id that does not exist yet is created as a new customer during processing and fires the customer.created webhook (with data.source = API). Existing customers are updated silently.
Response Codes
| Code | Description |
|---|---|
| 200 | Customer data has been successfully received and forwarded for processing |
| 400 | Bad request |
| 401 | Unauthorized - Missing customerscore-key in the header |
| 403 | Forbidden - Invalid key |
| 405 | Method Not Allowed - Only POST method is allowed |
| 406 | Not Acceptable |
| 429 | Too Many Requests |
| 490 | Unconfigured scoring |