---
title: "Bulk Create or Update Customers"
method: POST
path: "/customers"
tags: ["Customers"]
---

# Bulk Create or Update Customers

`POST /customers`

## Request

- Base URL: `https://api.smartroutes.io/v2`
- URL: `https://api.smartroutes.io/v2/customers`
- Auth: none declared

Creates or updates up to 100 customers in your depot in one request, matching on `account`. If a customer with that `account` already exists in your depot, it is updated: the fields you send overwrite the stored values, and the fields you leave out are kept. For `capacities`, `skills`, `tags`, `time_windows` and `custom_fields`, sending the array replaces the customer's whole set (an empty array clears it). Leaving the array out keeps the existing set.

- Each customer needs an `address`, or both `lat` and `lng`. An address sent without coordinates is geocoded. If geocoding fails, the customer is still saved, just without coordinates.
- `capacities` are matched by `id` (see `GET /capacities`), `skills` and `tags` by name (ignoring case), and `custom_fields` by `id` or `name` (see `GET /custom-fields`). Entries that match nothing in your depot or organisation are dropped without an error.
- If any customer in the array fails validation, nothing is saved and the response is `400 INVALID_INPUT`. A depot can hold at most 300,000 customers. A request that would go over that limit is also rejected with `400 INVALID_INPUT`.

The response lists the `id` and `account` of each customer, in request order. This endpoint supports [Idempotency](#section/Getting-started/Idempotency), but a replayed response has the original status and an empty body, so recover the IDs with `GET /customers`. Because customers are matched on `account`, sending the same batch again updates the same customers rather than creating duplicates.

## Headers

- `idempotency-key` string

## Request body

- object[]
  - `name` string: Full name of the customer
  - `account` string, required: Unique account reference (required)
  - `address` string
  - `postcode` string
  - `phone` string
  - `email` string, email
  - `lat` number: Latitude coordinate
  - `lng` number: Longitude coordinate
  - `duration` number: Visit duration in minutes
  - `notes` string
  - `capacities` object[]: List of predefined capacity types by ID
    - `id` integer, required
    - `capacity` number, required
  - `skills` string[]: List of skill names (must already exist)
  - `tags` string[]: List of tag names (must already exist)
  - `time_windows` object[]: Up to two delivery/pickup time windows per customer
    - `from` string, required: Start time in 24-hour HH:mm format
    - `to` string: End time in 24-hour HH:mm format
  - `custom_fields` object[]: Custom field values. Use `id` or `name` to identify each custom field.
    - `id` integer
    - `name` string
    - `value` string, required

## Response `201`

Customers created successfully

- object
  - `customers` object[]
    - `id` integer
    - `account` string
  - `errors` unknown[]: List of customers that failed validation
    - unknown

## Other responses

- `400`: Invalid input. The request body failed validation.
- `403`: Forbidden. Returned for rate-limit exhaustion (`name = FORBIDDEN`, with `details.retry_after_seconds` and `Retry-After` header) and permission gate failures (`name = PERMISSION`). Inspect `name` to disambiguate. Send `x-sr-api-features: rate-limit-429` to receive `429 RATE_LIMIT_EXCEEDED` for the rate-limit case instead, leaving `403` to mean permission failures only — see [API Features](#section/Getting-started/API-Features).
- `404`: The requested resource was not found.
- `409`: A request with this idempotency key is already in progress. Retry after the period given in the `Retry-After` header.
- `422`: The same idempotency key was used with a different request body for this endpoint.
- `429`: Rate limit exceeded (`name = RATE_LIMIT_EXCEEDED`). **Only returned to callers that opt in** with the `x-sr-api-features: rate-limit-429` request header; without it the same condition returns `403` with `name = FORBIDDEN` and an otherwise identical body. See [API Features](#section/Getting-started/API-Features).
- `500`: An unexpected server error occurred. The body carries only `name` and `description`: internal details are never returned.

---

[API](https://skmtc.dev/smartroutes/apis/smartroutes-api.md) · [All operations](https://skmtc.dev/smartroutes/apis/smartroutes-api/llms.txt) · [OpenAPI document](https://skmtc.dev/smartroutes/apis/smartroutes-api/revisions/a7ea507afac7?raw)
