Summary and recommendation
The commercetools Platform API manages Customer (shopper) resources via REST under `https://api.{region}.commercetools.com/{projectKey}/customers`. Authentication uses OAuth 2.0 client credentials flow; tokens are project-scoped and must declare all required scopes at API Client creation - scopes cannot be amended post-creation without recreating the client.
The critical architectural distinction: Merchant Center team users (admin accounts) are not manageable via the Platform API. There is no REST endpoint for team user CRUD; that surface is UI-only or SSO/OIDC-only.
For identity graph use cases - syncing an external IdP or directory into commercetools customer records - the `externalId` field on the Customer object is the primary correlation key. It is not enforced as unique by the platform, so uniqueness must be guaranteed in the integration layer.
Subscriptions (webhooks) deliver CustomerCreated, CustomerDeleted, CustomerEmailVerified, and CustomerPasswordUpdated events to HTTP endpoints or message queues (AWS SQS/SNS, Azure Service Bus, Google Cloud Pub/Sub), enabling downstream identity graph updates without polling.
API quick reference
| Has user API | Yes |
| Auth method | OAuth 2.0 (Client Credentials flow for M2M; Password flow for customer login) |
| Base URL | Official docs |
| SCIM available | No |
| SCIM plan required | Enterprise |
Authentication
Auth method: OAuth 2.0 (Client Credentials flow for M2M; Password flow for customer login)
Setup steps
- Create an API Client in the Merchant Center under Settings > Developer Settings > API Clients.
- Select the required scopes (e.g., manage_customers, view_customers) for the client.
- Copy the client_id and client_secret shown once at creation time.
- POST to https://auth.{region}.commercetools.com/oauth/token with grant_type=client_credentials, client_id, client_secret, and scope to obtain a Bearer token.
- Include the Bearer token in the Authorization header for all API requests.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| manage_customers:{projectKey} | Full read/write access to Customer resources. | Create, update, delete customers |
| view_customers:{projectKey} | Read-only access to Customer resources. | List and retrieve customers |
| manage_customer_groups:{projectKey} | Create and manage Customer Groups. | Assigning customers to groups |
| manage_orders:{projectKey} | Access to orders associated with customers. | Retrieving customer order history |
| manage_project:{projectKey} | Admin-level project management including team user administration via Merchant Center API. | Managing Merchant Center team users programmatically |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | string (UUID) | Unique identifier of the Customer. | auto-generated | immutable | System-assigned UUID. |
| version | integer | Current version for optimistic concurrency control. | starts at 1 | must be provided; increments on each update | Required in all update requests. |
| string | Customer's email address; must be unique per project. | required | via ChangeEmail action | Case-insensitive uniqueness enforced. | |
| password | string (hashed) | Hashed password; never returned in plaintext. | required (plaintext, then hashed) | via ChangePassword action | Not returned in GET responses. |
| firstName | string | Customer's first name. | optional | via SetFirstName action | |
| lastName | string | Customer's last name. | optional | via SetLastName action | |
| customerNumber | string | User-defined unique customer number. | optional | via SetCustomerNumber action | Must be unique if set. |
| externalId | string | External system identifier. | optional | via SetExternalId action | Useful for syncing with external identity systems. |
| isEmailVerified | boolean | Whether the customer's email has been verified. | optional (default false) | via SetEmailVerified action | |
| customerGroup | CustomerGroupReference | Reference to the customer's group (for pricing/discounts). | optional | via SetCustomerGroup action | |
| addresses | array of Address | List of addresses associated with the customer. | optional | via AddAddress / ChangeAddress / RemoveAddress actions | |
| defaultShippingAddressId | string | ID of the default shipping address from the addresses array. | optional | via SetDefaultShippingAddress action | |
| defaultBillingAddressId | string | ID of the default billing address from the addresses array. | optional | via SetDefaultBillingAddress action | |
| dateOfBirth | date (YYYY-MM-DD) | Customer's date of birth. | optional | via SetDateOfBirth action | |
| companyName | string | Company name associated with the customer. | optional | via SetCompanyName action | |
| vatId | string | VAT ID for B2B customers. | optional | via SetVatId action | |
| locale | string (BCP 47) | Preferred locale of the customer. | optional | via SetLocale action | |
| custom | CustomFields | Project-defined custom fields. | optional | via SetCustomField / SetCustomType actions | Requires a Custom Type to be defined first. |
| createdAt | datetime (ISO 8601) | Timestamp of customer creation. | auto-generated | immutable | |
| lastModifiedAt | datetime (ISO 8601) | Timestamp of last modification. | auto-generated | auto-updated |
Core endpoints
Create Customer
- Method: POST
- URL:
https://api.{region}.commercetools.com/{projectKey}/customers - Watch out for: Response wraps the customer in a CustomerSignInResult object (with cart if applicable). The password is never returned.
Request example
POST /{projectKey}/customers
Authorization: Bearer {token}
Content-Type: application/json
{
"email": "jane@example.com",
"password": "secret123",
"firstName": "Jane",
"lastName": "Doe"
}
Response example
{
"customer": {
"id": "abc-123",
"version": 1,
"email": "jane@example.com",
"firstName": "Jane",
"lastName": "Doe",
"isEmailVerified": false,
"createdAt": "2024-01-15T10:00:00.000Z"
}
}
Get Customer by ID
- Method: GET
- URL:
https://api.{region}.commercetools.com/{projectKey}/customers/{id} - Watch out for: Requires manage_customers or view_customers scope. Password hash is never included in the response.
Request example
GET /{projectKey}/customers/abc-123
Authorization: Bearer {token}
Response example
{
"id": "abc-123",
"version": 1,
"email": "jane@example.com",
"firstName": "Jane",
"lastName": "Doe",
"isEmailVerified": false
}
Query Customers
- Method: GET
- URL:
https://api.{region}.commercetools.com/{projectKey}/customers - Watch out for: Uses commercetools Query Predicate syntax for the
whereparameter. Max limit is 500. For large datasets, use offset pagination carefully as deep offsets are slow.
Request example
GET /{projectKey}/customers?where=email%3D%22jane%40example.com%22&limit=20&offset=0
Authorization: Bearer {token}
Response example
{
"limit": 20,
"offset": 0,
"count": 1,
"total": 1,
"results": [
{ "id": "abc-123", "email": "jane@example.com" }
]
}
Update Customer
- Method: POST
- URL:
https://api.{region}.commercetools.com/{projectKey}/customers/{id} - Watch out for: Updates use an action-based pattern (not PATCH with field diffs). The current
versionmust be sent; a version mismatch returns HTTP 409 ConcurrentModification.
Request example
POST /{projectKey}/customers/abc-123
Authorization: Bearer {token}
Content-Type: application/json
{
"version": 1,
"actions": [
{ "action": "setFirstName", "firstName": "Janet" }
]
}
Response example
{
"id": "abc-123",
"version": 2,
"firstName": "Janet",
"email": "jane@example.com"
}
Delete Customer
- Method: DELETE
- URL:
https://api.{region}.commercetools.com/{projectKey}/customers/{id}?version={version} - Watch out for: The
versionquery parameter is mandatory. Deletion is permanent and returns the deleted Customer object.
Request example
DELETE /{projectKey}/customers/abc-123?version=2
Authorization: Bearer {token}
Response example
{
"id": "abc-123",
"version": 2,
"email": "jane@example.com"
}
Customer Login (Authenticate)
- Method: POST
- URL:
https://api.{region}.commercetools.com/{projectKey}/login - Watch out for: This endpoint authenticates a shopper (Customer), not a Merchant Center team user. Returns CustomerSignInResult. Use the Password flow token endpoint for session tokens.
Request example
POST /{projectKey}/login
Content-Type: application/json
{
"email": "jane@example.com",
"password": "secret123"
}
Response example
{
"customer": {
"id": "abc-123",
"email": "jane@example.com",
"version": 2
}
}
Create Customer Token (Email Verification / Password Reset)
- Method: POST
- URL:
https://api.{region}.commercetools.com/{projectKey}/customers/email-token - Watch out for: The token
valuemust be sent to the customer via your own email infrastructure. commercetools does not send emails.
Request example
POST /{projectKey}/customers/email-token
Authorization: Bearer {token}
Content-Type: application/json
{
"id": "abc-123",
"version": 2,
"ttlMinutes": 2880
}
Response example
{
"id": "token-uuid",
"customerId": "abc-123",
"value": "abc123tokenvalue",
"expiresAt": "2024-01-17T10:00:00.000Z"
}
Get Customer by Key
- Method: GET
- URL:
https://api.{region}.commercetools.com/{projectKey}/customers/key={key} - Watch out for: The
keyfield must be set on the Customer at creation or via SetKey action. Not all customers have keys.
Request example
GET /{projectKey}/customers/key=my-external-key
Authorization: Bearer {token}
Response example
{
"id": "abc-123",
"key": "my-external-key",
"email": "jane@example.com",
"version": 2
}
Rate limits, pagination, and events
- Rate limits: commercetools enforces per-project rate limits. Default limits vary by plan and region. Limits are communicated via response headers when a threshold is approached or exceeded.
- Rate-limit headers: Yes
- Retry-After header: Yes
- Rate-limit notes: HTTP 429 is returned when rate limit is exceeded. The X-RateLimit-Limit and X-RateLimit-Remaining headers are present. Retry-After header is included on 429 responses. Exact numeric limits are not publicly published and are project-specific.
- Pagination method: offset
- Default page size: 20
- Max page size: 500
- Pagination pointer: limit / offset
| Plan | Limit | Concurrent |
|---|---|---|
| Standard/Growth | Varies by project; typically ~100–200 requests/second per project | 0 |
| Enterprise/Premium | Higher limits negotiated per contract; contact commercetools support | 0 |
- Webhooks available: Yes
- Webhook notes: commercetools supports Subscriptions (webhooks) that deliver messages to external endpoints (HTTP/SQS/SNS/Azure Service Bus/Google Pub/Sub) when resource changes occur, including Customer events.
- Alternative event strategy: Subscriptions can also target message queues (AWS SQS/SNS, Azure Service Bus, Google Cloud Pub/Sub) instead of HTTP endpoints.
- Webhook events: CustomerCreated, CustomerUpdated (via resource update messages), CustomerDeleted, CustomerEmailVerified, CustomerPasswordUpdated, CustomerGroupSet
SCIM API status
- SCIM available: No
- SCIM version: Not documented
- Plan required: Enterprise
- Endpoint: Not documented
Limitations:
- SCIM is not documented or officially supported by commercetools as of the latest available documentation.
- Merchant Center team user provisioning is managed via the Merchant Center UI or SSO/OIDC (Identity Enterprise).
- SSO via OIDC is available for Enterprise plans; SCIM automated provisioning is not available.
- Customer (shopper) accounts are managed via the Platform API, not SCIM.
Common scenarios
Three integration patterns cover the majority of identity-adjacent use cases:
Provision + verify: POST to
/{projectKey}/customersto create the record, then POST to/{projectKey}/customers/email-tokento generate a verification token. commercetools does not send email - the token value must be delivered via your own email infrastructure and confirmed via/{projectKey}/customers/email/confirm.Action-based profile updates: All Customer mutations use a POST with an
actionsarray, not a PATCH. The currentversionis mandatory; a stale version returns HTTP 409 ConcurrentModification. Multiple actions (e.g., setFirstName, setLocale, setExternalId) can be batched in a single request to minimize round-trips and version conflicts.External identity sync via externalId: Set
externalIdat creation to the upstream directory's user ID. Query by predicate (where=externalId%3D%22{value}%22) to resolve the commercetools customer from an external event. Subscribe to CustomerCreated and CustomerDeleted messages to propagate changes back to the identity graph without polling the query endpoint.
Provision a new customer account and trigger email verification
- POST to /{projectKey}/customers with email, password, firstName, lastName to create the customer.
- Extract the customer
idandversionfrom the CustomerSignInResult response. - POST to /{projectKey}/customers/email-token with the customer
id,version, and desiredttlMinutes. - Receive the token
valuein the response and send it to the customer via your own email service. - When the customer clicks the link, POST to /{projectKey}/customers/email/confirm with the token
valueto mark isEmailVerified=true.
Watch out for: commercetools does not send emails. Steps 4–5 require your own email delivery infrastructure and a front-end confirmation page.
Update customer profile fields using action-based updates
- GET /{projectKey}/customers/{id} to retrieve the current customer and note the
version. - POST to /{projectKey}/customers/{id} with the current
versionand anactionsarray containing the desired update actions (e.g., setFirstName, setLastName, setLocale). - Multiple actions can be batched in a single request.
- The response returns the updated Customer with an incremented
version.
Watch out for: If another process updates the customer between your GET and POST, you will receive a 409 ConcurrentModification. Re-fetch the customer and retry with the new version.
Sync external identity system users to commercetools customers using externalId
- When creating a customer, include
externalIdset to the user's ID in your external identity system. - To look up a customer by externalId, use GET /{projectKey}/customers?where=externalId%3D%22ext-user-456%22.
- To update the mapping, POST to /{projectKey}/customers/{id} with action
setExternalIdand the new value. - Subscribe to CustomerCreated and CustomerDeleted messages via a Subscription to keep the external system in sync.
Watch out for: externalId is not enforced as unique by commercetools. Enforce uniqueness in your integration layer or use the where query to check before creation.
Why building this yourself is a trap
The most consequential caveat for any identity automation build: SCIM is not supported. Merchant Center team user provisioning has no API surface - it is managed exclusively through the Merchant Center UI or OIDC-based SSO for Enterprise plans.
Any workflow that assumes programmatic control over admin user lifecycle will hit a hard wall; there is no endpoint to create, update, or delete team members via the Platform API.
Pagination uses offset/limit with a maximum page size of 500. Deep offset queries (offset > 10,000) degrade significantly; large-dataset traversals should use the Customer Search API or sort-keyed iteration rather than naive offset increments.
Rate limits are project-specific and not publicly published - the X-RateLimit-Remaining header is the only real-time signal, and HTTP 429 responses include a Retry-After header. Exact thresholds for Standard/Growth plans are approximately 100–200 requests/second per project, but Enterprise limits are contract-negotiated and must be confirmed with commercetools directly.
Automate commercetools workflows without one-off scripts
Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.