Search Documentation

Search for a documentation page...

Products API

REST API endpoints for browsing, creating, updating, and deleting products.

List Products

GET /api/v1/products

Returns a paginated list of products with variants, categories, and translations.

Query Parameters

ParameterTypeDefaultDescription
limitnumber10Products per page (1-100)
offsetnumber0Number of products to skip
cursorstringProduct UUID for keyset pagination
categorystringFilter by category slug
brandstringFilter by brand slug
querystringSearch term for product name
activebooleanFilter by published status
excludeBundlesbooleanfalseExclude bundle products
includeEventsbooleanfalseInclude event-products (excluded by default — see the Events API)
orderBystringSort field: price, name, or createdAt
orderDirectionstringdescSort direction: asc or desc
currencystringCurrency code for price display (e.g. EUR)
langstringLocale code for translations (e.g. pl-PL)

Response

{
"data": [
{
"id": "0191abc0-1234-7def-8000-000000000001",
"name": "Classic Tee",
"slug": "classic-tee",
"summary": "A comfortable everyday tee",
"images": ["https://cdn.example.com/tee.jpg"],
"status": "published",
"categoryId": "0191abc0-0000-7000-8000-000000000010",
"taxRate": {
"id": "0191abc0-1234-7def-8000-000000000001",
"name": "Standard VAT",
"rate": 23,
"label": null
},
"variants": [
{
"id": "0191abc0-0000-7000-8000-000000000100",
"sku": "CT-SM-BLK",
"price": "2500",
"priceGross": "3075",
"stock": 42
}
],
"translations": []
}
],
"meta": {
"count": 1
}
}

Prices and tax

Prices are stored net — excluding tax — as strings in minor units. Each store also has a tax behavior setting (admin: Settings → Taxes), returned as taxBehavior by GET /api/v1/me and GET /api/v1/settings:

Tax behaviorWhat shoppers seeAt checkout
inclusiveGross prices — tax already includedNothing is added
exclusiveNet prices — tax excludedTax is added on top

So that a storefront never has to redo tax math, every variant carries a gross twin next to each net price field. Gross is net × (1 + rate), rounded by the platform; with no tax rate assigned, gross equals net. Both are strings in minor units.

Net fieldGross twinWhere
pricepriceGrossevery variant
originalPriceoriginalPriceGrossevery variant
calculatedPricecalculatedPriceGrossevery variant
prePromotionPriceprePromotionPriceGrossevery variant
omnibusPriceomnibusPriceGrossproduct detail only
prices[].priceprices[].priceGrossmulti-currency entries
prices[].calculatedPriceprices[].calculatedPriceGrossmulti-currency entries
volumePricingTiers[].pricevolumePricingTiers[].priceGrossproduct detail only
bundle.groups[].items[].variant.price…variant.priceGrossbundle products, from the constituent's own rate
bundleFixedPriceAmountbundleFixedPriceAmountGrossbundle products, product detail only
bundleAmountOffAmountbundleAmountOffAmountGrossbundle products, product detail only

The product itself carries its assigned rate as taxRatenull when the product has none:

{
"taxRate": {
"id": "0191abc0-1234-7def-8000-000000000001",
"name": "Standard VAT",
"rate": 23,
"label": null
}
}

rate is a percentage number (23 = 23%), the same shape as GET /api/v1/tax-rates. The older productTaxRate object is still returned for compatibility; its nested taxRate.rate is the internal representation — percent × 1000, e.g. "23000" — and should not be used for display.

Both taxRate and the gross twins are returned wherever products appear: GET /products, GET /products/:idOrSlug, and the product lists on collections and search. Cart line items carry the same twins on their productVariant — see the Carts API — and so do order line items, priced from the rate frozen into the order snapshot rather than the product's current one; see the Orders API.

Displaying a price

One rule covers every storefront:

const shown = store.taxBehavior === "inclusive" ? variant.priceGross : variant.price;

A Polish store with 23% VAT sells a variant at price: "7236", priceGross: "8900". On inclusive the storefront shows 89,00 zł. Switched to exclusive, the same store shows 72,36 zł and adds 16,64 zł of tax at checkout.


Get Product

GET /api/v1/products/:idOrSlug

Returns a single product by UUID or URL slug, with full variant details, category, collections, and translations.

ParameterTypeDescription
currencystringCurrency code for variant price resolution
langstringLocale code for translated slug lookup

Supports ?lang= query parameter for translated slug lookup when translations are enabled.

Draft visibility by slug: When looking up by slug, the result depends on the authentication method. Operator credentials (OAuth tokens from "Sign in with YNS" apps, or internal tokens used by Elliot) can see draft products by slug — this lets tools read back a just-created draft without publishing it first. Public store API keys (sk- prefix) only see published products when looking up by slug. Lookups by UUID return drafts to any authenticated caller regardless of credential type.

Response

Each variant includes an omnibusPrice field — the lowest price recorded in the last 30 days (EU Omnibus Directive). The value is a string in minor units or null when the store has not enabled omnibus pricing or no historical price exists for that variant. Its gross twin is omnibusPriceGross; see Prices and tax for the full set of net/gross fields and the product-level taxRate.

Bundle products additionally include a bundle object describing the configurable structure: always-included items (forced: true) plus "pick exactly N" choice groups, each with variant price/stock/images. Use it to render and price the bundle on your storefront, then submit the shopper's picks via POST /api/v1/carts.

{
"id": "0191abc0-1234-7def-8000-000000000001",
"name": "Classic Tee",
"slug": "classic-tee",
"summary": "A comfortable everyday tee",
"images": ["https://cdn.example.com/tee.jpg"],
"status": "published",
"category": { "id": "...", "name": "Shirts", "slug": "shirts" },
"taxRate": {
"id": "0191abc0-1234-7def-8000-000000000001",
"name": "Standard VAT",
"rate": 23,
"label": null
},
"variants": [
{
"id": "0191abc0-0000-7000-8000-000000000100",
"sku": "CT-SM-BLK",
"price": "2500",
"priceGross": "3075",
"stock": 42,
"combinations": { "size": "Small", "color": "Black" },
"omnibusPrice": "2200",
"omnibusPriceGross": "2706"
}
],
"collections": [],
"translations": []
}

Bundle object (bundle products only)

When the product's type is "bundle", the response includes a bundle field:

{
"bundle": {
"discountPercentage": 15,
"groups": [
{
"id": "0191abc0-0000-7000-8000-000000000300",
"name": "Pick your size",
"position": 0,
"minQuantity": 1,
"maxQuantity": 1,
"allowDuplicates": false,
"items": [
{
"variantId": "0191abc0-0000-7000-8000-000000000100",
"position": 0,
"forced": false,
"defaultSelected": true,
"fixedQuantity": null,
"maxQuantity": null,
"variant": {
"id": "0191abc0-0000-7000-8000-000000000100",
"price": "2500",
"priceGross": "3075",
"sku": "CT-SM-BLK",
"stock": 42,
"images": [],
"productName": "Classic Tee",
"productSlug": "classic-tee",
"productImages": ["https://cdn.example.com/tee.jpg"],
"taxRate": {
"id": "0191abc0-1234-7def-8000-000000000001",
"name": "Standard VAT",
"rate": 23,
"label": null
}
}
}
]
}
]
}
}

Each group item's variant carries priceGross and the taxRate that produced it from the constituent's own product — a bundle may mix rates, so pricing a 5% book inside a 23% bundle with the bundle's rate would be 18 points too high. The two bundle-level money fields are grossed on the product itself, with the bundle's own rate: bundleFixedPriceAmountGross (for bundlePriceMode: "fixed") and bundleAmountOffAmountGross (for "amount"). Both are null when the bundle has no such amount. See Prices and tax.

Rolling subscriptions

Every product carries subscriptionMode, which says how it may be bought:

ValueMeaning
optionalOne-time purchase or subscription (the default)
onlySubscription required — a plan-less add to cart is refused
rollingA curated box whose contents change every cycle; subscription required

A rolling product's fixed record says nothing about what a shopper actually receives, so GET /api/v1/products/:idOrSlug additionally returns subscriptionCycles with the box shipping now and the one announced next. Only published cycles resolve, and an item whose own product has gone back to draft is dropped from the box. The field is absent entirely for every other product.

{
"subscriptionMode": "rolling",
"subscriptionCycles": {
"current": {
"id": "0191abc0-1234-7def-8000-000000000001",
"productId": "0191abc0-0000-7000-8000-000000000200",
"title": "October 2026",
"description": "Two washed lots picked for filter brewing.",
"image": "https://cdn.example.com/october-box.jpg",
"startsAt": "2026-10-01T00:00:00.000Z",
"endsAt": "2026-11-01T00:00:00.000Z",
"status": "published",
"items": [
{
"productVariantId": "0191abc0-0000-7000-8000-000000000100",
"productId": "0191abc0-0000-7000-8000-000000000300",
"name": "Ethiopia Guji",
"variantLabel": "250g / Whole bean",
"sku": "ETH-GUJI-250",
"image": "https://cdn.example.com/eth-guji.jpg",
"quantity": 1
}
],
"createdAt": "2026-09-01T10:30:00.000Z",
"updatedAt": "2026-09-01T10:30:00.000Z"
},
"upcoming": null
}
}

Either side is null when there is no such box — current: null means the programme is between boxes and POST /api/v1/carts will refuse the subscription until the next one is announced. Manage the calendar with the Subscription Cycles API.


Create Product

POST /api/v1/products

Creates a product in one call. Image URLs are downloaded and re-uploaded to the store's CDN. Categories and collections are found or created automatically when referenced by name.

By default a single default variant is created at the given price. To create a product with multiple variants (e.g. Size × Color), pass the variants array — see the multi-variant example below.

Request Body

FieldTypeRequiredDescription
namestringYesProduct display name
slugstringYesURL-friendly identifier (^[a-z0-9-]+$)
descriptionstringNoPlain text product description
contentobjectNoRich TipTap JSON document ({ "type": "doc", ... }) for the product body
pricenumberYes*Net price as a decimal (e.g. 29.99)
priceGrossnumberNo*Gross price as a decimal — converted to net using the product's tax rate. Send instead of price, never both.
imagesstring[]NoArray of image URLs to upload
categoryIdstringNoCategory UUID — takes priority over category when both are provided
categorystringNoCategory name — created automatically if it doesn't exist
brandIdstringNoBrand UUID — must reference an existing brand (pre-create via the admin)
collectionIdsstring[]NoCollection UUIDs to attach the product to (unioned with collectionNames)
collectionNamesstring[]NoCollection names — created automatically if missing
taxRateIdstringNoTax rate UUID to apply to this product (pre-create via POST /tax-rates)
stocknumberNoInitial inventory quantity. Omit to leave the product untracked — see Inventory tracking below.
quantitynumberNoDeprecated — use stock instead
pricesobjectNoMulti-currency prices (e.g. { "EUR": 25.99, "GBP": 22.50 })
variantsobject[]NoStructured variants — see Multi-Variant Products below
costnumberNoUnit cost of goods (COGS) as a decimal in the store currency (e.g. 12.50). Merchant-private — never returned by read endpoints. Applied to the default variant, or as the fallback for structured variants that omit their own cost.
seoobjectNoSEO overrides — see SEO fields below

* Provide either price or priceGross — sending both is rejected.

The price (and per-variant price) must be large enough to represent at least one minor unit in the store's currency. A value like 0.001 for USD passes the schema's positivity check but rounds to 0 cents — the API returns a 400 with "Price is too small to represent in the store currency".

Writing net or gross prices

Prices are stored net, so price is always the net amount — on POST /products and on each entry of the variants array. Send priceGross instead when you have the shopper-facing gross amount: the platform converts it to net using the product's tax rate — taxRateId from the same request when present, otherwise the product's current rate. With no rate, gross and net are identical. price and priceGross are mutually exclusive in one request.

PATCH /products/:idOrSlug has no price field at all: price lives on the variant, so update prices through the variant endpointsPOST /api/v1/products/:idOrSlug/variants and PATCH /api/v1/variants/:idOrSku. Those take cents, not decimals, so the pair there is priceCents / priceGrossCents (integer minor units) with the same one-or-the-other rule.

See Prices and tax for the read side.

Inventory tracking

Omitting stock is not the same as sending 0:

stockStored asMeaning
omittednullUntracked — the variant sells freely, with no quantity ceiling
00Sold outPOST /api/v1/carts rejects add-to-cart with 409 Insufficient stock
> 0the numberTracked at that quantity

The same rule applies to the per-variant stock in variants, to POST /api/v1/products/:idOrSlug/variants, and to the deprecated quantity. Send an explicit 0 only when you mean the product is unavailable; otherwise leave stock out and start tracking later with PATCH /api/v1/variants/:idOrSku.

Simple product

curl -X POST \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Classic Tee",
"slug": "classic-tee",
"price": 25.00,
"images": ["https://example.com/tee.jpg"],
"category": "Shirts",
"stock": 100,
"prices": { "EUR": 23.00 }
}' \
https://your-store.yns.store/api/v1/products

Response (201)

{
"message": "Product created successfully",
"product": {
"id": "0191abc0-1234-7def-8000-000000000001",
"name": "Classic Tee",
"slug": "classic-tee",
"description": null,
"category": "Shirts",
"collectionIds": [],
"images": ["https://cdn.store.com/tee-abc123.jpg"],
"variant": {
"id": "0191abc0-0000-7000-8000-000000000100",
"price": "2500",
"stock": 100
}
}
}

Multi-Variant Products

Pass the variants array to create a product with real variant types, values, and combinations (e.g. Size × Color). Every variant must define the same set of option keys. The top-level price is used as the default when a variant omits its own price.

Variant object fields

FieldTypeRequiredDescription
optionsobjectYesOption label → value map, e.g. { "Size": "L", "Color": "Red" }
skustringNoVariant SKU (required to sync stock and per-variant prices)
barcodestring | nullNoVariant barcode (EAN, UPC, GTIN, ISBN, or internal)
descriptionstring | nullNoPer-variant description (markdown, max 4000 chars), shown on the product page
pricenumberNoNet variant price as a decimal — defaults to the product price
priceGrossnumberNoGross variant price as a decimal — converted to net using the product's tax rate. Send instead of price, never both.
stocknumberNoVariant inventory quantity. Omit to leave this variant untracked — see Inventory tracking.
imagesstring[]NoVariant-specific image URLs
shippablebooleanNoWhether the variant ships physically (default: true)
weightnumberNoWeight for shipping calculation
widthnumberNoWidth dimension
heightnumberNoHeight dimension
depthnumberNoDepth dimension
costnumberNoPer-variant unit cost of goods (COGS) as a decimal. Falls back to the product-level cost when omitted. Merchant-private — never returned by read endpoints.
pricesobjectNoPer-variant multi-currency prices (e.g. { "EUR": 28.00 })
attributesobject[]NoPer-variant key/value rows shown on the product page, e.g. [{ "key": "Nuty zapachowe", "value": "bergamotka, cedr" }]. Each entry is { key, value, checked? }.

Example

curl -X POST \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Classic Tee",
"slug": "classic-tee",
"price": 25.00,
"category": "Shirts",
"images": ["https://example.com/tee.jpg"],
"variants": [
{ "options": { "Size": "S", "Color": "Black" }, "sku": "CT-S-BLK", "stock": 20, "description": "Heavyweight charcoal colourway.", "attributes": [{ "key": "Material", "value": "220 gsm cotton" }] },
{ "options": { "Size": "S", "Color": "White" }, "sku": "CT-S-WHT", "stock": 15 },
{ "options": { "Size": "M", "Color": "Black" }, "sku": "CT-M-BLK", "price": 27.00, "stock": 30 },
{ "options": { "Size": "M", "Color": "White" }, "sku": "CT-M-WHT", "price": 27.00, "stock": 25 }
]
}' \
https://your-store.yns.store/api/v1/products

Multi-variant response (201)

{
"message": "Product created successfully",
"product": {
"id": "0191abc0-1234-7def-8000-000000000001",
"name": "Classic Tee",
"slug": "classic-tee",
"description": null,
"content": null,
"category": "Shirts",
"collectionIds": [],
"images": ["https://cdn.store.com/tee-abc123.jpg"],
"variantCount": 4
}
}

Update Product

PATCH /api/v1/products/:idOrSlug

Partially updates a product. Only the provided fields are changed.

Request Body

FieldTypeDescription
titlestringNew product name
descriptionstringNew plain text description
contentobjectRich TipTap JSON document ({ "type": "doc", ... }) for the product body
imagesstring[]Replace the product's images. External URLs are uploaded to the store's CDN; URLs already on the CDN pass through. [] removes all images.
statusstringactive (published) or draft
categoryIdstring | nullCategory UUID — takes priority over category; pass null to clear
categorystringCategory name — created automatically if it doesn't exist
brandIdstring | nullBrand UUID — must reference an existing brand; pass null to clear
taxRateIdstring | nullTax rate UUID to apply to this product (create via POST /tax-rates); pass null to clear. Replaces any existing assignment.
collectionIdsstring[]Collection UUIDs. Replaces the product's full membership set ([] removes all). Unioned with collectionNames.
collectionNamesstring[]Collection names — created automatically if missing. Unioned with collectionIds.
eventobjectToggle event/ticketing behaviour — see Event fields below
seoobjectSEO overrides — see SEO fields below

This endpoint does not change prices — price lives on the variant. Use PATCH /api/v1/variants/:idOrSku for an existing variant, or POST /api/v1/products/:idOrSlug/variants for a new one. See Writing net or gross prices.

Event fields

The event object merges onto the product's existing event metadata. Setting enabled: false disables ticketing while preserving the stored details for later re-enabling. Omitted sub-fields keep their current value.

FieldTypeRequiredDescription
enabledbooleanYesEnable or disable event/ticketing behaviour
startsAtstring | nullNoEvent start time as an ISO 8601 string
locationstring | nullNoEvent location
capacitynumber | nullNoMaximum attendees (positive integer, metadata only — not wired to inventory)
guestLabelstring | nullNoDisplay label for the guest line (max 100 chars, e.g. "Speakers")
gueststring | nullNoGuest/speaker name(s) as free text (max 500 chars, comma-separated for multiple)

SEO fields

The seo object sets meta/OG title and description overrides for the product page. On PATCH, it merges onto existing values: omitted fields keep their stored value, null clears a field back to the default (derived from the product name/summary).

FieldTypeDescription
titlestring | nullSEO meta title (null to clear)
descriptionstring | nullSEO meta description (null to clear)
canonicalstring | nullCanonical URL (null to clear)

Basic update

curl -X PATCH \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{"title": "Premium Tee", "status": "active"}' \
https://your-store.yns.store/api/v1/products/classic-tee

Enable event mode

curl -X PATCH \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"event": {
"enabled": true,
"startsAt": "2025-09-20T18:00:00Z",
"location": "Warsaw, Poland",
"capacity": 200,
"guest": "Jane Doe, John Smith"
}
}' \
https://your-store.yns.store/api/v1/products/classic-tee

When the event field is included, the response contains the merged event metadata:

{
"id": "0191abc0-1234-7def-8000-000000000001",
"title": "Classic Tee",
"status": "active",
"event": {
"enabled": true,
"startsAt": "2025-09-20T18:00:00Z",
"location": "Warsaw, Poland",
"capacity": 200,
"guestLabel": null,
"guest": "Jane Doe, John Smith"
}
}

Set SEO overrides

curl -X PATCH \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"seo": {
"title": "Classic Tee — Premium Cotton | Your Store",
"description": "Heavyweight 220 gsm cotton tee in 4 colourways. Free shipping over $50."
}
}' \
https://your-store.yns.store/api/v1/products/classic-tee

When the seo field is included, the response contains the merged SEO metadata:

{
"id": "0191abc0-1234-7def-8000-000000000001",
"title": "Classic Tee",
"status": "active",
"seo": {
"title": "Classic Tee — Premium Cotton | Your Store",
"description": "Heavyweight 220 gsm cotton tee in 4 colourways. Free shipping over $50.",
"canonical": null
}
}

Delete Product

DELETE /api/v1/products/:idOrSlug

Deletes a product if it has no order history. Products with orders are archived (status set to hidden) instead of deleted.

{ "message": "Product deleted" }
// or
{ "message": "Product archived (has order history)" }

Create Variant

POST /api/v1/products/:idOrSlug/variants

Adds a new variant to an existing product.

Request Body

FieldTypeRequiredDescription
skustringYesSKU code
barcodestring | nullNoBarcode (EAN, UPC, GTIN, ISBN, or internal)
descriptionstring | nullNoPer-variant description (markdown, max 4000 chars), shown on the product page
titlestringYesVariant display name (e.g. Large / Red)
priceCentsnumberNo*Net price in cents (e.g. 2999 = $29.99)
priceGrossCentsnumberNo*Gross price in cents — converted to net using the product's assigned tax rate. Send instead of priceCents, never both.
costCentsnumberNoUnit cost of goods in cents (e.g. 1250 = $12.50). Merchant-private.
imageUrlstringNoVariant image URL
stocknumberNoInventory quantity. Omit to create the variant untracked (sells freely); an explicit 0 means sold out — see Inventory tracking.
optionsobjectNoOption label → value map (e.g. { "Zapach": "Black Fig" }). Builds the variant types/values/combinations that drive the storefront picker.
attributesobject[]NoPer-variant key/value rows shown on the product page, e.g. [{ "key": "Nuty zapachowe", "value": "bergamotka, cedr" }]. Each entry is { key, value, checked? }.

* Exactly one of priceCents or priceGrossCents is required — sending both, or neither, is rejected. Unlike POST /products, these are cents (integer minor units), not decimals. Gross is converted to net using the tax rate assigned to the product; with no rate, gross and net are identical. See Prices and tax.

curl -X POST \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{"sku": "CT-LG-RED", "title": "Large / Red", "priceCents": 2500, "stock": 50}' \
https://your-store.yns.store/api/v1/products/classic-tee/variants

Or with the shopper-facing gross amount — at 23% VAT, 3075 gross stores 2500 net:

curl -X POST \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{"sku": "CT-LG-RED", "title": "Large / Red", "priceGrossCents": 3075, "stock": 50}' \
https://your-store.yns.store/api/v1/products/classic-tee/variants

Batch Create Products

POST /api/v1/products/batch

Creates up to 100 products in a single request. Each product gets a default variant. Partial failures are reported in the errors array.

Request Body

JSON array of product objects:

FieldTypeRequiredDescription
namestringYesProduct name
slugstringNoURL slug (auto-generated from name)
summarystringNoShort description
pricenumberYesPrice as decimal
imagesstring[]NoImage URLs
statusstringNodraft, published, or hidden (default: draft)
category_namestringNoCategory name
collection_namesstring[]NoCollection names to add to
skustringNoStock keeping unit
costnumberNoUnit cost of goods (COGS) as a decimal in the store currency. Merchant-private.
stocknumberNoInventory quantity
shippablebooleanNoRequires shipping (default: true)
curl -X POST \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '[
{"name": "Soap A", "price": 12.99, "sku": "SOAP-A", "stock": 50, "cost": 5.00},
{"name": "Soap B", "price": 14.99, "status": "published"}
]' \
https://your-store.yns.store/api/v1/products/batch

Response

{
"message": "Import completed",
"totalProducts": 2,
"imported": 2,
"errors": []
}

Import Products (CSV)

POST /api/v1/products/import
Content-Type: text/plain

Import products from CSV. Supports multi-variant products (multiple rows with the same slug) and updates via product_id. Categories and collections are created or matched automatically.

curl -X POST \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: text/plain" \
--data-binary @products.csv \
https://your-store.yns.store/api/v1/products/import

Response

{
"message": "Import completed",
"totalProducts": 10,
"imported": 10,
"errors": []
}

Product Reviews

List Reviews

GET /api/v1/products/:idOrSlug/reviews

Returns approved reviews with summary statistics.

ParameterTypeDefaultDescription
limitnumber10Reviews per page (1-100)
offsetnumber0Reviews to skip
langstringLocale code for translated slug lookup
{
"data": [
{
"id": "0191abc0-0000-7000-8000-000000000200",
"author": "Jane",
"content": "Love this product!",
"rating": 5,
"createdAt": "2024-06-15T10:30:00.000Z"
}
],
"meta": { "count": 1, "offset": 0, "limit": 10 },
"summary": { "averageRating": 5, "reviewCount": 1 }
}

Create Review

POST /api/v1/products/:idOrSlug/reviews

Submits a review (pending approval by store owner).

FieldTypeRequiredDescription
authorstringYesReviewer name (1-100 chars)
emailstringYesReviewer email (not returned in responses)
contentstringYesReview text (1-5000 chars)
ratingnumberYesStar rating (1-5)

Product Filters

GET /api/v1/products/filters

Returns the facets available for building a storefront filter UI: the store's price range, its variant option types with their values, and the active categories, collections, and brands. Scoped store-wide rather than to a result set, so you can render the filter sidebar before running a search.

Only active categories, collections, and brands are returned, and variant types with no values are omitted.

curl \
-H "Authorization: Bearer your_api_key" \
https://your-store.yns.store/api/v1/products/filters

Response

{
"priceBounds": {
"min": 0,
"max": 24900
},
"variantTypes": [
{
"label": "Size",
"values": ["S", "M", "L"]
},
{
"label": "Color",
"values": ["Black", "White"]
}
],
"categories": [
{ "name": "Candles", "slug": "candles" }
],
"collections": [
{ "name": "Summer", "slug": "summer" }
],
"brands": [
{ "name": "Acme", "slug": "acme" }
]
}

Prices are in cents. Feed the selected values back into List Products using category, collection, brand, vts, and the price range parameters.


Translations

PUT /api/v1/products/:idOrSlug/translations/:locale

Upsert or delete a single locale's translated fields for a product. See the Localization page for the full field reference, behaviour, and examples.


Reset Catalog

POST /api/v1/admin/reset-catalog

Deletes the store's entire catalog and blog in a single transaction. Intended for reseeding a demo or test store, not for routine cleanup.

This is irreversible. Products cascade to their variants, prices, combinations, memberships, favorites, and translations.

Body Parameters

ParameterTypeRequiredDescription
confirmstringYesMust exactly match the authenticated store's UUID

The confirm field is a safety belt: if it doesn't equal the store id behind the API key, the request is refused with 400 and nothing is deleted. Unknown fields are rejected.

curl -X POST \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{"confirm":"0191abc0-1234-7def-8000-000000000001"}' \
https://your-store.yns.store/api/v1/admin/reset-catalog

Response

{
"ok": true,
"deleted": {
"products": 128,
"collections": 6,
"categories": 12,
"posts": 9,
"blog_categories": 3,
"page_section_data": 4
}
}

Orders, customers, and settings are left untouched.

Get your store's UUID from GET /api/v1/me.