docs/api-reference/admin-api/querying.mdx
The Admin API uses Ransack for filtering and sorting on collection endpoints. All filter conditions go through the q parameter; sorting and pagination are top-level params.
Pass filter conditions via q:
const orders = await client.orders.list({
status_eq: 'complete',
total_gteq: 100,
email_cont: '@example.com',
})
curl -G 'https://store.example.com/api/v3/admin/orders' \
-H 'X-Spree-Api-Key: sk_xxx' \
--data-urlencode 'q[status_eq]=complete' \
--data-urlencode 'q[total_gteq]=100' \
--data-urlencode 'q[email_cont][email protected]'
| Predicate | Description | SDK | cURL |
|---|---|---|---|
eq | Equals | status_eq: 'complete' | q[status_eq]=complete |
not_eq | Not equals | status_not_eq: 'canceled' | q[status_not_eq]=canceled |
cont | Contains (case-insensitive) | email_cont: '@acme' | q[email_cont]=@acme |
start | Starts with | number_start: 'R10' | q[number_start]=R10 |
end | Ends with | slug_end: 'sale' | q[slug_end]=sale |
lt / lteq | Less than / less than or equal | total_lteq: 50 | q[total_lteq]=50 |
gt / gteq | Greater than / greater than or equal | total_gteq: 100 | q[total_gteq]=100 |
in | In a list | status_in: ['complete', 'canceled'] | q[status_in][]=complete&q[status_in][]=canceled |
null / not_null | Is null / not null | completed_at_not_null: true | q[completed_at_not_null]=true |
true / false | Boolean | accepts_email_marketing_true: 1 | q[accepts_email_marketing_true]=1 |
Resource ID filters accept Stripe-style prefixed IDs directly. The server decodes them before querying:
<CodeGroup>// All orders for a specific customer
const orders = await client.orders.list({
user_id_eq: 'cus_UkLWZg9DAJ',
})
curl -G 'https://store.example.com/api/v3/admin/orders' \
-H 'X-Spree-Api-Key: sk_xxx' \
--data-urlencode 'q[user_id_eq]=cus_UkLWZg9DAJ'
The same applies to _id_in, _id_not_eq, and other ID predicates.
Multiple filters combine with AND:
<CodeGroup>// Completed orders over $100 from the last 7 days
const orders = await client.orders.list({
status_eq: 'complete',
total_gteq: 100,
completed_at_gteq: new Date(Date.now() - 7 * 86_400_000).toISOString(),
})
curl -G 'https://store.example.com/api/v3/admin/orders' \
-H 'X-Spree-Api-Key: sk_xxx' \
--data-urlencode 'q[status_eq]=complete' \
--data-urlencode 'q[total_gteq]=100' \
--data-urlencode 'q[completed_at_gteq]=2026-04-22T00:00:00Z'
Use underscore notation to filter on associated model attributes:
<CodeGroup>// Customers tagged as "wholesale"
const customers = await client.customers.list({
tags_name_eq: 'wholesale',
})
// Products in a specific category
const products = await client.products.list({
taxons_id_eq: 'ctg_xxx',
})
curl -G 'https://store.example.com/api/v3/admin/customers' \
-H 'X-Spree-Api-Key: sk_xxx' \
--data-urlencode 'q[tags_name_eq]=wholesale'
Some resources expose convenience search scopes:
| Resource | Scope | Example |
|---|---|---|
| Customers | search | Full-text over email + first/last name |
| Customers | with_min_total_spent | Filter by lifetime spend |
| Orders | multi_search | Number + email full-text |
const customers = await client.customers.list({
search: 'jane',
})
const vipCustomers = await client.customers.list({
with_min_total_spent: 1000,
})
curl -G 'https://store.example.com/api/v3/admin/customers' \
-H 'X-Spree-Api-Key: sk_xxx' \
--data-urlencode 'q[search]=jane'
Use the top-level sort parameter on any list endpoint. Prefix with - for descending. Follows the JSON:API sorting convention.
// Most recent orders first
const orders = await client.orders.list({
sort: '-completed_at',
})
// Multiple sort keys (priority left to right)
const customers = await client.customers.list({
sort: '-total_spent,email',
})
curl -G 'https://store.example.com/api/v3/admin/orders' \
-H 'X-Spree-Api-Key: sk_xxx' \
-d 'sort=-completed_at'
Sortable columns are limited to those whitelisted on the model (Ransack's whitelisted_ransackable_attributes). Sorting on a virtual column (e.g., a serializer-computed field like display_total_spent) is not supported.
All collection endpoints return paginated results. Control with page and limit:
const { data: orders, meta } = await client.orders.list({
page: 2,
limit: 50,
})
// meta.count, meta.pages, meta.previous, meta.next ...
curl -G 'https://store.example.com/api/v3/admin/orders' \
-H 'X-Spree-Api-Key: sk_xxx' \
-d 'page=2' \
-d 'limit=50'
| Parameter | Default | Max | Description |
|---|---|---|---|
page | 1 | — | Page number (1-indexed) |
limit | 25 | 100 | Records per page |
Responses include a meta object:
{
"data": [...],
"meta": {
"page": 2,
"limit": 50,
"count": 327,
"pages": 7,
"from": 51,
"to": 100,
"in": 50,
"previous": 1,
"next": 3
}
}
| Field | Description |
|---|---|
page | Current page number |
limit | Records per page |
count | Total number of matching records |
pages | Total number of pages |
from / to / in | Position range / count of this page |
previous / next | Previous/next page number, or null |
Most admin endpoints return slim payloads by default — associations are returned as IDs. Use the expand parameter to include related resources inline:
const order = await client.orders.get('or_xxx', {
expand: ['items', 'fulfillments', 'payments', 'customer'],
})
// Nested expand
const customer = await client.customers.get('cus_xxx', {
expand: ['addresses', 'store_credits'],
})
curl -G 'https://store.example.com/api/v3/admin/orders/or_xxx' \
-H 'X-Spree-Api-Key: sk_xxx' \
-d 'expand=items,fulfillments,payments,customer'
Use dot notation up to 4 levels deep:
const order = await client.orders.get('or_xxx', {
expand: ['items.variant.product', 'fulfillments.tracking'],
})
A nested expand implicitly includes its parent — expand: ['items.variant'] returns both items and their variant data.
Each endpoint documents its supported expand keys in the OpenAPI reference. Common admin expansions:
| Resource | Common expands |
|---|---|
| Orders | items, fulfillments, payments, customer, discounts, adjustments, billing_address, shipping_address |
| Customers | addresses, store_credits, orders |
| Products | variants, media, option_types, categories |
Use the fields parameter to request only specific fields on a resource. Reduces payload size for bandwidth-sensitive integrations:
const orders = await client.orders.list({
fields: ['number', 'total', 'status', 'completed_at'],
})
curl -G 'https://store.example.com/api/v3/admin/orders' \
-H 'X-Spree-Api-Key: sk_xxx' \
-d 'fields=number,total,status,completed_at'
Rules:
id is always includedfields