# Creating inventory items (/docs/inventory-items)



Use `POST /api/v1/inventory` to create an item and `PUT /api/v1/inventory/{id}` to update it. Send camelCase property names, an `X-API-Key` with `inventory.create` or `inventory.update`, and the location you are acting for in `X-Location-Id`. The REST reference lists every property you can write and every property an item is returned with, each with a description. A returned property is writable only if the write schema lists it; any other key in a write request is ignored, not stored and not reported as an error.

## A minimal item [#a-minimal-item]

This request creates a regular 51.00 SEK item with 12% VAT and nothing else. Replace the location ID with an accessible location:

```json
{
  "locationId": 123,
  "title": "Example coffee",
  "itemType": "regular",
  "pricings": [{ "name": "BASE", "price": 51, "order": 0 }],
  "vatValue": 12
}
```

No tags, category, modifier groups or visibility are needed for an item to have a price and VAT.

## Item type [#item-type]

`itemType` is a catalogue classification string of at most 50 characters. It is not a closed enum: any string is stored unchanged, including `""`. Only recognised values carry behaviour:

| Value       | Effect                                                                                                                                                              |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `regular`   | An ordinary, separately sold product. Use this unless another value applies. `null` and `""` behave the same.                                                       |
| `variant`   | A sub-product that exists only as an option of another product (a modifier choice). It is left out of delivery-marketplace menus and can be hidden from POS search. |
| `delivery`  | A shipping-fee product, selectable as the fee for storefront deliveries.                                                                                            |
| `gift_card` | Each sale issues a gift card for the line amount. Where an integration provides the location's gift cards, the sale loads the integration's card instead.           |

Any other string is stored and returned but changes nothing. Creating an item requires the property; it may be empty.

Three similarly named fields have different contracts:

| Field                                    | Contract                                                                                                                                            |
| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Inventory `itemType` / `items.item_type` | Catalogue string, described above.                                                                                                                  |
| Legacy `item_meta_data.item_type_key`    | Reference to the legacy item-type dictionary; not written by the inventory `itemType` input.                                                        |
| BigQuery `purchase_line_items.item_type` | Recorded sales-line classification; see the [goods export rules](/docs/bigquery#finality-types-and-money). Not interchangeable with catalogue type. |

## Price [#price]

Set the price with `pricings` or an existing `pricingGroupId`, then read the item back to check `calculatedPrice`. `basePrice` is accepted on create for older clients but ignored.

`pricings` is a list of rules applied in `order`, each starting from the result of the one before. `price` is in **currency units**: `51` means 51.00 SEK in an SEK location.

| `name`    | Effect                                                                    | `metadata`                                                                          |
| --------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `BASE`    | Sets the price to `price`. Use one BASE rule for a plain price.           | none                                                                                |
| `FIXED`   | Sets the price to `price`, or adds `price` to it with `mode: "relative"`. | `mode`: `absolute` (default) or `relative`                                          |
| `PERCENT` | Changes the price by `percentage` percent.                                | `percentage`, and optionally `maxAdjustment`, `minPrice`, `maxPrice` in minor units |
| `FORMULA` | Sets the price to the result of `formula`.                                | `formula`, and optionally `variables`                                               |

Any other `name` is stored but does not change the price. `scheduleId` limits a rule to a schedule and `channelTypes` to channels (for example `["POS"]`); with neither, the rule always applies. On update, `pricings` replaces the item's rules; send a rule's `id` back to keep its ID.

A `pricingGroupId` links an existing [shared price](/docs/shared-prices) instead of the item's own rules; it is unrelated to `variantGroupIds`, which link modifier/option groups.

## VAT [#vat]

Set VAT with `vatId` (an ID from `GET /api/v1/vat-rates`) or `vatValue`. If both are sent, `vatId` wins.

`vatValue` has **different units on write and read**:

| Direction             | Unit              | 12% is sent/returned as |
| --------------------- | ----------------- | ----------------------- |
| Write (`POST`, `PUT`) | percentage, 0–100 | `12`                    |
| Read (`GET`)          | basis points      | `1200`                  |

So never send back the `vatValue` you read; use the returned `vatId` instead. Writes reject values above 100.

VAT is not required to save a price. When a create request has no `vatId`, `vatValue` or `accountIds`, the item gets the location country's default VAT, which is often wrong for the product (for example food versus alcohol), so set it explicitly. `takeawayVatId` is a separate rate used when the line is sold as takeaway.

## Tags [#tags]

Send tags as an object keyed by tag type:

```json
{ "CATEGORY_PRIMARY": ["DRINK"], "ALLERGEN": ["NUTS"], "CUSTOM": ["oat-milk"] }
```

* **Tags are references, not upserts.** Tag types and tag keys must already exist. Unknown ones are skipped silently: the request still succeeds and nothing is created. List category tags with `GET /api/v1/category-tags`; create a custom tag first with `POST /api/v1/custom-tags`. Read the item back to confirm which tags were attached.
* **`CUSTOM`** resolves existing custom tags in the `CUISINE` custom-tag category.
* **`tags` replaces.** When an update includes `tags`, every non-custom tag type you leave out is removed, `CATEGORY_PRIMARY` included. To change allergens without losing the category, send both. Omit `CUSTOM` to keep custom tags; `CUSTOM: []` clears them.
* **No tag is required for price or VAT.** `CATEGORY_PRIMARY` groups the item in reports and analytics.
* The legacy string-array shape (`["DRINK"]`) passes validation but matches no tag type, so it removes every non-custom tag. Use the object shape.

## Visibility [#visibility]

`visibility` (`POS`, `KIOSK`, `TAKEAWAY`, `ONLINE`, `GENERAL`) is a legacy field. It is stored but **nothing reads it**: it does not change where the item is sold, and it is not returned when you read the item. To control where an item can be ordered, use:

* channel availability in the menu layouts (returned on the item as `channelAvailability`),
* `POST /api/v1/inventory/{itemId}/availability` with `ACTIVATE` or `DEACTIVATE` to put the item on or take it off sale everywhere,
* `allowedTabTypes` to limit the kinds of tab it can be ordered onto.

`isAvailable` on the item reports the result. Sending `isAvailable` in a create or update request has no effect.

## Existing references [#existing-references]

`variantGroupIds`, `printerGroupIds`, `accountIds`, `servingCenterId`, `pricingGroupId`, `vatId` and `takeawayVatId` all reference **existing** resources; none of them creates one. Create or look up the referenced resource first. On update, a supplied array replaces the item's links and `[]` removes them.

`vatValue` is the one exception: it finds the VAT rate with that value, creating it if needed.

## Free-form objects [#free-form-objects]

Every write property is listed in the reference, and any other key is ignored. Two properties are deliberately free-form, and the reference shows them as such:

* **`metadata`** is yours. Karma stores it as you send it, returns it unchanged and reads none of its keys. Put your own identifiers here, prefixed with your company or system name; keys are case-sensitive, and keep article numbers as strings to preserve leading zeros. Never store secrets.
* **`context`** holds settings for particular kinds of item. Its public key is `gift_card`, the settings of a `gift_card` item (amounts, card types, fees). Karma's own systems keep other keys in it, so treat it as read-modify-write: an update replaces the whole object.

## Updates [#updates]

An update changes only the properties it includes. Relationship arrays, `pricings`, `tags`, `context` and `metadata` replace what is stored. To merge selected top-level `metadata` keys instead, use `PATCH /api/v1/inventory/{id}/metadata`.

## Reading items [#reading-items]

`GET /api/v1/inventory` (list) and `GET /api/v1/inventory/{id}` (single item) return the same item, with a few properties only on one of them; the reference marks those "List endpoint only" or "Single-item GET only". `posIntegration` appears only when the location has a POS integration. Money is in currency units, except properties ending in `Cents`, which are minor units (öre). The exception is `ministockCents`: stock in hundredths of a unit, not money.
