Customers
The shop's customer accounts: find them, add them, and set the wholesale membership that prices their carts.
Reach them through admin.customers. Reading needs the customers:read scope, changing them customers:write.
List customers
admin.customers.list(query?: ListQuery & { search?: string; email?: string }): Promise<CursorPage<AdminCustomer>>
One page of the shop's customers, by words in the name, email or phone, or an exact email; or oldest change first with updatedSince. Read the next page with meta.nextCursor as cursor. Scope: customers:read.
Parameters
| Parameter |
Type |
|
query |
object |
The query fields below, by name. |
Query fields
| Parameter | Type | Description |
|---|
search | string | At most 200 characters. |
email | string | An exact email (case-insensitive). At most 255 characters. |
limit | integer | At least 1. At most 100. Default 50. |
cursor | string | meta.nextCursor (or the cursor in links.next) of the previous page. At most 1000 characters. |
updatedSince | string (date-time) | Records changed at or after this time (ISO 8601, any offset or Z), oldest change first, for incremental sync. |
Returns
A CursorPage<AdminCustomer>:
| Field | Type | Description |
|---|
links | object | |
links.first | null | |
links.last | null | |
links.prev | string (uri) or null | |
links.next | string (uri) or null | |
meta | object | |
meta.path | string (uri) | |
meta.perPage | integer | |
meta.nextCursor | string or null | |
meta.prevCursor | string or null | |
data | array of AdminCustomer | |
data[].id | integer | |
data[].name | string | |
data[].email | string or null | |
data[].emailVerified | boolean | |
data[].phone | string or null | |
data[].phoneVerified | boolean | |
data[].wholesale | boolean | |
data[].wholesaleMembershipId | integer or null | |
data[].createdAt | string (date-time) or null | |
data[].updatedAt | string (date-time) or null | |
Errors
| Status | When |
|---|
401 | invalid_api_key: no active Admin key in Authorization: Bearer. |
403 | admin_key_in_browser (an Origin header was sent), ip_not_allowed, plan_required(the shop's plan lacks the Integration API, or the feature the endpoint needs), orinsufficient_scope. |
422 | Validation errors ({ message, errors }), or an idempotency refusal(idempotency_key_reused, idempotency_key_invalid). |
429 | Rate limited. |
Example
const { data } = await admin.customers.list({ email: '[email protected]' });
const customer = data[0];
Iterate over every customer
admin.customers.all(query?: ListQuery & { search?: string; email?: string }): AsyncGenerator<AdminCustomer, void, undefined>
Every customer matching the query, one at a time, fetching the next page as you read. It takes the same query as list, without cursor. Scope: customers:read.
Returns
An async generator of AdminCustomer, each:
| Field | Type | Description |
|---|
[].id | integer | |
[].name | string | |
[].email | string or null | |
[].emailVerified | boolean | |
[].phone | string or null | |
[].phoneVerified | boolean | |
[].wholesale | boolean | |
[].wholesaleMembershipId | integer or null | |
[].createdAt | string (date-time) or null | |
[].updatedAt | string (date-time) or null | |
Example
for await (const customer of admin.customers.all({
updatedSince: lastSyncedAt,
})) {
await crm.saveCustomer(customer);
}
Get a customer
admin.customers.get(id: number): Promise<AdminCustomer>
One customer by their id. Scope: customers:read.
Parameters
| Parameter |
Type |
|
id |
number |
The customer's id. |
Returns
An AdminCustomer:
| Field | Type | Description |
|---|
id | integer | |
name | string | |
email | string or null | |
emailVerified | boolean | |
phone | string or null | |
phoneVerified | boolean | |
wholesale | boolean | |
wholesaleMembershipId | integer or null | |
createdAt | string (date-time) or null | |
updatedAt | string (date-time) or null | |
Errors
| Status | When |
|---|
401 | invalid_api_key: no active Admin key in Authorization: Bearer. |
403 | admin_key_in_browser (an Origin header was sent), ip_not_allowed, plan_required(the shop's plan lacks the Integration API, or the feature the endpoint needs), orinsufficient_scope. |
404 | not_found: the shop has no such record. |
429 | Rate limited. |
Create a customer
admin.customers.create(input: AdminCustomerInput, options?: RequestOptions): Promise<AdminCustomer>
A new customer, emailed at once to choose a password. The email, and the phone when given, must be unique in the shop. Scope: customers:write.
Parameters
| Parameter |
Type |
|
input |
object |
The customer's fields, below. |
options |
RequestOptions |
idempotencyKey, signal and headers (see retries). |
Request fields
| Field | Type | Description |
|---|
name required | string | At most 255 characters. |
email required | string (email) | Unique in the shop. At most 255 characters. |
phone | string or null | Unique in the shop. At most 50 characters. |
Returns
The new AdminCustomer:
| Field | Type | Description |
|---|
id | integer | |
name | string | |
email | string or null | |
emailVerified | boolean | |
phone | string or null | |
phoneVerified | boolean | |
wholesale | boolean | |
wholesaleMembershipId | integer or null | |
createdAt | string (date-time) or null | |
updatedAt | string (date-time) or null | |
Errors
| Status | When |
|---|
401 | invalid_api_key: no active Admin key in Authorization: Bearer. |
403 | admin_key_in_browser (an Origin header was sent), ip_not_allowed, plan_required(the shop's plan lacks the Integration API, or the feature the endpoint needs), orinsufficient_scope. |
409 | idempotency_in_progress: a request with this Idempotency-Key is still running. Retry with the same key after Retry-After seconds; the first answer is then replayed. |
422 | Validation errors ({ message, errors }), or an idempotency refusal(idempotency_key_reused, idempotency_key_invalid). |
429 | Rate limited. |
Example
const customer = await admin.customers.create({
name: 'Sara Ali',
email: '[email protected]',
});
Set a wholesale membership
admin.customers.setMembership(id: number, wholesaleMembershipId: number | null, options?: UpdateOptions): Promise<AdminCustomer>
Puts the customer in one of the shop's wholesale memberships, or back to retail with null. Business plan (403 plan_required). Scope: customers:write.
Parameters
| Parameter |
Type |
|
id |
number |
The customer's id. |
wholesaleMembershipId |
number | null |
The membership's id, or null for retail. |
options |
UpdateOptions |
unmodifiedSince (the updatedAt you read; a newer customer is refused with 409 edit_conflict) and idempotencyKey. |
Request fields
The SDK sends the membership id as wholesaleMembershipId:
| Field | Type | Description |
|---|
wholesaleMembershipId required | integer or null | |
updatedAt | string (date-time) or null | The record's updatedAt as you read it; a newer record gets 409 edit_conflict. |
Returns
The changed AdminCustomer:
| Field | Type | Description |
|---|
id | integer | |
name | string | |
email | string or null | |
emailVerified | boolean | |
phone | string or null | |
phoneVerified | boolean | |
wholesale | boolean | |
wholesaleMembershipId | integer or null | |
createdAt | string (date-time) or null | |
updatedAt | string (date-time) or null | |
Errors
| Status | When |
|---|
401 | invalid_api_key: no active Admin key in Authorization: Bearer. |
403 | admin_key_in_browser (an Origin header was sent), ip_not_allowed, plan_required(the shop's plan lacks the Integration API, or the feature the endpoint needs), orinsufficient_scope. |
404 | not_found: the shop has no such record. |
409 | edit_conflict (the record changed after If-Unmodified-Since or updatedAt), or idempotency_in_progress. |
422 | Validation errors ({ message, errors }), or an idempotency refusal(idempotency_key_reused, idempotency_key_invalid). |
429 | Rate limited. |
Example
const customer = await admin.customers.get(88);
await admin.customers.setMembership(88, 3, {
unmodifiedSince: customer.updatedAt ?? undefined,
});