List categories
Returns a paginated page of categories in the one-map resource shape (system fields top-level, every customer key under attributes). Reference attributes embed COMPLETE as labeled stubs — no expansion calls; inverse-reference keys never appear (query the other side via filters, or page /products/{id}/references). Filter with filter[<key>]=<value> / filter[<key>][<op>]=<value> (see the filter parameter for the grammar), sort with sort, page with limit/offset, and narrow the returned keys with fields=<key>,<key>.
Scope: read:categories.
Request
- Base URLhttps://www.merchkit.com/api/v1
- AuthHTTP bearer
Query parameters
Rows per page, 1 to 200. Defaults to 50.
Larger pages mean fewer round-trips. 200 is also the cap on in operator value lists, so refetching a full page of IDs with filter[id][in]=... always fits in one call.
Rows to skip. Zero-based. Echoed back in pagination.offset.
Page forward by adding limit each time. Stop when pagination.has_more is false rather than comparing counts. Default order is created_at ascending, which keeps long sweeps stable: new rows land at the end instead of shifting pages you have not read yet.
Comma-separated keys. Prefix with - for descending: sort=-updated_at,sku. Defaults to created_at ascending.
Sortable: text, number, and date attributes, plus id, parent_id, created_at, and updated_at. Reference and list-reference keys have no scalar order. Sorting on one returns 400 (not_sortable).
Narrow results by attribute value. Filtering runs in the database, so pagination.total reflects matches, not the whole catalog.
Two forms: filter[sku]=ARIA-DT-72 is shorthand for equals. filter[price][gte]=100 uses the bracket-operator form filter[<key>][<op>]=<value>.
Keys: any attribute key from GET /attributes, plus id, parent_id, created_at, and updated_at. Reference keys like vendor match on UUID, not name.
Operators:
- Text: eq, ne, contains, not_contains, starts_with, ends_with
- Number, date, timestamp: gt, gte, lt, lte. A bare date means UTC midnight.
- Any type: exists (=true has a value, =false is blank)
- Text, id, and reference keys: in (comma-separated OR-of-equals, up to 200 values)
Examples:
- filter[sku]=ARIA-DT-72 one product by your own identifier
- filter[description][exists]=false everything missing a description
- filter[price][gte]=100&filter[material]=Oak both must match (see filter_join)
- filter[id][in]=<uuid1>,<uuid2> refetch a known set in one call
- filter[vendor]=<uuid>&sort=-updated_at one vendor's catalog, newest first
A bad filter never silently matches everything. Unknown keys, unknown operators, or type mismatches return 400 with field_errors naming the exact clause.
Brackets and spaces need URL-encoding. With curl, use -G --data-urlencode "filter[vendor_name]=Nordic Timber Co.". In JavaScript, let URLSearchParams handle it.
How multiple filter clauses combine. and means every clause must match (the default). or means any clause can match. Case-insensitive. Any other value returns 400.
Only matters with two or more clauses. One join applies to all of them. There is no grouping or nesting, so mixed AND/OR logic belongs on your side.
For OR across values of the same key, prefer filter[<key>][in]=a,b,c. One clause, up to 200 values, and it reads better in a log.
Return only the attribute keys you name instead of every populated one. Useful when a workspace has hundreds of attributes and your integration reads a handful.
fields=sku,price (comma-separated) or fields=sku&fields=price (repeated param, same result). Omit it entirely to get every populated key.
Only the attributes map narrows. System fields (id, type, label, parent_id, created_at, updated_at) always come back, so naming one returns 400. label still resolves from the primary attribute whether or not you select it. pagination.total is unchanged. filter and sort are independent: filter on keys you did not select.
Sparse still means sparse. A selected key with no value stays absent. Keep reading attributes[key] ?? null.
Reference keys return {id, type, label} stubs as usual, but fields does not reach through them. fields=gallery_images.image_url is not supported. Select the reference key, then batch-fetch: GET /images?filter[id][in]=<ids>&fields=image_url.
Unknown keys return 400 with field_errors. Inverse-reference keys are rejected the same way. They are queries, not stored values. Read them via /{resource}/{id}/references?attribute=.
Selecting only non-reference keys skips the reference join entirely, which avoids loading every referenced entity just to attach its label.
Response
A page of categories.
Example response
{
"data": [
{
"id": "9b2f6c1e-8a04-4c6e-b0d3-5f2f6f7a9e21",
"type": "product",
"label": "Aria Oak Dining Table",
"parent_id": null,
"created_at": "2026-06-02T14:11:09Z",
"updated_at": "2026-07-18T09:30:22Z",
"attributes": {
"sku": "ARIA-DT-72",
"product_name": "Aria Oak Dining Table",
"price": 1299,
"material": "White Oak",
"vendor": {
"id": "77e1b2aa-6b3d-4f19-9d10-8c2a5e7f4b21",
"type": "vendor",
"label": "Nordic Timber Co."
},
"category": {
"id": "c41d09f3-2e85-47a6-b93c-d5f8e1a20c67",
"type": "category",
"label": "Dining Tables"
},
"gallery_images": [
{
"id": "f0a1b2c3-d4e5-4f60-8a7b-9c0d1e2f3a4b",
"type": "image",
"label": "aria-hero.jpg"
},
{
"id": "f0a2c3d4-e5f6-4a70-9b8c-0d1e2f3a4b5c",
"type": "image",
"label": "aria-detail.jpg"
},
{
"id": "f0a3d4e5-f6a7-4b80-8c9d-1e2f3a4b5c6d",
"type": "image",
"label": "aria-side.jpg"
},
{
"id": "f0a4e5f6-a7b8-4c90-9dae-2f3a4b5c6d7e",
"type": "image",
"label": "aria-lifestyle.jpg"
},
{
"id": "f0a5f6a7-b8c9-4da0-8ebf-3a4b5c6d7e8f",
"type": "image",
"label": "aria-packshot.jpg"
}
]
}
}
],
"pagination": {
"total": 128,
"limit": 50,
"offset": 0,
"has_more": true
}
}Changes
No recorded changes to this operation across all 1 revision of this API.