---
title: "Get a product"
description: "A product with its option groups. Takes the same `orderType` and `at`\nas the menu; a product hidden at that moment answers\n`\"available\": false`. The quote and the order enforce the same option\nrules.\n"
---

> Documentation Index
> Fetch the complete documentation index at: https://developer.unipage.be/llms.txt
> Use this file to discover all available pages before exploring further.

Path: Storefront API › Menu

`GET /products/{id}`

A product with its option groups. Takes the same `orderType` and `at`
as the menu; a product hidden at that moment answers
`"available": false`. The quote and the order enforce the same option
rules.

## Authentication

Requires one of the following:

- `publicKey`, apiKey, in header, header `X-Unipage-Key`
- `secretKey`, http, header `Authorization`

## Path parameters

- `id` (`getProduct.path.id`; string, required) — The product id.

## Query parameters

- `orderType` (`getProduct.query.orderType`; string, required) — The order type to read for.
  - one of `"TAKEOUT"`, `"DELIVERY"`
- `at` (`getProduct.query.at`; integer, optional) — Unix seconds of the chosen slot, default now. Prices and visibility follow the menu plan of that moment.

## Code samples

### cURL

```bash
curl --request GET \
  --url 'https://api.unipage.eu/storefront/v1/products/p7d2x?orderType=TAKEOUT' \
  --header 'X-Unipage-Key: <X-Unipage-Key>'
```

### TypeScript

```typescript
const url = 'https://api.unipage.eu/storefront/v1/products/p7d2x?orderType=TAKEOUT';
const options = {method: 'GET', headers: {'X-Unipage-Key': '<X-Unipage-Key>'}};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));
```

### Python

```python
import requests

url = "https://api.unipage.eu/storefront/v1/products/p7d2x?orderType=TAKEOUT"

headers = {"X-Unipage-Key": "<X-Unipage-Key>"}

response = requests.get(url, headers=headers)

print(response.text)
```

## Responses

### 200

The product and its option groups.

#### Example

```json
{
  "id": "p7d2x",
  "name": "Medium fries",
  "description": "string",
  "price": 4,
  "priceType": "PER_UNIT",
  "image": "string",
  "available": true,
  "soldOut": true,
  "hasOptions": true,
  "maxPerOrder": 0,
  "allergens": [
    "gluten",
    "nuts"
  ],
  "allergenSources": {
    "gluten": [
      "wheat"
    ],
    "nuts": [
      "almonds"
    ]
  },
  "optionGroups": [
    {
      "id": "g5t1r",
      "name": "Toppings",
      "type": "RADIO",
      "min": 1,
      "max": 3,
      "required": true,
      "options": [
        {
          "id": "o9w3e",
          "name": "Feta",
          "price": 0,
          "available": true
        }
      ]
    }
  ]
}
```

- `id` (`getProduct.response.200.id`; string, optional)
- `name` (`getProduct.response.200.name`; string, optional)
- `description` (`getProduct.response.200.description`; string | null, optional)
- `price` (`getProduct.response.200.price`; number, optional)
- `priceType` (`getProduct.response.200.priceType`; string, optional) — Products priced by weight are not supported.
  - one of `"PER_UNIT"`
- `image` (`getProduct.response.200.image`; string | null, optional) — Image URL.
- `available` (`getProduct.response.200.available`; boolean, optional) — `false` means not orderable right now: show it greyed out.
- `soldOut` (`getProduct.response.200.soldOut`; boolean, optional) — `true` means out of stock.
- `hasOptions` (`getProduct.response.200.hasOptions`; boolean, optional) — Get the product to read its option groups.
- `maxPerOrder` (`getProduct.response.200.maxPerOrder`; integer | null, optional)
- `allergens` (`getProduct.response.200.allergens`; array<string>, optional)
  - one of `"gluten"`, `"crustaceans"`, `"eggs"`, `"fish"`, `"peanuts"`, `"soybeans"`, `"milk"`, `"nuts"`, `"celery"`, `"mustard"`, `"sesame"`, `"sulphur_dioxide"`, `"lupine"`, `"molluscs"`
- `allergenSources` (`getProduct.response.200.allergenSources`; map<array<string>>, optional) — The specific cereal (`wheat`, `spelt`, `khorasan`, `rye`, `barley`, `oats`) or nut (`almonds`, `hazelnuts`, `walnuts`, `cashews`, `pecans`, `brazil_nuts`, `pistachios`, `macadamia`) per allergen, when the restaurant recorded it. EU food-information rules ask you to show it before the order is placed: "gluten (wheat)".
- `optionGroups` (`getProduct.response.200.optionGroups`; array<object>, optional)
  - `id` (`getProduct.response.200.optionGroups.id`; string, optional)
  - `name` (`getProduct.response.200.optionGroups.name`; string, optional)
  - `type` (`getProduct.response.200.optionGroups.type`; string, optional) — - `RADIO`, `SELECT`: one option; required unless `required: false` (then zero or one). - `CHECKBOX`: between `min` and `max` options. - `AMOUNT`: a quantity per option; the sum is between `min` and `max`.
    - one of `"RADIO"`, `"SELECT"`, `"CHECKBOX"`, `"AMOUNT"`
  - `min` (`getProduct.response.200.optionGroups.min`; integer, optional)
  - `max` (`getProduct.response.200.optionGroups.max`; integer | null, optional) — `0` or `null` means no maximum.
  - `required` (`getProduct.response.200.optionGroups.required`; boolean, optional)
  - `options` (`getProduct.response.200.optionGroups.options`; array<object>, optional)
    - `id` (`getProduct.response.200.optionGroups.options.id`; string, optional)
    - `name` (`getProduct.response.200.optionGroups.options.name`; string, optional)
    - `price` (`getProduct.response.200.optionGroups.options.price`; number, optional)
    - `available` (`getProduct.response.200.optionGroups.options.available`; boolean, optional)

### 401

A missing, unknown or revoked key (`UNAUTHENTICATED`).

#### Example

```json
{
  "error": {
    "code": "UNAUTHENTICATED",
    "message": "No valid key."
  }
}
```

- `error` (`getProduct.response.401.error`; object, required)
  - `code` (`getProduct.response.401.error.code`; string, required)
    - one of `"UNAUTHENTICATED"`, `"FORBIDDEN"`, `"NOT_FOUND"`, `"METHOD_NOT_ALLOWED"`, `"VALIDATION"`, `"SLOT_UNAVAILABLE"`, `"DELIVERY_UNAVAILABLE"`, `"PRODUCT_UNAVAILABLE"`, `"COUPON_INVALID"`, `"MINIMUM_NOT_REACHED"`, `"RATE_LIMITED"`, `"INTERNAL"`
  - `message` (`getProduct.response.401.error.message`; string, required) — Human readable.
  - `fields` (`getProduct.response.401.error.fields`; map<string>, optional) — Only present when the error belongs to specific request fields.

### 403

A public key on a secret route, or used from an origin that is not
registered for it (`FORBIDDEN`).

#### Example

```json
{
  "error": {
    "code": "FORBIDDEN",
    "message": "Origin not registered for this key."
  }
}
```

- `error` (`getProduct.response.403.error`; object, required)
  - `code` (`getProduct.response.403.error.code`; string, required)
    - one of `"UNAUTHENTICATED"`, `"FORBIDDEN"`, `"NOT_FOUND"`, `"METHOD_NOT_ALLOWED"`, `"VALIDATION"`, `"SLOT_UNAVAILABLE"`, `"DELIVERY_UNAVAILABLE"`, `"PRODUCT_UNAVAILABLE"`, `"COUPON_INVALID"`, `"MINIMUM_NOT_REACHED"`, `"RATE_LIMITED"`, `"INTERNAL"`
  - `message` (`getProduct.response.403.error.message`; string, required) — Human readable.
  - `fields` (`getProduct.response.403.error.fields`; map<string>, optional) — Only present when the error belongs to specific request fields.

### 404

Unknown route, product or order (`NOT_FOUND`).

#### Example

```json
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Not found."
  }
}
```

- `error` (`getProduct.response.404.error`; object, required)
  - `code` (`getProduct.response.404.error.code`; string, required)
    - one of `"UNAUTHENTICATED"`, `"FORBIDDEN"`, `"NOT_FOUND"`, `"METHOD_NOT_ALLOWED"`, `"VALIDATION"`, `"SLOT_UNAVAILABLE"`, `"DELIVERY_UNAVAILABLE"`, `"PRODUCT_UNAVAILABLE"`, `"COUPON_INVALID"`, `"MINIMUM_NOT_REACHED"`, `"RATE_LIMITED"`, `"INTERNAL"`
  - `message` (`getProduct.response.404.error.message`; string, required) — Human readable.
  - `fields` (`getProduct.response.404.error.fields`; map<string>, optional) — Only present when the error belongs to specific request fields.

### 429

Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds.

#### Example

```json
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Too many requests."
  }
}
```

- `error` (`getProduct.response.429.error`; object, required)
  - `code` (`getProduct.response.429.error.code`; string, required)
    - one of `"UNAUTHENTICATED"`, `"FORBIDDEN"`, `"NOT_FOUND"`, `"METHOD_NOT_ALLOWED"`, `"VALIDATION"`, `"SLOT_UNAVAILABLE"`, `"DELIVERY_UNAVAILABLE"`, `"PRODUCT_UNAVAILABLE"`, `"COUPON_INVALID"`, `"MINIMUM_NOT_REACHED"`, `"RATE_LIMITED"`, `"INTERNAL"`
  - `message` (`getProduct.response.429.error.message`; string, required) — Human readable.
  - `fields` (`getProduct.response.429.error.fields`; map<string>, optional) — Only present when the error belongs to specific request fields.


Source: https://developer.unipage.be/storefront/api/products/retrieve/index.md
