# Products and bundles

> Items used on invoice lines, with VAT category, unit and price.

Source: /docs/guides/products

Products (and services) are the items the invoice editor and the API can put on lines. Referencing an item by `itemId` on an invoice line keeps reporting per product consistent; the line still carries its own description, price and VAT values.

## Fields

| Field | Notes |
| --- | --- |
| `name`, `nameAr`, `description` | Names; the Arabic name is used on bilingual PDFs |
| `type` | `product` (default) or `service` |
| `sku`, `barcode` | Your codes; `GET /products/valid-code?sku=` checks that a SKU is free, `POST` returns `409` on a duplicate |
| `unitCode` | UN/ECE Rec 20 (`EA`, `HUR`, `KGM`, …), default `EA` |
| `unitPrice`, `currency` | Net price, default currency `AED` |
| `taxCategory`, `taxRate` | `standard` (5 %), `zero_rated`, `exempt`, `reverse_charge`; the rate defaults from the category |
| `hsnCode` | HS code (commodity classification, BT-158) |
| `itemNumber` | Your item number; the `item` number series allocates them in the app |
| `gln` | GS1 GLN / GTIN, 8–14 digits |
| `costPrice` | Purchase price; the response adds `margin` in percent of the net price |
| `trackInventory`, `inventoryCount`, `lowStockThreshold` | Stock is reduced by the invoiced quantity when an invoice leaves draft (not for test documents or credit notes) |
| `category` | Free-text group |
| `isActive` | Inactive items stay on old invoices but are hidden from pickers |

## Routes

```bash
curl "$BASE/products?keyword=consulting&sort=unitPrice&order=desc" -H "X-API-KEY: $KEY"
curl -X POST "$BASE/products" -H "X-API-KEY: $KEY" -H "Content-Type: application/json" \
  -d '{ "name": "Consulting hour", "type": "service", "sku": "CONS-H", "unitCode": "HUR", "unitPrice": 500 }'
curl -X POST "$BASE/products/import" -H "X-API-KEY: $KEY" -H "Content-Type: application/json" -d '{ "products": [ … ] }'
```

`DELETE /products/{id}` deactivates items that are used on invoice lines instead of deleting them.

## Price lists

A price list holds net prices per item with optional quantity breaks, a validity window, and either `isDefault: true` (applies to every customer without an assignment) or a list of `customerIds`. A customer can also carry `defaultPriceListId`.

```bash
curl -X POST "$BASE/price-lists" -H "X-API-KEY: $KEY" -H "Content-Type: application/json" -d '{
  "name": "Wholesale 2027", "validFrom": "2027-01-01", "validTo": "2027-12-31",
  "items": [{ "itemId": "…", "unitPrice": 90 }, { "itemId": "…", "unitPrice": 80, "minQuantity": 10 }],
  "customerIds": ["…"]
}'
curl "$BASE/products/{id}/price?customerId=…&quantity=12&date=2027-03-01" -H "X-API-KEY: $KEY"
```

`GET /products/{id}/price` resolves the price the editor would use: customer-assigned lists (highest `priority` first), then default lists, then the item's own price; within a list the row with the largest `minQuantity` that fits wins. The response names the `source` and the list. `PUT /price-lists/{id}/items` and `PUT /price-lists/{id}/customers` replace the prices or the assignment in one call.

Invoice inputs still take an explicit `unitPrice` per line; price lists are a lookup for your integration (and for the editor), not a server-side override.

## Bundles

A bundle is a set of items with quantities. `isSplittable: true` (default) means the bundle is invoiced as one line per component, each priced through the price lists; otherwise it is one line at the bundle price (`priceMode: "sum"` of components or `"fixed"` with `fixedPrice`, plus the bundle's own VAT category).

```bash
curl -X POST "$BASE/bundles" -H "X-API-KEY: $KEY" -H "Content-Type: application/json" \
  -d '{ "name": "Starter kit", "items": [{ "itemId": "…", "quantity": 3 }, { "itemId": "…", "quantity": 1 }] }'
curl "$BASE/bundles/{id}/lines?quantity=2&customerId=…" -H "X-API-KEY: $KEY"
```

`GET /bundles/{id}/lines` returns ready-made `items[]` for `POST /invoices`. MCP: `expand_bundle`, `get_product_price`, `create_price_list`, `create_bundle`.
