> ## 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/import

> Add or update many products at once from a CSV, matched on the SKU.

Adds or updates products from a CSV file, sent as a multipart form with the file in the field `file`. Each row is matched on its SKU: a SKU the account has updates that product, a new one adds a product. Every row is checked before anything is saved, so a file with a bad row changes nothing. The panel's **Export CSV** gives a file with the same columns.

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

## Form fields

| Field | Type | Required | Notes |
| :- | :- | :- | :- |
| `file` | file | Yes | A UTF-8 CSV, 2 MB or smaller, with up to 5,000 rows below its header. |

## Columns

Only `sku` is required; the other columns are optional and may come in any order.

| Column | Notes |
| :- | :- |
| `sku` | The product code — up to 100 letters, digits, dots, dashes and underscores. |
| `name` | Needed for a new product. |
| `description` | |
| `price` | In rupees (or the currency's main unit), not minor units: `2499.00`. |
| `sale_price` | Also in rupees. An empty cell clears the sale price. |
| `currency` | A three-letter code. |
| `availability` | `in_stock` or `out_of_stock`. |
| `stock` | A whole number. An empty cell stops counting; `0` makes the product `out_of_stock`. |
| `category` | |
| `brand` | |
| `url` | Its page on your website. |
| `sets` | Set names, separated by vertical bars (see the sample below). The product is put in exactly these sets. |

An empty cell in the other columns leaves what the product has. Empty rows are skipped.

```csv theme={null}
sku,name,price,sale_price,stock,category,sets
KUR-COT-IND-M,"Cotton kurta — indigo, M",2499.00,1999.00,18,Kurtas,Diwali edit
DUP-PHUL-RED,Phulkari dupatta — red,1799.00,,6,Dupattas,Diwali edit | Bestsellers
```

Products on sale are queued for the WhatsApp catalogue. An import doesn't send `product.changed` for each row it saves.

## Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.fireflo.au/v1/catalogue/products/import" \
    -H "Authorization: Bearer $OMNI_API_KEY" \
    -F "file=@products.csv;type=text/csv"
  ```

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

  import requests

  with open("products.csv", "rb") as upload:
      response = requests.post(
          "https://api.fireflo.au/v1/catalogue/products/import",
          headers={"Authorization": f"Bearer {os.environ['OMNI_API_KEY']}"},
          files={"file": ("products.csv", upload, "text/csv")},
      )
  print(response.status_code, response.json())
  ```

  ```javascript Node theme={null}
  import { readFile } from "node:fs/promises";

  const form = new FormData();
  form.append("file", new Blob([await readFile("products.csv")], { type: "text/csv" }), "products.csv");

  const response = await fetch("https://api.fireflo.au/v1/catalogue/products/import", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.OMNI_API_KEY}` },
    body: form,
  });
  console.log(response.status, await response.json());
  ```
</CodeGroup>

## Response

`200 OK` — how many rows were read, and how many products were added and updated.

```json theme={null}
{
  "created": 12,
  "updated": 236,
  "rows": 248
}
```

## Errors

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

| Status | Error code | When |
| :- | :- | :- |
| 400 | `invalid_request` | The file can't be imported, and nothing was saved (`field` is `file`): no `file` field, larger than 2 MB, not UTF-8, no `sku` column, more than 5,000 rows, or a row that can't be saved — `message` names it, as in "Row 14: A SKU is up to 100 letters, digits, dots, dashes and underscores." |
| 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.