List products

Returns a paginated page of products 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:products.

get/products

Request

  • Base URL
    https://www.merchkit.com/api/v1
  • Auth
    HTTP bearer

Query parameters

limitinteger

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.

offsetinteger

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.

sortstring

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).

filterobject

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.

filter_join'and' | 'or'

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.

fieldsstring

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 products.

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.