REST API / Categories
REST API / Categories
Categories
Note
2026-05: 14,606 categories under 26 verticals, up to 8 levels deep. It is read-only over the API, and identical for every reseller and supplier — a category id means the same thing in your storefront as it does in the supplier's catalogue.Find a category
Search the taxonomy and copy the breadcrumb exactly as the importer expects it. Pasting the copied value into the Category column of a product CSV is the one way to name a category that can't be misread — every string this returns is byte-identical to what the importer matches against.
Endpoint
/v1/categoriesList categories
Returns the full category tree, nested under their parents. The tree is large — 14,606 categories across 26 top-level verticals, up to 8 levels deep — so treat this as a bulk reference you fetch and cache, not something you call on every page view.
Response
{
"data": [
{
"id": "2ab8df9b-b3ea-4977-ac42-af8c1fa189e3",
"name": "Apparel & Accessories",
"slug": "apparel-accessories-aa",
"description": null,
"children": [
{
"id": "dd943db3-a583-453a-acdb-0f80d4f3aa5d",
"name": "Clothing",
"slug": "clothing-aa-1",
"description": null,
"children": [
{
"id": "638ce987-1534-4a42-bfa8-9778eb9d5870",
"name": "Pants",
"slug": "pants-aa-1-12",
"description": null,
"children": [
{
"id": "1e14381d-8b7c-4b12-b117-d57d50bf9de6",
"name": "Joggers",
"slug": "joggers-aa-1-12-7",
"description": null,
"children": []
}
]
}
]
}
]
}
]
}That example is one branch, truncated for readability. Clothing alone has 425 categories under it.
Identifying a category
A category has several identifiers and they are not interchangeable. Three of the four below are unique across all 14,606 categories. name is not, and that is the single most common mistake made against this API.
idUniquefc78e0cc-4921-47b2-8718-b2c89264abccslugUniquet-shirts-aa-1-13-8breadcrumbUniqueApparel & Accessories > Clothing > Clothing Tops > T-ShirtsnameNot uniqueT-Shirtsdescription is on the response but is null for every category in the taxonomy — don't build UI that depends on it.
Warning
T-Shirts:Match on id or on the full breadcrumb. A lookup by name will silently pick the wrong one.
Deriving the breadcrumb and the Shopify code
The response gives you id, name, slug, description and children. The breadcrumb, the depth and the Shopify taxonomy gid are not fields on it — but you don't need them to be, because all three fall out of one walk of the tree. Build this index once, cache it, and look categories up by id from then on.
type Category = {
id: string;
name: string;
slug: string;
description: string | null;
children: Category[];
};
const res = await fetch("https://api.feedapi.co.uk/v1/categories", {
headers: { "x-api-key": process.env.FEEDAPI_KEY! },
});
const { data }: { data: Category[] } = await res.json();
const index = new Map<
string,
{ breadcrumb: string; depth: number; shopifyGid: string }
>();
function walk(nodes: Category[], trail: string[]) {
for (const node of nodes) {
const path = [...trail, node.name];
// Every slug ends with the category's Shopify taxonomy code:
// "t-shirts-aa-1-13-8" -> "aa-1-13-8". The number of segments in
// that code is the category's depth.
const code = node.slug.match(/([a-z]{2}(?:-\d+)*)$/)?.[1] ?? "";
index.set(node.id, {
breadcrumb: path.join(" > "),
depth: path.length - 1,
shopifyGid: `gid://shopify/TaxonomyCategory/${code}`,
});
walk(node.children, path);
}
}
walk(data, []);
index.get("fc78e0cc-4921-47b2-8718-b2c89264abcc");
// {
// breadcrumb: "Apparel & Accessories > Clothing > Clothing Tops > T-Shirts",
// depth: 3,
// shopifyGid: "gid://shopify/TaxonomyCategory/aa-1-13-8"
// }The slug rule holds for every category in the taxonomy: the trailing aa-1-13-8 is the Shopify code, and its segment count is the depth. That gives you a clean join onto anything else built on the same taxonomy — a Shopify store, a Google Merchant feed — without us having to hand you a mapping table.
The shape of the tree
Depth is not a proxy for how specific a category is, and products are not all filed at the leaves. Expect to receive categories at any depth.
The 26 verticals
Every category descends from one of these. The counts show how much tree sits underneath each, which is worth knowing before you decide to render any of it — one vertical alone accounts for over a fifth of the taxonomy.
sg3,079hg2,285ae1,256el1,175ha1,121hb905fb763aa662vp646bi593fr473ap417tg270bt251os242co211se55so48me37lb36ma36rc12pa7bu—gc—na—Bundles, Gift Cards and Uncategorized have no children at all — a product filed under one of them is at a vertical, not in a leaf.
Don't render the whole tree
The full nested response is roughly 2 MB of JSON, and the overwhelming majority of it describes categories that no product on the platform is filed under. A menu built by walking the tree is mostly dead ends.
Build navigation from your own catalogue instead. Every product carries its category, so the set of categories worth showing is exactly the set your products reference — usually a few dozen, not 14,606.
// 14,606 categories exist. Only the ones your own products actually
// reference belong in a menu — build it from your catalogue, then use
// the tree for labels and ordering.
const inUse = new Map<
string,
{ name: string; breadcrumb: string; products: number }
>();
for await (const product of paginate("/products")) {
const category = product.category;
if (!category) continue; // category is optional — always guard
const entry = inUse.get(category.id) ?? {
name: category.name,
breadcrumb: index.get(category.id)?.breadcrumb ?? category.name,
products: 0,
};
entry.products += 1;
inUse.set(category.id, entry);
}
// Sort by breadcrumb and you have a menu that mirrors the taxonomy's
// own ordering, with nothing empty in it.
const menu = [...inUse.values()].sort((a, b) =>
a.breadcrumb.localeCompare(b.breadcrumb)
);How a product ends up in a category
Suppliers pick a category in the dashboard, or name one in a CSV import. For CSV, the text in the Category column is resolved in this order, and the first rule that matches wins.
Saved mapping
If the supplier has previously confirmed what a given piece of text means, that mapping wins. Case-insensitive.Exact breadcrumb
The full path, matched exactly — Apparel & Accessories > Clothing > Clothing Tops > T-Shirts. This is the only unambiguous way to name a category in a CSV.Exact name, if globally unique
A bare name is accepted only when exactly one category in the whole taxonomy carries it. T-Shirts is rejected at this step; Beeswax is accepted.Unresolved
The row is flagged with the closest breadcrumb as a suggestion. The suggestion is never applied automatically — a wrong guess would silently mis-file the product, so a human confirms it.Two consequences worth designing for. A product's category can be broader than you would file it yourself, because a supplier naming T-Shirts gets nothing and has to choose a specific branch. And category can be null — nothing forces a supplier to categorise a product, so guard for it rather than assuming every product has one.