> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ya.works/llms.txt
> Use this file to discover all available pages before exploring further.

# Manage Products

> Full CRUD for products, variants, options, media, and categories

# Manage Products

This guide covers all operations to query, create, update, and delete products and their sub-entities.

<Info>
  For all available fields, types, and validations for each endpoint, see the [API Reference](/api-reference).
</Info>

## Important Rules

Before you begin, keep these rules in mind — they apply to all product management:

<Warning>
  **Mandatory creation order:** Product → Options → Variants → Media / Categories. You cannot create variants without options first.
</Warning>

* **Product status:** Currently the only supported value is `"published"`. The status is returned as-is in responses.
* **`option_values` is immutable:** Once a variant is created, you cannot change its `option_values` via PATCH. If you need to fix them, delete the variant and create it again.
* **Supported currencies:** `MXN`, `PEN`, `COP`, `CLP`, `ARS`, `BRL`, `VES`, `BOB`, `USD`, `PYG`, `UYU`, `CRC`, `EUR`.
* **Currency must match the catalog's currency:** All variants of a product must use the same currency configured in the catalog. Using a different currency may cause internal inconsistencies.
* **Responses:** `PATCH` returns `200` with the updated resource. `DELETE` returns `204` with no body.
* **Every product requires at least one variant:** Even simple products (no sizes or colors) need at minimum one option and one variant. Use a generic name like "Presentation" or "Unit".
* **No batch endpoints:** Each entity must be created individually.

## Query Products

### List products

Returns a paginated list of products with all their sub-entities.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.ya.onl/v1/products?page=1&limit=25" \
    -H "X-API-Key: yv_live_v1_your_api_key"
  ```

  ```python Python theme={null}
  import httpx

  headers = {"X-API-Key": "yv_live_v1_your_api_key"}

  response = httpx.get(
      "https://api.ya.onl/v1/products",
      headers=headers,
      params={"page": 1, "limit": 25},
  )
  result = response.json()
  products = result["data"]
  pagination = result["pagination"]
  ```

  ```javascript Node.js theme={null}
  const headers = { "X-API-Key": "yv_live_v1_your_api_key" };

  const response = await fetch(
    "https://api.ya.onl/v1/products?page=1&limit=25",
    { headers }
  );
  const { data: products, pagination } = await response.json();
  ```
</CodeGroup>

The response includes pagination:

```json theme={null}
{
  "data": [ /* array of products */ ],
  "pagination": {
    "page": 1,
    "limit": 25,
    "total_pages": 4,
    "total_items": 100,
    "has_more": true
  }
}
```

<Info>
  `limit` accepts a maximum of `100`. See [Pagination](/pagination) for more details.
</Info>

### Get a product

Returns a product with all its options, variants (with media), and categories.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.ya.onl/v1/products/9876" \
    -H "X-API-Key: yv_live_v1_your_api_key"
  ```

  ```python Python theme={null}
  response = httpx.get(
      f"https://api.ya.onl/v1/products/{product_id}",
      headers=headers,
  )
  product = response.json()
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    `https://api.ya.onl/v1/products/${productId}`,
    { headers }
  );
  const product = await response.json();
  ```
</CodeGroup>

Response example:

```json theme={null}
{
  "id": 9876,
  "created_at": "2026-01-15T10:00:00Z",
  "name": "Polo Negro",
  "description": "Polo de algodón pima de alta calidad",
  "characteristics": "Material: 100% algodón pima. Peso: 180g/m².",
  "status": "published",
  "categories": [
    { "id": 1, "name": "Ropa" }
  ],
  "options": [
    {
      "id": 10,
      "name": "Talla",
      "values": [
        { "id": 100, "name": "S" },
        { "id": 101, "name": "M" },
        { "id": 102, "name": "L" }
      ]
    }
  ],
  "variants": [
    {
      "id": 500,
      "name": "Polo Negro - Talla M",
      "base_price": 49.90,
      "currency": "PEN",
      "status": "available",
      "media": [
        {
          "id": 1000,
          "title": "Front view",
          "url": "https://cdn.example.com/polo-negro-m-front.jpg"
        }
      ]
    }
  ]
}
```

## Create a Complete Product

### 1. Create the product

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.ya.onl/v1/products" \
    -H "X-API-Key: yv_live_v1_your_api_key" \
    -H "Content-Type: application/json" \
    -d '{
      "catalog_id": 1,
      "name": "Polo Negro",
      "description": "Polo de algodón pima de alta calidad",
      "characteristics": "Material: 100% algodón pima. Peso: 180g/m².",
      "status": "published"
    }'
  ```

  ```python Python theme={null}
  import httpx

  headers = {"X-API-Key": "yv_live_v1_your_api_key"}

  response = httpx.post(
      "https://api.ya.onl/v1/products",
      headers=headers,
      json={
          "catalog_id": 1,
          "name": "Polo Negro",
          "description": "Polo de algodón pima de alta calidad",
          "characteristics": "Material: 100% algodón pima. Peso: 180g/m².",
          "status": "published",
      },
  )
  product = response.json()
  product_id = product["id"]
  ```

  ```javascript Node.js theme={null}
  const headers = {
    "X-API-Key": "yv_live_v1_your_api_key",
    "Content-Type": "application/json",
  };

  const response = await fetch("https://api.ya.onl/v1/products", {
    method: "POST",
    headers,
    body: JSON.stringify({
      catalog_id: 1,
      name: "Polo Negro",
      description: "Polo de algodón pima de alta calidad",
      characteristics: "Material: 100% algodón pima. Peso: 180g/m².",
      status: "published",
    }),
  });
  const product = await response.json();
  const productId = product.id;
  ```
</CodeGroup>

### 2. Add options

The response includes the IDs of the created values. **Save these IDs** to use them when creating variants.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.ya.onl/v1/products/9876/options" \
    -H "X-API-Key: yv_live_v1_your_api_key" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Talla",
      "values": ["S", "M", "L"]
    }'
  ```

  ```python Python theme={null}
  response = httpx.post(
      f"https://api.ya.onl/v1/products/{product_id}/options",
      headers=headers,
      json={"name": "Talla", "values": ["S", "M", "L"]},
  )
  option = response.json()
  # option = {"id": 10, "name": "Talla", "values": [
  #   {"id": 100, "name": "S"}, {"id": 101, "name": "M"}, {"id": 102, "name": "L"}
  # ]}
  value_ids = {v["name"]: v["id"] for v in option["values"]}
  ```

  ```javascript Node.js theme={null}
  const optionRes = await fetch(
    `https://api.ya.onl/v1/products/${productId}/options`,
    {
      method: "POST",
      headers,
      body: JSON.stringify({ name: "Talla", values: ["S", "M", "L"] }),
    }
  );
  const option = await optionRes.json();
  // option = {id: 10, name: "Talla", values: [
  //   {id: 100, name: "S"}, {id: 101, name: "M"}, {id: 102, name: "L"}
  // ]}
  const valueIds = Object.fromEntries(
    option.values.map((v) => [v.name, v.id])
  );
  ```
</CodeGroup>

<Info>
  **Need to add more values to an option?** Use `POST /v1/products/{product_id}/options/{option_id}/values` with the new value name (e.g. `{"name": "XL"}`).
</Info>

### 3. Create variants

Each variant must include `option_values` with at least one option value ID.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.ya.onl/v1/products/9876/variants" \
    -H "X-API-Key: yv_live_v1_your_api_key" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Polo Negro - Talla M",
      "base_price": 49.90,
      "currency": "PEN",
      "status": "available",
      "option_values": [101]
    }'
  ```

  ```python Python theme={null}
  response = httpx.post(
      f"https://api.ya.onl/v1/products/{product_id}/variants",
      headers=headers,
      json={
          "name": "Polo Negro - Talla M",
          "base_price": 49.90,
            "currency": "PEN",
          "status": "available",
          "option_values": [value_ids["M"]],
      },
  )
  variant = response.json()
  variant_id = variant["id"]
  ```

  ```javascript Node.js theme={null}
  const variantRes = await fetch(
    `https://api.ya.onl/v1/products/${productId}/variants`,
    {
      method: "POST",
      headers,
      body: JSON.stringify({
        name: "Polo Negro - Talla M",
        base_price: 49.9,
        currency: "PEN",
        status: "available",
        option_values: [valueIds["M"]],
      }),
    }
  );
  const variant = await variantRes.json();
  const variantId = variant.id;
  ```
</CodeGroup>

### 4. Add media to the variant

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.ya.onl/v1/products/9876/variants/500/media" \
    -H "X-API-Key: yv_live_v1_your_api_key" \
    -H "Content-Type: application/json" \
    -d '{
      "title": "Front view",
      "url": "https://cdn.example.com/polo-negro-m-front.jpg"
    }'
  ```

  ```python Python theme={null}
  response = httpx.post(
      f"https://api.ya.onl/v1/products/{product_id}/variants/{variant_id}/media",
      headers=headers,
      json={
          "title": "Front view",
          "url": "https://cdn.example.com/polo-negro-m-front.jpg",
      },
  )
  media = response.json()
  ```

  ```javascript Node.js theme={null}
  const mediaRes = await fetch(
    `https://api.ya.onl/v1/products/${productId}/variants/${variantId}/media`,
    {
      method: "POST",
      headers,
      body: JSON.stringify({
        title: "Front view",
        url: "https://cdn.example.com/polo-negro-m-front.jpg",
      }),
    }
  );
  const media = await mediaRes.json();
  ```
</CodeGroup>

<Warning>
  Media URLs must be **public and accessible**. The system cannot access private or authentication-protected URLs.
</Warning>

<Info>
  **Allowed file types:**

  | Type | Extension | Max size |
  | - | - | - |
  | Image | `jpeg` | 5 MB |
  | Image | `png` | 5 MB |
  | Video | `3gp` | 16 MB |
  | Video | `mp4` | 16 MB |
  | Document | `pdf` | 100 MB |
</Info>

### 5. Assign categories

This endpoint is **idempotent**: if a tag is already assigned, it is skipped and the response is `200`. Only returns `201` when new tags are assigned.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.ya.onl/v1/products/9876/categories" \
    -H "X-API-Key: yv_live_v1_your_api_key" \
    -H "Content-Type: application/json" \
    -d '{"tag_ids": [1, 2]}'
  ```

  ```python Python theme={null}
  response = httpx.post(
      f"https://api.ya.onl/v1/products/{product_id}/categories",
      headers=headers,
      json={"tag_ids": [1, 2]},
  )
  categories = response.json()
  ```

  ```javascript Node.js theme={null}
  const catRes = await fetch(
    `https://api.ya.onl/v1/products/${productId}/categories`,
    {
      method: "POST",
      headers,
      body: JSON.stringify({ tag_ids: [1, 2] }),
    }
  );
  const categories = await catRes.json();
  ```
</CodeGroup>

## Update Entities

<AccordionGroup>
  <Accordion title="Update product">
    <CodeGroup>
      ```bash cURL theme={null}
      curl -X PATCH "https://api.ya.onl/v1/products/9876" \
        -H "X-API-Key: yv_live_v1_your_api_key" \
        -H "Content-Type: application/json" \
        -d '{"name": "Polo Negro Premium"}'
      ```

      ```python Python theme={null}
      response = httpx.patch(
          f"https://api.ya.onl/v1/products/{product_id}",
          headers={"X-API-Key": "yv_live_v1_your_api_key"},
          json={"name": "Polo Negro Premium"},
      )
      ```

      ```javascript Node.js theme={null}
      const response = await fetch(
        `https://api.ya.onl/v1/products/${productId}`,
        {
          method: "PATCH",
          headers: {
            "X-API-Key": "yv_live_v1_your_api_key",
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ name: "Polo Negro Premium", status: "draft" }),
        }
      );
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Update option">
    <CodeGroup>
      ```bash cURL theme={null}
      curl -X PATCH "https://api.ya.onl/v1/products/9876/options/10" \
        -H "X-API-Key: yv_live_v1_your_api_key" \
        -H "Content-Type: application/json" \
        -d '{"name": "Talla (cm)"}'
      ```

      ```python Python theme={null}
      response = httpx.patch(
          f"https://api.ya.onl/v1/products/{product_id}/options/10",
          headers={"X-API-Key": "yv_live_v1_your_api_key"},
          json={"name": "Talla (cm)"},
      )
      ```

      ```javascript Node.js theme={null}
      const response = await fetch(
        `https://api.ya.onl/v1/products/${productId}/options/10`,
        {
          method: "PATCH",
          headers: {
            "X-API-Key": "yv_live_v1_your_api_key",
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ name: "Talla (cm)" }),
        }
      );
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Update option value">
    <CodeGroup>
      ```bash cURL theme={null}
      curl -X PATCH "https://api.ya.onl/v1/products/9876/options/10/values/100" \
        -H "X-API-Key: yv_live_v1_your_api_key" \
        -H "Content-Type: application/json" \
        -d '{"name": "Small"}'
      ```

      ```python Python theme={null}
      response = httpx.patch(
          f"https://api.ya.onl/v1/products/{product_id}/options/10/values/100",
          headers={"X-API-Key": "yv_live_v1_your_api_key"},
          json={"name": "Small"},
      )
      ```

      ```javascript Node.js theme={null}
      const response = await fetch(
        `https://api.ya.onl/v1/products/${productId}/options/10/values/100`,
        {
          method: "PATCH",
          headers: {
            "X-API-Key": "yv_live_v1_your_api_key",
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ name: "Small" }),
        }
      );
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Update variant">
    <CodeGroup>
      ```bash cURL theme={null}
      curl -X PATCH "https://api.ya.onl/v1/products/9876/variants/500" \
        -H "X-API-Key: yv_live_v1_your_api_key" \
        -H "Content-Type: application/json" \
        -d '{"base_price": 59.90, "status": "not_available"}'
      ```

      ```python Python theme={null}
      response = httpx.patch(
          f"https://api.ya.onl/v1/products/{product_id}/variants/500",
          headers={"X-API-Key": "yv_live_v1_your_api_key"},
          json={"base_price": 59.90, "status": "not_available"},
      )
      ```

      ```javascript Node.js theme={null}
      const response = await fetch(
        `https://api.ya.onl/v1/products/${productId}/variants/500`,
        {
          method: "PATCH",
          headers: {
            "X-API-Key": "yv_live_v1_your_api_key",
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ base_price: 59.9, status: "not_available" }),
        }
      );
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Update media">
    <CodeGroup>
      ```bash cURL theme={null}
      curl -X PATCH "https://api.ya.onl/v1/products/9876/variants/500/media/1000" \
        -H "X-API-Key: yv_live_v1_your_api_key" \
        -H "Content-Type: application/json" \
        -d '{"title": "Updated front view"}'
      ```

      ```python Python theme={null}
      response = httpx.patch(
          f"https://api.ya.onl/v1/products/{product_id}/variants/500/media/1000",
          headers={"X-API-Key": "yv_live_v1_your_api_key"},
          json={"title": "Updated front view"},
      )
      ```

      ```javascript Node.js theme={null}
      const response = await fetch(
        `https://api.ya.onl/v1/products/${productId}/variants/500/media/1000`,
        {
          method: "PATCH",
          headers: {
            "X-API-Key": "yv_live_v1_your_api_key",
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ title: "Updated front view" }),
        }
      );
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

## Delete Entities

<AccordionGroup>
  <Accordion title="Remove category from a product">
    This unlinks the category from the product, but **does not delete** the global category.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X DELETE "https://api.ya.onl/v1/products/9876/categories/1" \
        -H "X-API-Key: yv_live_v1_your_api_key"
      ```

      ```python Python theme={null}
      response = httpx.delete(
          f"https://api.ya.onl/v1/products/{product_id}/categories/1",
          headers={"X-API-Key": "yv_live_v1_your_api_key"},
      )
      ```

      ```javascript Node.js theme={null}
      await fetch(
        `https://api.ya.onl/v1/products/${productId}/categories/1`,
        {
          method: "DELETE",
          headers: { "X-API-Key": "yv_live_v1_your_api_key" },
        }
      );
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Delete media">
    <CodeGroup>
      ```bash cURL theme={null}
      curl -X DELETE "https://api.ya.onl/v1/products/9876/variants/500/media/1000" \
        -H "X-API-Key: yv_live_v1_your_api_key"
      ```

      ```python Python theme={null}
      response = httpx.delete(
          f"https://api.ya.onl/v1/products/{product_id}/variants/500/media/1000",
          headers={"X-API-Key": "yv_live_v1_your_api_key"},
      )
      ```

      ```javascript Node.js theme={null}
      await fetch(
        `https://api.ya.onl/v1/products/${productId}/variants/500/media/1000`,
        {
          method: "DELETE",
          headers: { "X-API-Key": "yv_live_v1_your_api_key" },
        }
      );
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Delete variant">
    <Warning>
      Deleting a variant also deletes all its associated media.
    </Warning>

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X DELETE "https://api.ya.onl/v1/products/9876/variants/500" \
        -H "X-API-Key: yv_live_v1_your_api_key"
      ```

      ```python Python theme={null}
      response = httpx.delete(
          f"https://api.ya.onl/v1/products/{product_id}/variants/500",
          headers={"X-API-Key": "yv_live_v1_your_api_key"},
      )
      ```

      ```javascript Node.js theme={null}
      await fetch(
        `https://api.ya.onl/v1/products/${productId}/variants/500`,
        {
          method: "DELETE",
          headers: { "X-API-Key": "yv_live_v1_your_api_key" },
        }
      );
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Delete option value">
    <Warning>
      You cannot delete an option value if it is associated with an active variant.
    </Warning>

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X DELETE "https://api.ya.onl/v1/products/9876/options/10/values/100" \
        -H "X-API-Key: yv_live_v1_your_api_key"
      ```

      ```python Python theme={null}
      response = httpx.delete(
          f"https://api.ya.onl/v1/products/{product_id}/options/10/values/100",
          headers={"X-API-Key": "yv_live_v1_your_api_key"},
      )
      ```

      ```javascript Node.js theme={null}
      await fetch(
        `https://api.ya.onl/v1/products/${productId}/options/10/values/100`,
        {
          method: "DELETE",
          headers: { "X-API-Key": "yv_live_v1_your_api_key" },
        }
      );
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Delete option">
    <Warning>
      Deleting an option also deletes all its values. Variants associated with those values may end up in an inconsistent state.
    </Warning>

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X DELETE "https://api.ya.onl/v1/products/9876/options/10" \
        -H "X-API-Key: yv_live_v1_your_api_key"
      ```

      ```python Python theme={null}
      response = httpx.delete(
          f"https://api.ya.onl/v1/products/{product_id}/options/10",
          headers={"X-API-Key": "yv_live_v1_your_api_key"},
      )
      ```

      ```javascript Node.js theme={null}
      await fetch(
        `https://api.ya.onl/v1/products/${productId}/options/10`,
        {
          method: "DELETE",
          headers: { "X-API-Key": "yv_live_v1_your_api_key" },
        }
      );
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Delete entire product">
    <Warning>
      Deleting a product deletes **all** its variants, options, option values, and media. This action cannot be undone.
    </Warning>

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X DELETE "https://api.ya.onl/v1/products/9876" \
        -H "X-API-Key: yv_live_v1_your_api_key"
      ```

      ```python Python theme={null}
      response = httpx.delete(
          f"https://api.ya.onl/v1/products/{product_id}",
          headers={"X-API-Key": "yv_live_v1_your_api_key"},
      )
      ```

      ```javascript Node.js theme={null}
      await fetch(`https://api.ya.onl/v1/products/${productId}`, {
        method: "DELETE",
        headers: { "X-API-Key": "yv_live_v1_your_api_key" },
      });
      ```
    </CodeGroup>
  </Accordion>
</AccordionGroup>

## Simple Product (no variations)

Not all products have sizes or colors. Even so, every product requires at least **one option and one variant**. Use a generic name for the option.

<CodeGroup>
  ```bash cURL theme={null}
  # 1. Create product
  curl -X POST "https://api.ya.onl/v1/products" \
    -H "X-API-Key: yv_live_v1_your_api_key" \
    -H "Content-Type: application/json" \
    -d '{"catalog_id": 1, "name": "Dona Glaseada", "description": "Dona artesanal con glaseado de azúcar", "status": "published"}'

  # 2. Create generic option
  curl -X POST "https://api.ya.onl/v1/products/9877/options" \
    -H "X-API-Key: yv_live_v1_your_api_key" \
    -H "Content-Type: application/json" \
    -d '{"name": "Presentación", "values": ["Estándar"]}'

  # 3. Create single variant
  curl -X POST "https://api.ya.onl/v1/products/9877/variants" \
    -H "X-API-Key: yv_live_v1_your_api_key" \
    -H "Content-Type: application/json" \
    -d '{"name": "Dona Glaseada", "base_price": 5.00, "currency": "PEN", "option_values": [200]}'

  # 4. Add image
  curl -X POST "https://api.ya.onl/v1/products/9877/variants/600/media" \
    -H "X-API-Key: yv_live_v1_your_api_key" \
    -H "Content-Type: application/json" \
    -d '{"title": "Dona Glaseada", "url": "https://cdn.example.com/dona-glaseada.jpg"}'
  ```

  ```python Python theme={null}
  import httpx

  headers = {"X-API-Key": "yv_live_v1_your_api_key"}
  base = "https://api.ya.onl/v1"

  # 1. Create product
  response = httpx.post(f"{base}/products", headers=headers, json={
      "catalog_id": 1, "name": "Dona Glaseada",
      "description": "Dona artesanal con glaseado de azúcar", "status": "published",
  })
  product_id = response.json()["id"]

  # 2. Create generic option
  response = httpx.post(f"{base}/products/{product_id}/options", headers=headers,
      json={"name": "Presentación", "values": ["Estándar"]})
  value_id = response.json()["values"][0]["id"]

  # 3. Create single variant
  response = httpx.post(f"{base}/products/{product_id}/variants", headers=headers,
      json={"name": "Dona Glaseada", "base_price": 5.00, "currency": "PEN", "option_values": [value_id]})
  variant_id = response.json()["id"]

  # 4. Add image
  httpx.post(f"{base}/products/{product_id}/variants/{variant_id}/media", headers=headers,
      json={"title": "Dona Glaseada", "url": "https://cdn.example.com/dona-glaseada.jpg"})
  ```

  ```javascript Node.js theme={null}
  const headers = { "X-API-Key": "yv_live_v1_your_api_key", "Content-Type": "application/json" };
  const base = "https://api.ya.onl/v1";

  // 1. Create product
  const prodRes = await fetch(`${base}/products`, {
    method: "POST", headers,
    body: JSON.stringify({ catalog_id: 1, name: "Dona Glaseada",
      description: "Dona artesanal con glaseado de azúcar", status: "published" }),
  });
  const productId = (await prodRes.json()).id;

  // 2. Create generic option
  const optRes = await fetch(`${base}/products/${productId}/options`, {
    method: "POST", headers,
    body: JSON.stringify({ name: "Presentación", values: ["Estándar"] }),
  });
  const valueId = (await optRes.json()).values[0].id;

  // 3. Create single variant
  const varRes = await fetch(`${base}/products/${productId}/variants`, {
    method: "POST", headers,
    body: JSON.stringify({ name: "Dona Glaseada", base_price: 5.0, currency: "PEN", option_values: [valueId] }),
  });
  const variantId = (await varRes.json()).id;

  // 4. Add image
  await fetch(`${base}/products/${productId}/variants/${variantId}/media`, {
    method: "POST", headers,
    body: JSON.stringify({ title: "Dona Glaseada", url: "https://cdn.example.com/dona-glaseada.jpg" }),
  });
  ```
</CodeGroup>

<Info>
  The option name is free-form: `"Presentación"`, `"Tipo"`, `"Unidad"`, etc.
</Info>

## Common Errors

Validation errors (`422`) return a body with details about the failing field:

```json theme={null}
{
  "detail": [
    {
      "loc": ["body", "option_values"],
      "msg": "List should have at least 1 item",
      "type": "too_short"
    }
  ]
}
```

<AccordionGroup>
  <Accordion title="422 — &#x22;option_values must have at least 1 item&#x22;">
    You are creating a variant without `option_values` or with an empty list. You must create options first with `POST /v1/products/{product_id}/options` and use the IDs of the returned values.
  </Accordion>

  <Accordion title="Creating variants before options">
    If you try to create a variant before creating options, you will not have value IDs available for the `option_values` field. Always create options first.
  </Accordion>

  <Accordion title="Using name instead of tag_id for categories">
    The `POST /v1/products/{product_id}/categories` endpoint requires `tag_ids` (a list of numeric IDs), not category names. First create the category with `POST /v1/categories` and use the returned `id`.
  </Accordion>

  <Accordion title="Forgetting catalog_id when creating a product">
    The `catalog_id` field is required when creating a product. Get your catalog with `GET /v1/catalogs`.
  </Accordion>

  <Accordion title="Invalid option_value IDs">
    The `option_values` IDs must correspond to values from the same product. Using IDs from another product or nonexistent IDs will result in an error.
  </Accordion>

  <Accordion title="Trying to change option_values of an existing variant">
    `PATCH /v1/products/{product_id}/variants/{variant_id}` does not accept `option_values`. Option values for a variant are assigned only at creation time. If you need to fix them, delete the variant and create it again with the correct values.
  </Accordion>
</AccordionGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.