# Products · Content Delivery API

> Content Delivery API reference for products: GET /products, GET /products/{slug}.

> Markdown version of https://kanorio.com/en/developers/delivery-api/products. Append `.md` to any developer docs URL for this format. Index: https://kanorio.com/llms.txt · MCP: https://app.kanorio.com/api/mcp

Base URL: `https://{site}/api/v1` · [Errors](https://kanorio.com/en/developers/basics/errors) · [Pagination](https://kanorio.com/en/developers/basics/pagination)

### Published products listed in the shop (no deliverable outline)

`GET /products`

- Stability: beta
- Authentication: none

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | Maximum number of items to return, 1-100. Defaults to 20. |
| `cursor` | query | string | no | Opaque cursor from the previous page's nextCursor. Omit for the first page. |
| `category` | query | string | no | — |
| `author` | query | string | no | Author/instructor slug (system collection authors). Returns only products linked to an active deliverable this person teaches; use it to list a person's courses on an instructor page. |
| `locale` | query | string | no | Locale for product names, descriptions, options and the category record, and for promotion badge text. One of the site's locales from /site (a code such as `en-US` or the URL prefix such as `en`); omit it for the primary locale. Other values, including locales that are still translating, return 400 UNSUPPORTED_LOCALE. Untranslated fields fall back to the primary locale. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `data` | object[] | yes | — |
| `data[].resolvedLocale` | string | no | Multilingual sites only: the locale this product was delivered in (the requested locale when it has a published translation, otherwise the primary locale). |
| `data[].availableLocales` | string[] | no | Multilingual sites only: locales with a published translation of this product (the primary locale is not listed). Use it for hreflang and canonical. |
| `data[].localePaths` | object | no | Multilingual sites only: this product's path in every live locale, keyed by locale code, without the locale prefix (each locale can have its own translated slug). Use it for hreflang, a language switcher and sitemap alternates. |
| `data[].id` | string | yes | — |
| `data[].slug` | string | yes | — |
| `data[].title` | string | yes | — |
| `data[].description` | string \| null | no | — |
| `data[].type` | string | no | — |
| `data[].currency` | string | yes | — |
| `data[].images` | object[] | no | — |
| `data[].options` | object[] | no | Option dimensions (0–3). Each pricing tier's `optionValues` lists one value per option, in this order. With `locale`, `name` and `values` are translated and tier `optionValues` use the same translated strings. |
| `data[].options[].id` | string | no | Stable option id (the key for option translations) |
| `data[].options[].name` | string | yes | — |
| `data[].options[].values` | string[] | yes | — |
| `data[].pricingTiers` | object[] | yes | — |
| `data[].allowCustomAmount` | boolean | no | — |
| `data[].canPurchase` | boolean | yes | — |
| `data[].deliverables` | object[] | no | — |
| `data[].deliverables[].id` | string | yes | Deliverable id. Pass it to <kanorio-curriculum deliverable> or the Customer API. |
| `data[].deliverables[].title` | string | yes | — |
| `data[].deliverables[].description` | string \| null | yes | — |
| `data[].deliverables[].structure` | "COURSE" \| "DOWNLOAD" | yes | — |
| `data[].deliverables[].coverAssetId` | string \| null | yes | — |
| `data[].deliverables[].cover` | object \| null | no | Cover image of the deliverable (null when unset or the image is in the trash). |
| `data[].deliverables[].authors` | object[] | no | Authors/instructors in the owner's order (shared site author list). Older servers omit it. |
| `data[].deliverables[].unitCount` | integer | yes | — |
| `data[].deliverables[].totalDurationSeconds` | number \| null | yes | — |
| `data[].deliverables[].units` | object[] | yes | — |
| `data[].updatedAt` | string | no | — |
| `data[].seo` | object \| null | no | Product SEO (title, description, Open Graph, robots). Null when unset. |
| `data[].pageSeo` | object \| null | no | Alias of seo, kept for the hosted storefront. |
| `hasMore` | boolean | yes | True when another page exists; pass nextCursor to fetch it. |
| `nextCursor` | string \| null | yes | Cursor for the next page, or null on the last page. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `UNSUPPORTED_LOCALE` | 400 | — |
| `INVALID_CURSOR` | 400 | — |

```bash
curl -X GET "https://{site}/api/v1/products"
```

### One product, including the public outline of its deliverables

`GET /products/{slug}`

- Stability: beta
- Authentication: none

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | string | yes | — |
| `locale` | query | string | no | Locale for the product name, description, SEO fields, options and deliverable outline, and for promotion badge text. One of the site's locales from /site (a code such as `en-US` or the URL prefix such as `en`); omit it for the primary locale. Other values, including locales that are still translating, return 400 UNSUPPORTED_LOCALE. Untranslated fields fall back to the primary locale. On multilingual sites the product carries resolvedLocale and availableLocales. |

**Response** (200)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `product` | object | yes | — |
| `product.resolvedLocale` | string | no | Multilingual sites only: the locale this product was delivered in (the requested locale when it has a published translation, otherwise the primary locale). |
| `product.availableLocales` | string[] | no | Multilingual sites only: locales with a published translation of this product (the primary locale is not listed). Use it for hreflang and canonical. |
| `product.localePaths` | object | no | Multilingual sites only: this product's path in every live locale, keyed by locale code, without the locale prefix (each locale can have its own translated slug). Use it for hreflang, a language switcher and sitemap alternates. |
| `product.id` | string | yes | — |
| `product.slug` | string | yes | — |
| `product.title` | string | yes | — |
| `product.description` | string \| null | no | — |
| `product.type` | string | no | — |
| `product.currency` | string | yes | — |
| `product.images` | object[] | no | — |
| `product.options` | object[] | no | Option dimensions (0–3). Each pricing tier's `optionValues` lists one value per option, in this order. With `locale`, `name` and `values` are translated and tier `optionValues` use the same translated strings. |
| `product.options[].id` | string | no | Stable option id (the key for option translations) |
| `product.options[].name` | string | yes | — |
| `product.options[].values` | string[] | yes | — |
| `product.pricingTiers` | object[] | yes | — |
| `product.allowCustomAmount` | boolean | no | — |
| `product.canPurchase` | boolean | yes | — |
| `product.deliverables` | object[] | no | — |
| `product.deliverables[].id` | string | yes | Deliverable id. Pass it to <kanorio-curriculum deliverable> or the Customer API. |
| `product.deliverables[].title` | string | yes | — |
| `product.deliverables[].description` | string \| null | yes | — |
| `product.deliverables[].structure` | "COURSE" \| "DOWNLOAD" | yes | — |
| `product.deliverables[].coverAssetId` | string \| null | yes | — |
| `product.deliverables[].cover` | object \| null | no | Cover image of the deliverable (null when unset or the image is in the trash). |
| `product.deliverables[].authors` | object[] | no | Authors/instructors in the owner's order (shared site author list). Older servers omit it. |
| `product.deliverables[].unitCount` | integer | yes | — |
| `product.deliverables[].totalDurationSeconds` | number \| null | yes | — |
| `product.deliverables[].units` | object[] | yes | — |
| `product.updatedAt` | string | no | — |
| `product.seo` | object \| null | no | Product SEO (title, description, Open Graph, robots). Null when unset. |
| `product.pageSeo` | object \| null | no | Alias of seo, kept for the hosted storefront. |

**Errors**

| Code | Status | When |
| --- | --- | --- |
| `PRODUCT_NOT_FOUND` | 404 | — |
| `UNSUPPORTED_LOCALE` | 400 | — |

```bash
curl -X GET "https://{site}/api/v1/products/{slug}"
```
