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

# POST /v1/catalogue/products

> Add a product to the catalogue — it is queued for the account's WhatsApp catalogue.

Adds a product. It needs a SKU and a name at least; money is in minor units. A product on sale is queued for the account's WhatsApp catalogue, and the change is recorded in the product's history under the key's name (as "API · Shopify sync"). Webhooks get `product.changed`.

<Note>Needs the `catalogue.products:write` scope.</Note>

## Body

| Field | Type | Required | Notes |
| :- | :- | :- | :- |
| `sku` | string | Yes | Your product code: up to 100 letters, digits, dots, dashes and underscores, unique in the account. WhatsApp calls it the retailer ID. |
| `name` | string | Yes | What customers see. |
| `description` | string | No | Agents quote it: say what a customer would ask — material, size, care. |
| `price_minor` | integer | No | The price in minor units (paise for `INR`): `249900` is ₹2,499.00. 0 when left out. |
| `sale_price_minor` | integer | No | The sale price in minor units, or `null` when it isn't on sale. While there is one, it is what a customer pays. |
| `currency` | string | No | A three-letter code. The account's catalogue currency when left out. |
| `availability` | string | No | `in_stock` or `out_of_stock`. |
| `stock` | integer | No | How many are left, or `null` to not count them. `0` makes the product `out_of_stock`. |
| `category` | string | No | For finding and grouping products. |
| `brand` | string | No | |
| `url` | string | No | Its page on your website. Without one, customers get its page on your public catalogue. |
| `sets` | array | No | Names of the sets it belongs to — exactly these, up to 20. A name with no set yet makes one. |
| `active` | boolean | No | Whether it is on sale. `true` by default. |

Give it a picture afterwards with [POST /v1/catalogue/products/\{product\_id}/image](/api-reference/catalogue/upload-image).

## Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.fireflo.au/v1/catalogue/products" \
    -H "Authorization: Bearer $OMNI_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
    "sku": "KUR-COT-IND-M",
    "name": "Cotton kurta — indigo, M",
    "description": "Hand-block printed cotton from Jaipur. Machine wash cold.",
    "price_minor": 249900,
    "sale_price_minor": 199900,
    "stock": 18,
    "category": "Kurtas",
    "brand": "Neel Weaves",
    "sets": [
      "Diwali edit"
    ]
  }'
  ```

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

  import requests

  response = requests.post(
      "https://api.fireflo.au/v1/catalogue/products",
      headers={
          "Authorization": f"Bearer {os.environ['OMNI_API_KEY']}",
      },
      json={
          "sku": "KUR-COT-IND-M",
          "name": "Cotton kurta — indigo, M",
          "description": "Hand-block printed cotton from Jaipur. Machine wash cold.",
          "price_minor": 249900,
          "sale_price_minor": 199900,
          "stock": 18,
          "category": "Kurtas",
          "brand": "Neel Weaves",
          "sets": [
              "Diwali edit",
          ],
      },
  )
  print(response.status_code, response.json())
  ```

  ```javascript Node theme={null}
  const response = await fetch("https://api.fireflo.au/v1/catalogue/products", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.OMNI_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      "sku": "KUR-COT-IND-M",
      "name": "Cotton kurta — indigo, M",
      "description": "Hand-block printed cotton from Jaipur. Machine wash cold.",
      "price_minor": 249900,
      "sale_price_minor": 199900,
      "stock": 18,
      "category": "Kurtas",
      "brand": "Neel Weaves",
      "sets": [
        "Diwali edit"
      ]
    }),
  });
  console.log(response.status, await response.json());
  ```
</CodeGroup>

## Response

`201 Created` — the product. `price_minor` and `sale_price_minor` are in minor units — paise for `INR`, so `249900` is ₹2,499.00. `whatsapp.state` is where the product stands with the account's WhatsApp catalogue: `not_synced` (not on WhatsApp — off sale, or no catalogue connected), `pending` (queued to sync), `synced`, or `error` (the sync failed; `whatsapp.error` says why). `has_image` says whether it has a picture.

```json theme={null}
{
  "id": "3f8a2c1e-7b4d-4e6a-9c2f-1a5b8d7e6f40",
  "sku": "KUR-COT-IND-M",
  "name": "Cotton kurta — indigo, M",
  "description": "Hand-block printed cotton from Jaipur. Machine wash cold.",
  "price_minor": 249900,
  "sale_price_minor": 199900,
  "currency": "INR",
  "availability": "in_stock",
  "stock": 18,
  "category": "Kurtas",
  "brand": "Neel Weaves",
  "url": "",
  "has_image": false,
  "sets": [
    "Diwali edit"
  ],
  "active": true,
  "whatsapp": {
    "state": "pending",
    "error": ""
  },
  "updated_at": "2026-10-06T10:24:51+05:30"
}
```

## Errors

Every refusal is `{"error": {"code", "message", "field"}}`; `field` is there when one input is at fault.

| Status | Error code | When |
| :- | :- | :- |
| 400 | `invalid_request` | Something in the body can't be saved, and `message` says what: no SKU or name, a SKU with other characters or that another product already uses, `availability` that isn't `in_stock` or `out_of_stock`, a `stock` that isn't a whole number, a price that isn't a whole number of minor units or is below zero, or `sets` that isn't a list (`field` names it). |
| 404 | `module_off` | The account doesn't have Catalogue on. |
| 403 | `scope_missing` | The key doesn't have the `catalogue.products:write` scope. |

Any request can also be refused for its key, its account or its rate (`key_required`, `invalid_key`, `account_suspended`, `plan_excludes_api`, `address_not_allowed`, `rate_limited`); see [the overview](/api-reference/overview).


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