Skip to main content
Sign in with your store to see only the help that applies to you.

Customers API

Required scope: customers:read (read) · customers:write (write)

If the store's plan does not include customer profiles, every customer endpoint returns 403 with code: feature_not_enabled.


List Customers​

GET /api/v1/customers

Returns a paginated list of customers, most recently updated first.

Query Parameters​

ParameterTypeDescription
pageintegerPage number (default: 1)
per_pageintegerResults per page (default: 25, max: 100)
qstringSearch by name, email, phone, customer code, or loyalty card number
updated_sinceISO 8601Only customers updated after this timestamp. An unparseable value is ignored.

Response​

{
"data": [
{
"id": 1,
"name": "Jane Smith",
"email": "jane@example.com",
"email_receipt_opt_in": true,
"phone": "5551234567",
"customer_code": "C10001",
"loyalty_card_number": "LC-0001",
"date_of_birth": "1990-04-12",
"age": 35,
"birthday_today": false,
"loyalty_tier": "Gold",
"loyalty_points": 2450,
"lifetime_spend": "1250.00",
"visit_count": 34,
"store_credit_balance": "15.00",
"address": {
"line1": "123 Main St",
"city": "Saskatoon",
"province": "SK",
"postal_code": "S7K 1A1",
"country": "CA"
},
"created_at": "2025-06-10T08:00:00Z",
"updated_at": "2026-03-21T16:45:00Z"
}
],
"pagination": { ... }
}
FieldNotes
lifetime_spend, store_credit_balanceTwo-decimal strings
notesStaff notes, when present
addressPresent only when the customer has an address line 1; also may include line2

Fields with no value are omitted rather than sent as null.


Get a Customer​

GET /api/v1/customers/:id

Returns a single customer in the same shape as the list.


Create a Customer​

POST /api/v1/customers

Required scope: customers:write

Request Body​

{
"customer": {
"name": "John Doe",
"email": "john@example.com",
"phone": "5559876543",
"loyalty_card_number": "LC-0042"
}
}

Writable fields: name, email, phone, loyalty_card_number, email_receipt_opt_in, notes, date_of_birth.

Duplicate check​

Before creating, the API looks for an existing customer with the same phone or email. If one is found, nothing is created and the response is 200 OK:

{
"data": {
"customer": { "id": 1, "name": "Jane Smith", ... },
"duplicate": true,
"message": "Found existing customer Jane Smith."
},
"meta": { ... }
}

If you sent an email and the existing customer has none on file, it is saved onto them. An email already on file is never replaced.

To create the record anyway, send "force_create": true at the top level of the body (next to customer).

Response​

201 Created with data set to the new customer. Validation failures return 422 with { "errors": [ ... ] }.

Create and update return a different shape

The create, duplicate and update responses return the full internal customer record (more fields, flat address columns instead of an address object), not the shape used by list and get. If you store customers in the documented shape, follow a write with GET /api/v1/customers/:id.


Update a Customer​

PATCH /api/v1/customers/:id

PUT is also accepted.

Required scope: customers:write

Request Body​

{
"customer": {
"email": "newemail@example.com",
"phone": "5551111111"
}
}

The writable fields are the same as for create.


Other Customer Lookups​

These GET endpoints also work with customers:read. They return the full internal customer format rather than the shape above.

EndpointDescription
GET /api/v1/customers/lookup_by_code/:codeFind a customer by customer code (format C#####; other formats return 400)
GET /api/v1/customers/lookup_by_card/:card_numberFind a customer by loyalty card number
GET /api/v1/customers/:id/recent_salesThe customer's last 5 completed sales
GET /api/v1/customers/:id/loyaltyLoyalty details (requires loyalty in the store's plan)

Earning, redeeming or adjusting loyalty points (POST /api/v1/customers/:id/loyalty/*) is not available to API keys and returns 403.