Skip to main content
Categories and collections are the two storefront grouping resources in the Fluid Storefront v2026-04 API. Both expose two surfaces: a public, unauthenticated catalog you read to render storefront pages, and a company, token-authenticated surface you use to manage every row regardless of whether it is live. This guide covers finding existing categories and collections on both surfaces and creating new ones. Categories and collections share almost the same shape and the same operations. Examples below show categories in full and note where collections differ.

Base URLs and authentication

Public catalog reads resolve the company from the request subdomain, so call them against your company subdomain — for example https://acme.fluid.app. They take no credentials. Company operations require a Bearer token and are called against https://api.fluid.app. Read operations need the storefront.view scope; writes need storefront.update. Legacy per-resource grants (such as categories.update) are still accepted during the migration.
Public operations under /api/v202604/categories, /api/v202604/categories/{slug}, and the collection equivalents are unauthenticated. Sending a token to them is unnecessary. Every /api/v202604/company/... operation requires the token.
Error responses take different shapes by status code: 401 returns a bare {"message": ...} object, 403 returns a bare {"error": ...} or {"message": ...}, and only 404 and 422 use the wrapped error envelope ({"error": {"message": ...}, "status": ..., "meta": ...}). Don’t parse every error with one shape.

Browse the public catalog

GET /api/v202604/categories returns a page of live categories only — rows that are active, resolved-published, country-eligible, and language-eligible. Draft, scheduled, archived, and inactive rows never appear here.
The response wraps the rows in a categories array alongside a top-level status and a meta block:

Filter, search, and sort

The public catalog accepts these query parameters:
string
Free-text search over the translation-aware title and description fields.
string
ISO-2 country code. Restricts results to categories available in that country. Unrestricted categories (those with no country list) match every country. See Control country availability.
string
ISO locale. Restricts results to categories translated in that locale.
string
One of product, medium, page, library, or promotion. Categories only.
string
A category id (as a string), or "root" for top-level categories only. Categories only. See Work with the category hierarchy.
string
One sort key: position, title, or created_at. Prefix with - for descending (for example -created_at). Defaults to position then id.
string
Response locale as an ISO code (for example fr). Omit to use the company default. On the public surface, any field missing in the requested locale falls back to en.

Paginate with cursors

Paging is cursor-based. meta.pagination.next_cursor and prev_cursor carry the cursor for the adjacent page; pass it back as page[cursor]. page[limit] sets the page size (default 25, max 100 — a larger value returns 422).

Collections

GET /api/v202604/collections mirrors the category catalog and accepts the same q, filter[country], filter[language], sort, lang, and pagination parameters. Collections have no hierarchy, so they do not accept filter[parent_id] or filter[source_type], and their rows omit parent_id, position, source_type, and has_children.

Look up one by slug

The slug is the canonical public identifier and matches the row’s canonical_url. Fetch a single category with GET /api/v202604/categories/{slug}:
The category is returned under a category key inside the standard envelope.
The public show endpoints behave differently for non-live rows. A category slug returns 404 for any non-live row, so unlisted categories never reveal their existence. A collection slug returns 404 only for an archived row — draft, scheduled, and inactive collections still resolve here so preview and pre-launch links load (the theme renders them noindex). To inspect a non-public category, use the company surface below.

Find any row as an admin

GET /api/v202604/company/categories returns every category for the calling company, in any lifecycle state (draft, scheduled, published, or archived), so you can manage rows that are not publicly visible.
It accepts the same pagination, q, filter[country], filter[language], sort, and lang parameters as the public catalog, plus two admin-only filters:
string
One of draft, scheduled, published, or archived. Restricts to a single lifecycle state.
string
"true" or "false" to restrict to active or inactive rows. A blank value (filter[active]=) means no filter.
The company index accepts the same filter[parent_id] and filter[source_type] filters as the public catalog — the two surfaces share one query contract. filter[parent_id] selects one level of the tree; filter[source_type] narrows to a single source type. You can also page through the index unfiltered and group client-side by each row’s parent_id. See Work with the category hierarchy.
To fetch a single row as an admin, use the numeric id (the canonical company identifier) with GET /api/v202604/company/categories/{id}. This returns the category in any lifecycle state:
Collections expose the identical company surface at /api/v202604/company/collections and /api/v202604/company/collections/{id}.

Create a category

POST /api/v202604/company/categories creates a category. The request body nests fields under a category key. title is the only required field.
A full create can set the description, slug behavior, lifecycle, country availability, metafields, and SEO in one call:
A successful create returns 201 with the new category under the category key. A few things worth knowing at create time:
  • Slug management. Send slug together with custom_slug: true to pin a permanent, merchant-set slug. Without custom_slug: true, the slug is auto-generated from title and regenerates whenever the title changes. See Rename, publish, and schedule.
  • Lifecycle. status defaults control whether the category is publicly visible. Setting status: "scheduled" with a future publish_at schedules it to go live. See Rename, publish, and schedule.
  • Country availability. country_isos restricts availability; an empty array means available everywhere the company sells. See Control country availability.
  • Nesting. Set parent_id to nest a category under another. See Work with the category hierarchy.
  • Strict bodies. Write bodies are strict: a key the write schema doesn’t define is rejected with 422, not ignored. In particular, don’t echo a response object back into a write — read-only fields like countries and seo are not writable keys.
  • SEO keys differ between write and read. You write SEO as search_engine_optimizer_attributes, but every response returns it under seo. Round-tripping a response’s seo object into a PATCH won’t apply it.
Creating (and later updating) a category automatically queues fresh Lighthouse and compliance scans. Their results become available asynchronously on the company Lighthouse and compliance endpoints — the create response itself does not wait for them.

Create a collection

POST /api/v202604/company/collections mirrors category creation. The body nests under a collection key, and title is again the only required field. Because collections have no hierarchy, the body accepts no parent_id, position, or source_type.

Next steps