# Store API — options & variants

Manage your Southbill Store catalogue, option groups, variants, orders and evidence with your secret API key.

# Store API — options & variants

Your **Southbill Store** catalogue is reachable with the same secret API key you use for the rest of the REST API. Base URL:

`https://api.southbill.com/v1/store/v1`

Authenticate with `Authorization: Bearer sk_live_…` (server-side only — secret keys are rejected from a browser origin). Write calls accept an `Idempotency-Key` header.

Required scopes: `store:read`, `products:read`, `products:write`, `orders:read`, `orders:write`, `files:write`. Existing keys were upgraded automatically.

## Why variants

A product is the catalogue item ("Runner 01"). A **variant** is the concrete thing a buyer picks — *Size M, Colour Black*. Variants are built from **option groups** (Size) and **option values** (S, M, L). Each variant references exactly one value per option group and may carry its own price, SKU and stock.

```text
Product "Runner 01"
├─ Option group "Size"   → values S, M, L
├─ Option group "Colour" → values Black, White
└─ Variants: S/Black (SKU RUN-S-BLK), M/Black, …
```

## Endpoints

| Method | Path | Description |
|---|---|---|
| `GET` | `/store` | The merchant's store (slug, display name, status, logo/cover URLs). |
| `GET` | `/products` | List products. |
| `GET` | `/products/{id}` | Product incl. `option_groups` and `variants`. |
| `POST` | `/products` | Create a product. |
| `PATCH` | `/products/{id}` | Update a product. |
| `DELETE` | `/products/{id}` | Archive a product. |
| `POST` | `/products/{id}/prices` | Create a price (min 250 minor units). |
| `GET` `POST` | `/products/{id}/options` | List / create option groups. |
| `PATCH` `DELETE` | `/options/{id}` | Rename / delete an option group. |
| `POST` | `/options/{id}/values` | Add a value to a group. |
| `DELETE` | `/values/{id}` | Delete an option value. |
| `GET` `POST` | `/products/{id}/variants` | List / create variants. |
| `PATCH` `DELETE` | `/variants/{id}` | Update / delete a variant. |
| `GET` | `/orders`, `/orders/{id}` | Store orders (source `store` only). |
| `PATCH` | `/orders/{id}` | Fulfillment updates — `shipped`/`completed` need evidence. |
| `POST` | `/orders/{id}/evidence` | Attach delivery evidence. |
| `POST` | `/files` | Upload product images, documents or evidence (max 5 MB). |

## Create an option group

```bash
curl https://api.southbill.com/v1/store/v1/products/PRODUCT_ID/options \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "Size", "values": ["S", "M", "L"] }'
```

```json
{
  "object": "option_group",
  "id": "8df0e320-…",
  "product": "019c72f4-…",
  "name": "Size",
  "values": [
    { "object": "option_value", "id": "9f7e71ab-…", "value": "S", "sort_order": 0 },
    { "object": "option_value", "id": "82170ba4-…", "value": "M", "sort_order": 1 }
  ]
}
```

## Create a variant

```bash
curl https://api.southbill.com/v1/store/v1/products/PRODUCT_ID/variants \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: variant-run01-s-blk" \
  -d '{
    "options": [{ "group": "8df0e320-…", "value": "9f7e71ab-…" }],
    "price": "6040da6b-…",
    "sku": "RUN-S-BLK",
    "track_inventory": true,
    "stock_qty": 12
  }'
```

Rules enforced by the API:

- `options` must contain **exactly one value per option group** of the product.
- A value must belong to the group it is sent with (`invalid_options`, 400).
- The same combination cannot exist twice (`variant_exists`, 409).
- `price` must be an **active price on the same product**; omit it to inherit the product's default price.
- `stock_qty` is required when `track_inventory` is `true`.
- The option combination is immutable — delete and recreate the variant to change it.

## Deleting

Deletion is guarded so your storefront never ends up with dangling variants:

- `DELETE /values/{id}` → `409 value_in_use` while a variant uses that value.
- `DELETE /options/{id}` → `409 variants_exist` while the product still has variants.
- `DELETE /variants/{id}` works at any time, except for the implicit default variant of a product.

## Sandbox

The identical routes exist in the sandbox through an installed app (App API, environment `test`). Sandbox writes never touch a payment provider — the catalogue, variants and orders live in isolated sandbox tables and are wiped on a sandbox reset.

