> ## Documentation Index
> Fetch the complete documentation index at: https://docs.brand-wallet.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a product

> Add a product to your BrandWallet menu. Products power product-filtered campaigns and menu synchronization.

```http theme={null}
POST $BASE_URL/v1/products
```

**Adds a new product to your organization's menu; the record is always created with `status: 1` (published).**

> **Auth:** `Authorization: Bearer <accessToken>` — an organization-scoped token from [Authentication](/api/authentication). Integrator API users carry the product management permission by default.

> Products are also created automatically when you send `items[]` on transactions — see [menu synchronization](/api/process-transaction#menu-synchronization-lazy-sync). Use this endpoint when you want to upload or manage the menu explicitly.

### Payload schema

```json theme={null}
{
  "name": { "TR": "…", "EN": "…" },
  "price": 120,
  "label": { "TR": "…", "EN": "…" },
  "description": "…",
  "details": { "TR": "…", "EN": "…" },
  "categories": ["category-uuid"],
  "tags": ["…"],
  "barcode": "…",
  "sku": "…",
  "opaqueId": "…",
  "assets": [{}],
  "defaultasset": {},
  "attributes": { "key": "value" }
}
```

### Body

All fields except `name` and `price` are optional.

| Field | Type | Description |
| - | - | - |
| `name` | MLString | Required. Language code → text object (`{ "TR": "Latte", "EN": "Latte" }`). Supported language keys: `EN`, `TR`, `AR`, `RU`, `ES`, `IT`, `FR`, `HE`, `DE`, `AZ` — you do not have to send all of them. |
| `price` | number | Required. The product's list price. |
| `label` | MLString | Short label. |
| `description` | string | Free-text description. |
| `details` | MLString | Multi-language detail text. |
| `categories` | string\[] | Category ids — see [Categories](/api/list-categories). |
| `tags` | string\[] | Free-form tags. |
| `barcode` | string | Product barcode. |
| `sku` | string | Stock keeping unit. |
| `opaqueId` | string | Your external product id (used for [menu sync](/api/process-transaction#menu-synchronization-lazy-sync) matching). Unique within the organization — a second create with the same `opaqueId` returns `opaque_id_already_exists` (400). |
| `assets` | object\[] | Media list. |
| `defaultasset` | object | Default image. |
| `attributes` | object | Free key-value data; values may be string, number, or boolean. |

> `status` is not accepted in the body — records are always created with `1` (use the [delete endpoint](/api/delete-product) to remove a product). `organization` is derived from your token; you cannot create products for another organization. Fields outside the schema are silently dropped; type errors return 400 with the individual validation messages joined in `message` (e.g. `name must be an object / price must be a number`).

### Example

```bash theme={null}
curl --request POST \
  --url "$BASE_URL/v1/products" \
  --header 'Authorization: Bearer <accessToken>' \
  --header 'Content-Type: application/json' \
  --data '{
    "name": { "TR": "Latte", "EN": "Latte" },
    "price": 120,
    "categories": ["9e2b4c1d-7f3a-4e8b-a5d6-2c9f0b1e4a77"],
    "sku": "LATTE-M",
    "opaqueId": "pos-item-9001"
  }'
```

### Response

Returns **201** with the created product document.

```json theme={null}
{
  "id": "1f64ec1b-445e-4762-a8c3-0d1e6d75afe6",
  "name": { "TR": "Latte", "EN": "Latte" },
  "price": 120,
  "organization": "07f84834-1184-4eb6-82f5-7eda13231fc3",
  "categories": ["9e2b4c1d-7f3a-4e8b-a5d6-2c9f0b1e4a77"],
  "tags": [],
  "sku": "LATTE-M",
  "opaqueId": "pos-item-9001",
  "status": 1,
  "createdAt": "2026-09-18T08:24:08.415Z",
  "updatedAt": "2026-09-18T08:24:08.415Z"
}
```

Notes:

* `organization` is set server-side from your token — trust the values in the response, not what you sent.
* Optional fields you did not send are omitted from the response — do not expect `null`.
* Errors use the standard [error envelope](/api/errors).
