
# Products

What customers browse on WhatsApp. Changes show up in the chat immediately.

## The product object

```json
{
  "id": "pr_…",
  "object": "product",
  "name": "Unga 2kg",
  "description": "Jogoo, sifted",
  "price": 25000,
  "currency": "KES",
  "active": true,
  "stock": 40,
  "available": 37,
  "image_url": "https://api.kibanda.africa/media/…",
  "code": "UNG4",
  "external_id": "SKU-1001",
  "metadata": {},
  "created": 1792700000,
  "updated": 1792716000
}
```

| Field | |
|---|---|
| `price` | Cents. `25000` = KES 250. |
| `stock` | Units on hand, or `null` to not track stock (never sells out). |
| `available` | `stock` minus units held by open orders. When it hits 0 the product shows as sold out. |
| `code` | Share code: "P-UNG4" sent to the shop opens this product. Also in its `wa.me` share link. |
| `active` | `false` hides it from the menu (kept for order history). |
| `external_id` | Your id (SKU, Shopify variant id, Odoo `product.product` id). Unique per shop. |

Stock is reserved when an order is placed, released if it's cancelled or fails, and sold when it's delivered — two customers can't buy the last one.

## Create a product

`POST /v1/products`

| Parameter | |
|---|---|
| `name` | **Required.** 1–60 characters (WhatsApp shows ~24 in lists). |
| `price` | **Required.** Integer cents, > 0. |
| `description` | ≤ 72 characters. |
| `stock` | Integer ≥ 0, or `null`. |
| `active` | Default `true`. |
| `external_id`, `metadata` | |

## Update a product

`POST /v1/products/{id}` — send only what changes. Setting stock is how you restock:

```bash
curl $KIBANDA_API/v1/products/pr_123 -H "Authorization: Bearer $KIBANDA_KEY" \
  -H "Content-Type: application/json" -d '{"stock": 120}'
```

## Batch upsert

`POST /v1/products/batch` — up to 100 products, matched by `external_id`: existing ones are updated (only the fields you send), new ones created. This is the call for catalog sync.

```json
{ "products": [
  { "external_id": "SKU-1001", "name": "Unga 2kg", "price": 25000, "stock": 40 },
  { "external_id": "SKU-1002", "stock": 0 }
] }
```

Returns a list of the products plus `created` and `updated` counts. If any item is invalid the error's `param` points at it (e.g. `products[3].price`).

## Retrieve, list, archive

- `GET /v1/products/{id}`
- `GET /v1/products` — filters: `active=true|false`, `external_id`, `code`. [Paginated](https://kibanda.africa/docs/pagination), most recently updated first.
- `DELETE /v1/products/{id}` — archives (sets `active: false`).
