# Product pages

Fetch one product as its page shows it, with its offer, details and variants, by its id or its URL.

`GET /v1/product` answers one product as its page shows it: text, photos, categories, its offer, its details with labels and units, and its variants. The offer and badges are those its tile shows, so a page and a tile never disagree:

```sh title="Terminal"
curl -s 'http://127.0.0.1:7800/v1/product?id=SOFA-ASKA-2' \
  | jq -c '.product | .title, .badges, .signals, .offer, (.details[] | select(.attribute == "width"))'
```

```json title="Answer: 200 · 5.4 ms · release b25a6aaa"
"Aska 2-Sitzer-Sofa aus Samt"
["Bestseller"]
{"rating":4.3,"rating_count":118}
{"price":749.0,"sale_price":649.0,"currency":"EUR","availability":"in_stock","stock":16,"delivery_days":{"min":3,"max":5}}
{"attribute":"width","label":"Breite","values":[164],"unit":"cm"}
```

The `offer` sums up the [variants](/docs/reference/glossary#variant). It takes the lowest price a shopper pays among those with the best availability, here the sage variant's sale price, and adds up the stock of both.

"Bestseller" is a badge the [release](/docs/reference/glossary#release) gives it in its [overlays](/docs/reference/glossary#overlay), and 4.3 stars from 118 ratings are the catalog's signals. Each detail carries its label in the request's [locale](/docs/reference/glossary#locale) and its unit: Breite (width), 164 cm.

**Reference:** [`GET /v1/product`](/docs/reference/query-api#product) · [`Product`](/docs/reference/query-api#Product) · [`ProductOffer`](/docs/reference/query-api#ProductOffer)

## Variants

Each variant carries what sets it apart and its own offer:

```sh title="Terminal"
curl -s 'http://127.0.0.1:7800/v1/product?id=SOFA-ASKA-2' \
  | jq -c '.product.variants[] | {id, details: [.details[] | {label, values, swatch}],
      offer: (.offer | {price, sale_price, stock} | del(..|nulls))}'
```

```json title="Answer: 200 · 6.2 ms · release b25a6aaa"
{"id":"SOFA-ASKA-2-GRAU","details":[{"label":"Farbe","values":["Grau"],"swatch":"#9B9B9B"}],"offer":{"price":749.0,"stock":12}}
{"id":"SOFA-ASKA-2-SALBEI","details":[{"label":"Farbe","values":["Salbei"],"swatch":"#9CAF92"}],"offer":{"price":749.0,"sale_price":649.0,"stock":4}}
```

The Aska comes in grey (Grau) and sage (Salbei), and only sage is on sale. A color of one value carries the swatch its facet draws, its own or its color group's, so the page draws the same swatches as the filter. What every variant sold in the [channel](/docs/reference/glossary#channel) shares stands in the product's `details`, such as the width.

**Reference:** [`ProductVariant`](/docs/reference/query-api#ProductVariant) · [`ProductDetail`](/docs/reference/query-api#ProductDetail)

## Name it by its URL

A storefront that knows the page's path asks by `url`, in the locale the path belongs to:

```sh title="Terminal"
curl -s 'http://127.0.0.1:7800/v1/product?url=/en/p/aska-two-seater-sofa&locale=en' \
  | jq -c '.product | {id, title, url, alternates, badges}'
```

```json title="Answer: 200 · 5.1 ms · release b25a6aaa"
{"id":"SOFA-ASKA-2","title":"Aska two-seater velvet sofa","url":"/en/p/aska-two-seater-sofa","alternates":{"de":"/p/aska-2-sitzer-sofa"},"badges":["Best seller"]}
```

The English page answers in English, badge included. `alternates` holds the product's URL in each other locale of the channel, for a language switch and `hreflang`. A URL matches only as the catalog writes it: another case or a trailing slash is not found.

**Reference:** [`url`](/docs/reference/query-api#product.url) · [`alternates`](/docs/reference/query-api#Product.alternates)

## When the channel sells no such product

The rug `TEPPICH-SILO` never went live: the [index run](/docs/reference/glossary#index-run) left it out for its broken price. So the channel sells no product by that id:

```sh title="Terminal"
curl -s 'http://127.0.0.1:7800/v1/product?id=TEPPICH-SILO' | jq -c '{status, detail}'
```

```json title="Answer: 404 · 3.8 ms"
{"status":404,"detail":"The channel de sells no product with the id TEPPICH-SILO."}
```

Answer the shopper with your own 404 page; the client's `product` call returns `null` here ([The client](/docs/client)). An engine that does not answer is a `503`, never a `404`, so a crawler is never told a product is gone while Meilisearch is down.

**Reference:** [`404`](/docs/reference/query-api#product.404)

## A product's URL through resolve

A storefront that serves every page by its URL asks resolve, which answers the product with the status, canonical and robots its page needs, in one call:

```sh title="Terminal"
curl -s -G http://127.0.0.1:7800/v1/resolve --data-urlencode 'url=/p/aska-2-sitzer-sofa' \
  | jq -c '{type, status, canonical, robots, product: .product.id}'
```

```json title="Answer: 200 · 5.9 ms · release b25a6aaa"
{"type":"product","status":200,"canonical":"/p/aska-2-sitzer-sofa","robots":"index,follow","product":"SOFA-ASKA-2"}
```

`product` is what `GET /v1/product` answers for it. [URLs and redirects](/docs/urls) shows the route that serves it.

## Next

- [URLs and redirects](/docs/urls): serve product and category pages from the merchant's own URLs.
- [Category pages](/docs/category-pages): the listings a product page links back to.
- [React components](/docs/react): a product page with its gallery, details and variant choice.
