# Inventory

Menu items, products, and modifiers

## GET /api/v1/inventory

List inventory items

Retrieve a paginated list of inventory items with filtering and search capabilities

**Permissions:** 🔵 `inventory.read`

Query parameters:
- `titleFilter` (string, optional)
- `titleMatch` ("contains" | "eq", optional)
- `pricingName` (string, optional)
- `pricingMatch` ("contains" | "eq", optional)
- `source` ("local" | "inherited", optional)
- `minPrice` (number, optional) — Minimum effective price in currency units for the requested channel
- `maxPrice` (number, optional) — Maximum effective price in currency units for the requested channel
- `locationId` (number, optional) — Filter by location ID
- `page` (number, optional) — Page number
- `limit` (number, optional) — Items per page
- `sort` (string, optional) — Field to sort by (title, calculatedPrice, pricing, source, basePrice, createdAt)
- `order` ("asc" | "desc", optional) — Sort order
- `deleted` (boolean, optional) — Include deleted items
- `search` (string, optional) — Search term for title/description
- `ids` (integer[], optional) — Return only these inventory item IDs. Repeat the query parameter for multiple IDs.
- `tags` (string[], optional) — Filter by tags
- `itemType` (string, optional) — Filter by item type
- `excludeUnmapped` (boolean, optional) — When true and the location has an active POS integration, drop items that lack an integration_resource_mappings row. Used by storefront-web to hide items that would otherwise produce empty kitchen tickets.
- `metadata` (string, optional) — JSON-encoded jsonb-containment filter on `items.metadata`. Example: `?metadata={"someKey":"value"}`. The dotted shorthand `?metadata.someKey=value` is also accepted (all dotted values arrive as strings — use the JSON form for non-string values).
- `channel` (string, optional) — Channel context for pricing. Defaults to ONLINE. Use POS, KIOSK, TAKEAWAY, or any custom channel name (e.g. FOODORA) to get channel-specific calculatedPrice.

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    items: Array<{
        customFields?: unknown[]
        metadata: { … }
        pricingGroupId?: string | null // uuid
        pricingGroupName?: string | null
        measurement?: { … } | null
        measuredStockAtoms?: integer | null
        guestInput?: { … }
        id: number
        locationId: number
        title: string
        description?: string | null
        calculatedPricesByChannel?: { … }
        calculatedPrice?: number
        itemType?: string | null
        vatId?: integer | null
        vatValue?: number | null
        takeawayVatId?: integer | null
        takeawayVatValue?: number | null
        takeawayAccountId?: string | null
        manualIsAvailable?: boolean
        ingredientPaused?: boolean
        isAvailable?: boolean
        allowedTabTypes?: unknown[] | null
        ean?: string | null
        deleted: boolean
        madeToOrder: boolean
        tags?: { … }
        customTagDetails?: unknown[]
        createdDate: string // date-time
        updatedAt: string // date-time
        ingredients?: string | null
        imageUrl?: string | null
        externalImageUrl?: string | null
        images?: unknown[] | null
        integrationId?: number | null
        integrationItemId?: string | null
        context?: { … } | null
        pricings?: unknown[]
        purchaseCostCents?: number | null
        ministock?: number | null
        ministockCents?: number | null
        isAlcohol?: boolean
        variablePricing?: boolean | null
        minPriceCents?: number | null
        maxPriceCents?: number | null
        variableQuantity?: boolean
        tagIds?: unknown[]
        accountIds?: unknown[]
        variantGroupIds?: unknown[]
        printerGroupIds?: unknown[]
        servingCenterId?: number | null
        titlePrep?: string | null
        prepTimeMinutes?: number | null
        coursingOrder?: number | null
        preparation?: string | null
        printPreparation?: boolean
        color?: string | null
        kitchenSortOrder?: integer | null
        scheduleRules?: unknown[] | null
        isInherited?: boolean
        inheritToChildren?: boolean
        parentItemId?: number | null
        sourceLocationId?: number | null
        overriddenFields?: unknown[] | null
        allowedOverrides?: unknown[] | null
        inheritanceUpdatedAt?: string | null // date-time
        isPosSynced?: boolean
        posOverriddenFields?: unknown[]
        channelAvailability?: { … }
        posIntegration?: { … }
      }>
    pagination: {
      page: number
      limit: number
      total: number
      totalPages: number
    }
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v1/inventory

Create inventory item

Add a new menu item or product to the inventory

**Permissions:** 🟢 `inventory.create`

Custom fields: send customFields: [{ "key": "your_field_key", "value": "your_value", "expectedVersion": 0 }]. Use either fieldId or a stable key. expectedVersion is optional; supply the last-read version to reject stale edits. Omitted fields are unchanged; null clears an optional value. See [Custom field inputs](/docs/custom-fields#values-on-normal-create-and-update-calls).

Request body:

```ts
{
  ingredients?: string | null
  imageUrl?: string | null
  externalImageUrl?: string | null
  integrationId?: number | null
  integrationItemId?: string | null
  context?: {
    gift_card?: {
      template_id?: integer
      preset_amounts_cents?: integer[]
      allow_digital?: boolean
      allow_physical?: boolean
      allow_pickup?: boolean
      allow_delivery?: boolean
      delivery_fee_cents?: integer
      physical_card_fee_cents?: integer
      recipient_instructions?: string | null
    }
  } | string | null
  images?: Array<{
      url?: string
      name?: string
      size?: number
      type?: string
    }> | null
  customFields?: unknown | unknown[]
  classification?: {
    primary: string
    secondary?: string | null
    tertiary?: string | null
  } | null
  purchaseCostCents?: number | null
  deleted?: boolean
  madeToOrder?: boolean
  articleNumber?: string | null
  pricings?: Array<{
      id?: string
      price?: number
      name?: string
      order?: number
      metadata?: {
        mode?: "absolute" | "relative"
        percentage?: number
        maxAdjustment?: number
        minPrice?: number
        maxPrice?: number
        formula?: string
        variables?: { … }
      } | null
      scheduleId?: string | null
      channelTypes?: string[] | null
    }> | null
  takeawayVatId?: integer | null
  takeawayAccountId?: string | null
  ministock?: number | null
  ministockCents?: number | null
  servingCenterId?: number | null
  titlePrep?: string | null
  prepTimeMinutes?: number | null
  coursingOrder?: number | null
  preparation?: string | null
  printPreparation?: boolean
  color?: string | null
  kitchenSortOrder?: integer | null
  isAlcohol?: boolean
  variablePricing?: boolean | null
  variableQuantity?: boolean
  minPriceCents?: number | null
  maxPriceCents?: number | null
  locationId: number
  pricingGroupId?: string | null // uuid
  measurement?: {
    unit: "mg" | "g" | "hg" | "kg" | "ml" | "cl" | "dl" | "l" | "mm" | "cm" | "m"
    priceQuantityMicros: integer
    defaultAmountMicros: integer
    storefrontInput: boolean
  } | null
  measuredStockAtoms?: integer | null
  title: string
  description?: string | null
  basePrice?: number
  itemType: string
  tags?: string[] | Record<string, unknown>
  vatId?: integer | null
  vatValue?: number | null
  accountIds?: string[]
  printerGroupIds?: string[]
  variantGroupIds?: number[]
  visibility?: "POS" | "KIOSK" | "TAKEAWAY" | "ONLINE" | "GENERAL"[]
  allowedTabTypes?: "table" | "over_the_counter" | "takeaway"[] | null
  ean?: string | null
  metadata?: Record<string, unknown>
}
```

Response 201 — Default Response

```ts
{
  status: "success"
  data: {
    customFields?: Array<{
        fieldId?: string
        key?: string
        label?: string
        group?: string
        type?: string
        displayValue?: string
        value?: unknown // The stored value; its JSON type follows the field type
        version?: integer
        status?: string
      }>
    metadata: {
      customFields?: Record<string, unknown>
    }
    pricingGroupId?: string | null // uuid
    pricingGroupName?: string | null
    measurement?: {
      unit: "mg" | "g" | "hg" | "kg" | "ml" | "cl" | "dl" | "l" | "mm" | "cm" | "m"
      priceQuantityMicros: integer
      defaultAmountMicros: integer
      storefrontInput: boolean
    } | null
    measuredStockAtoms?: integer | null
    guestInput?: {
      schemaToken: string
      fields: { … }[]
    }
    id: number
    locationId: number
    title: string
    description?: string | null
    calculatedPricesByChannel?: Record<string, unknown>
    calculatedPrice?: number
    itemType?: string | null
    vatId?: integer | null
    vatValue?: number | null
    takeawayVatId?: integer | null
    takeawayVatValue?: number | null
    takeawayAccountId?: string | null
    manualIsAvailable?: boolean
    ingredientPaused?: boolean
    isAvailable?: boolean
    allowedTabTypes?: "table" | "over_the_counter" | "takeaway"[] | null
    ean?: string | null
    deleted: boolean
    madeToOrder: boolean
    tags?: Record<string, unknown>
    customTagDetails?: Array<{
        id: number
        key: string
        categoryHandle: string
      }>
    createdDate: string // date-time
    updatedAt: string // date-time
    ingredients?: string | null
    imageUrl?: string | null
    externalImageUrl?: string | null
    images?: Array<{
        url?: string
        name?: string
        size?: number
        type?: string
      }> | null
    integrationId?: number | null
    integrationItemId?: string | null
    context?: {
      gift_card?: {
        template_id?: integer
        preset_amounts_cents?: unknown[]
        allow_digital?: boolean
        allow_physical?: boolean
        allow_pickup?: boolean
        allow_delivery?: boolean
        delivery_fee_cents?: integer
        physical_card_fee_cents?: integer
        recipient_instructions?: string | null
      }
    } | null
    pricings?: Array<{
        id?: string // uuid
        price?: number
        name?: string
        order?: number
        metadata?: { … } | null
        validFrom?: string | null
        validTo?: string | null
        scheduleId?: string | null
        channelTypes?: unknown[] | null
        scheduleActive?: boolean
      }>
    purchaseCostCents?: number | null
    ministock?: number | null
    ministockCents?: number | null
    isAlcohol?: boolean
    variablePricing?: boolean | null
    minPriceCents?: number | null
    maxPriceCents?: number | null
    variableQuantity?: boolean
    tagIds?: number[]
    accountIds?: string[]
    variantGroupIds?: number[]
    printerGroupIds?: string[]
    servingCenterId?: number | null
    titlePrep?: string | null
    prepTimeMinutes?: number | null
    coursingOrder?: number | null
    preparation?: string | null
    printPreparation?: boolean
    color?: string | null
    kitchenSortOrder?: integer | null
    scheduleRules?: Array<{
        id?: string
        dayOfWeek?: integer
        startTime?: string
        endTime?: string
        active?: boolean
      }> | null
    isInherited?: boolean
    inheritToChildren?: boolean
    parentItemId?: number | null
    sourceLocationId?: number | null
    overriddenFields?: string[] | null
    allowedOverrides?: string[] | null
    inheritanceUpdatedAt?: string | null // date-time
    isPosSynced?: boolean
    posOverriddenFields?: string[]
    channelAvailability?: {
      byChannel?: {
        KIOSK?: boolean
        POS?: boolean
        ONLINE?: boolean
        TAKEAWAY?: boolean
        WOLT?: boolean
        FOODORA?: boolean
        UBER_EATS?: boolean
      }
      online?: boolean
      qr?: boolean
      kiosk?: boolean
      pos?: boolean
      wolt?: boolean
      foodora?: boolean
      uberEats?: boolean
    }
    posIntegration?: {
      integrationConfigId: number
      provider: string
      externalItemId: string | null
      isMapped: boolean
    }
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## GET /api/v1/inventory/{id}

Get inventory item

Retrieve detailed information about a specific inventory item by its ID

**Permissions:** 🔵 `inventory.read`

Path parameters:
- `id` (string, required) — Inventory item ID

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    customFields?: Array<{
        fieldId?: string
        key?: string
        label?: string
        group?: string
        type?: string
        displayValue?: string
        value?: unknown // The stored value; its JSON type follows the field type
        version?: integer
        status?: string
      }>
    metadata: {
      customFields?: Record<string, unknown>
    }
    pricingGroupId?: string | null // uuid
    pricingGroupName?: string | null
    measurement?: {
      unit: "mg" | "g" | "hg" | "kg" | "ml" | "cl" | "dl" | "l" | "mm" | "cm" | "m"
      priceQuantityMicros: integer
      defaultAmountMicros: integer
      storefrontInput: boolean
    } | null
    measuredStockAtoms?: integer | null
    guestInput?: {
      schemaToken: string
      fields: { … }[]
    }
    id: number
    locationId: number
    title: string
    description?: string | null
    calculatedPricesByChannel?: Record<string, unknown>
    calculatedPrice?: number
    itemType?: string | null
    vatId?: integer | null
    vatValue?: number | null
    takeawayVatId?: integer | null
    takeawayVatValue?: number | null
    takeawayAccountId?: string | null
    manualIsAvailable?: boolean
    ingredientPaused?: boolean
    isAvailable?: boolean
    allowedTabTypes?: "table" | "over_the_counter" | "takeaway"[] | null
    ean?: string | null
    deleted: boolean
    madeToOrder: boolean
    tags?: Record<string, unknown>
    customTagDetails?: Array<{
        id: number
        key: string
        categoryHandle: string
      }>
    createdDate: string // date-time
    updatedAt: string // date-time
    ingredients?: string | null
    imageUrl?: string | null
    externalImageUrl?: string | null
    images?: Array<{
        url?: string
        name?: string
        size?: number
        type?: string
      }> | null
    integrationId?: number | null
    integrationItemId?: string | null
    context?: {
      gift_card?: {
        template_id?: integer
        preset_amounts_cents?: unknown[]
        allow_digital?: boolean
        allow_physical?: boolean
        allow_pickup?: boolean
        allow_delivery?: boolean
        delivery_fee_cents?: integer
        physical_card_fee_cents?: integer
        recipient_instructions?: string | null
      }
    } | null
    pricings?: Array<{
        id?: string // uuid
        price?: number
        name?: string
        order?: number
        metadata?: { … } | null
        validFrom?: string | null
        validTo?: string | null
        scheduleId?: string | null
        channelTypes?: unknown[] | null
        scheduleActive?: boolean
      }>
    purchaseCostCents?: number | null
    ministock?: number | null
    ministockCents?: number | null
    isAlcohol?: boolean
    variablePricing?: boolean | null
    minPriceCents?: number | null
    maxPriceCents?: number | null
    variableQuantity?: boolean
    tagIds?: number[]
    accountIds?: string[]
    variantGroupIds?: number[]
    printerGroupIds?: string[]
    servingCenterId?: number | null
    titlePrep?: string | null
    prepTimeMinutes?: number | null
    coursingOrder?: number | null
    preparation?: string | null
    printPreparation?: boolean
    color?: string | null
    kitchenSortOrder?: integer | null
    scheduleRules?: Array<{
        id?: string
        dayOfWeek?: integer
        startTime?: string
        endTime?: string
        active?: boolean
      }> | null
    isInherited?: boolean
    inheritToChildren?: boolean
    parentItemId?: number | null
    sourceLocationId?: number | null
    overriddenFields?: string[] | null
    allowedOverrides?: string[] | null
    inheritanceUpdatedAt?: string | null // date-time
    isPosSynced?: boolean
    posOverriddenFields?: string[]
    channelAvailability?: {
      byChannel?: {
        KIOSK?: boolean
        POS?: boolean
        ONLINE?: boolean
        TAKEAWAY?: boolean
        WOLT?: boolean
        FOODORA?: boolean
        UBER_EATS?: boolean
      }
      online?: boolean
      qr?: boolean
      kiosk?: boolean
      pos?: boolean
      wolt?: boolean
      foodora?: boolean
      uberEats?: boolean
    }
    posIntegration?: {
      integrationConfigId: number
      provider: string
      externalItemId: string | null
      isMapped: boolean
    }
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## PUT /api/v1/inventory/{id}

Update inventory item

Modify an existing inventory item's details

**Permissions:** 🟠 `inventory.update`

Custom fields: send customFields: [{ "key": "your_field_key", "value": "your_value", "expectedVersion": 0 }]. Use either fieldId or a stable key. expectedVersion is optional; supply the last-read version to reject stale edits. Omitted fields are unchanged; null clears an optional value. See [Custom field inputs](/docs/custom-fields#values-on-normal-create-and-update-calls).

Path parameters:
- `id` (string, required) — Inventory item ID

Request body:

```ts
{
  ingredients?: string | null
  imageUrl?: string | null
  externalImageUrl?: string | null
  integrationId?: number | null
  integrationItemId?: string | null
  context?: {
    gift_card?: {
      template_id?: integer
      preset_amounts_cents?: integer[]
      allow_digital?: boolean
      allow_physical?: boolean
      allow_pickup?: boolean
      allow_delivery?: boolean
      delivery_fee_cents?: integer
      physical_card_fee_cents?: integer
      recipient_instructions?: string | null
    }
  } | string | null
  images?: Array<{
      url?: string
      name?: string
      size?: number
      type?: string
    }> | null
  customFields?: unknown | unknown[]
  classification?: {
    primary: string
    secondary?: string | null
    tertiary?: string | null
  } | null
  purchaseCostCents?: number | null
  deleted?: boolean
  madeToOrder?: boolean
  articleNumber?: string | null
  pricings?: Array<{
      id?: string
      price?: number
      name?: string
      order?: number
      metadata?: {
        mode?: "absolute" | "relative"
        percentage?: number
        maxAdjustment?: number
        minPrice?: number
        maxPrice?: number
        formula?: string
        variables?: { … }
      } | null
      scheduleId?: string | null
      channelTypes?: string[] | null
    }> | null
  takeawayVatId?: integer | null
  takeawayAccountId?: string | null
  ministock?: number | null
  ministockCents?: number | null
  servingCenterId?: number | null
  titlePrep?: string | null
  prepTimeMinutes?: number | null
  coursingOrder?: number | null
  preparation?: string | null
  printPreparation?: boolean
  color?: string | null
  kitchenSortOrder?: integer | null
  isAlcohol?: boolean
  variablePricing?: boolean | null
  variableQuantity?: boolean
  minPriceCents?: number | null
  maxPriceCents?: number | null
  tags?: string[] | Record<string, unknown>
  visibility?: "POS" | "KIOSK" | "TAKEAWAY" | "ONLINE" | "GENERAL"[]
  vatId?: integer | null
  vatValue?: number | null
  accountIds?: string[]
  printerGroupIds?: string[]
  variantGroupIds?: number[]
  pricingGroupId?: string | null // uuid
  measurement?: {
    unit: "mg" | "g" | "hg" | "kg" | "ml" | "cl" | "dl" | "l" | "mm" | "cm" | "m"
    priceQuantityMicros: integer
    defaultAmountMicros: integer
    storefrontInput: boolean
  } | null
  measuredStockAtoms?: integer | null
  title?: string
  description?: string | null
  itemType?: string
  ean?: string | null
  allowedTabTypes?: "table" | "over_the_counter" | "takeaway"[] | null
  metadata?: Record<string, unknown>
}
```

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    customFields?: Array<{
        fieldId?: string
        key?: string
        label?: string
        group?: string
        type?: string
        displayValue?: string
        value?: unknown // The stored value; its JSON type follows the field type
        version?: integer
        status?: string
      }>
    metadata: {
      customFields?: Record<string, unknown>
    }
    pricingGroupId?: string | null // uuid
    pricingGroupName?: string | null
    measurement?: {
      unit: "mg" | "g" | "hg" | "kg" | "ml" | "cl" | "dl" | "l" | "mm" | "cm" | "m"
      priceQuantityMicros: integer
      defaultAmountMicros: integer
      storefrontInput: boolean
    } | null
    measuredStockAtoms?: integer | null
    guestInput?: {
      schemaToken: string
      fields: { … }[]
    }
    id: number
    locationId: number
    title: string
    description?: string | null
    calculatedPricesByChannel?: Record<string, unknown>
    calculatedPrice?: number
    itemType?: string | null
    vatId?: integer | null
    vatValue?: number | null
    takeawayVatId?: integer | null
    takeawayVatValue?: number | null
    takeawayAccountId?: string | null
    manualIsAvailable?: boolean
    ingredientPaused?: boolean
    isAvailable?: boolean
    allowedTabTypes?: "table" | "over_the_counter" | "takeaway"[] | null
    ean?: string | null
    deleted: boolean
    madeToOrder: boolean
    tags?: Record<string, unknown>
    customTagDetails?: Array<{
        id: number
        key: string
        categoryHandle: string
      }>
    createdDate: string // date-time
    updatedAt: string // date-time
    ingredients?: string | null
    imageUrl?: string | null
    externalImageUrl?: string | null
    images?: Array<{
        url?: string
        name?: string
        size?: number
        type?: string
      }> | null
    integrationId?: number | null
    integrationItemId?: string | null
    context?: {
      gift_card?: {
        template_id?: integer
        preset_amounts_cents?: unknown[]
        allow_digital?: boolean
        allow_physical?: boolean
        allow_pickup?: boolean
        allow_delivery?: boolean
        delivery_fee_cents?: integer
        physical_card_fee_cents?: integer
        recipient_instructions?: string | null
      }
    } | null
    pricings?: Array<{
        id?: string // uuid
        price?: number
        name?: string
        order?: number
        metadata?: { … } | null
        validFrom?: string | null
        validTo?: string | null
        scheduleId?: string | null
        channelTypes?: unknown[] | null
        scheduleActive?: boolean
      }>
    purchaseCostCents?: number | null
    ministock?: number | null
    ministockCents?: number | null
    isAlcohol?: boolean
    variablePricing?: boolean | null
    minPriceCents?: number | null
    maxPriceCents?: number | null
    variableQuantity?: boolean
    tagIds?: number[]
    accountIds?: string[]
    variantGroupIds?: number[]
    printerGroupIds?: string[]
    servingCenterId?: number | null
    titlePrep?: string | null
    prepTimeMinutes?: number | null
    coursingOrder?: number | null
    preparation?: string | null
    printPreparation?: boolean
    color?: string | null
    kitchenSortOrder?: integer | null
    scheduleRules?: Array<{
        id?: string
        dayOfWeek?: integer
        startTime?: string
        endTime?: string
        active?: boolean
      }> | null
    isInherited?: boolean
    inheritToChildren?: boolean
    parentItemId?: number | null
    sourceLocationId?: number | null
    overriddenFields?: string[] | null
    allowedOverrides?: string[] | null
    inheritanceUpdatedAt?: string | null // date-time
    isPosSynced?: boolean
    posOverriddenFields?: string[]
    channelAvailability?: {
      byChannel?: {
        KIOSK?: boolean
        POS?: boolean
        ONLINE?: boolean
        TAKEAWAY?: boolean
        WOLT?: boolean
        FOODORA?: boolean
        UBER_EATS?: boolean
      }
      online?: boolean
      qr?: boolean
      kiosk?: boolean
      pos?: boolean
      wolt?: boolean
      foodora?: boolean
      uberEats?: boolean
    }
    posIntegration?: {
      integrationConfigId: number
      provider: string
      externalItemId: string | null
      isMapped: boolean
    }
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## DELETE /api/v1/inventory/{id}

Delete inventory item

Soft-delete an inventory item (marks as deleted without removing from database)

**Permissions:** 🔴 `inventory.delete`

Path parameters:
- `id` (string, required) — Inventory item ID

Response 200 — Item successfully deleted

```ts
{
  status?: "success"
  data?: {
    deleted?: boolean
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## PUT /api/v1/inventory/{id}/inheritance

Update item inheritance

Update inheritance settings for an inventory item in a multi-location hierarchy

**Permissions:** 🟠 `inventory.update`

Path parameters:
- `id` (string, required) — Inventory item ID

Request body:

```ts
{
  inheritToChildren?: boolean
  allowedOverrides?: "title" | "pricings" | "description" | "ingredients" | "is_available" | "image_url" | "ministock" | "printer_group_ids"[]
}
```

Response 200 — Default Response

```ts
{
  status?: "success"
  data?: {
    id?: number
    inheritToChildren?: boolean
    allowedOverrides?: string[]
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## PATCH /api/v1/inventory/{id}/metadata

Merge metadata into an inventory item

Stores customer- or integration-owned identifiers on an inventory item without requiring dedicated schema fields.

The supplied top-level keys are shallow-merged into the existing metadata object. Existing keys omitted from the request are preserved; supplied keys replace values with the same name.

Use provider- or company-prefixed key names to avoid collisions. Do not store credentials, access tokens, payment data, or other secrets in metadata.

**Permissions:** 🟠 `inventory.update`

Path parameters:
- `id` (string, required) — Inventory item ID

Request body:

```ts
{
  metadata: Record<string, unknown>
}
```

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    customFields?: Array<{
        fieldId?: string
        key?: string
        label?: string
        group?: string
        type?: string
        displayValue?: string
        value?: unknown // The stored value; its JSON type follows the field type
        version?: integer
        status?: string
      }>
    metadata: {
      customFields?: Record<string, unknown>
    }
    pricingGroupId?: string | null // uuid
    pricingGroupName?: string | null
    measurement?: {
      unit: "mg" | "g" | "hg" | "kg" | "ml" | "cl" | "dl" | "l" | "mm" | "cm" | "m"
      priceQuantityMicros: integer
      defaultAmountMicros: integer
      storefrontInput: boolean
    } | null
    measuredStockAtoms?: integer | null
    guestInput?: {
      schemaToken: string
      fields: { … }[]
    }
    id: number
    locationId: number
    title: string
    description?: string | null
    calculatedPricesByChannel?: Record<string, unknown>
    calculatedPrice?: number
    itemType?: string | null
    vatId?: integer | null
    vatValue?: number | null
    takeawayVatId?: integer | null
    takeawayVatValue?: number | null
    takeawayAccountId?: string | null
    manualIsAvailable?: boolean
    ingredientPaused?: boolean
    isAvailable?: boolean
    allowedTabTypes?: "table" | "over_the_counter" | "takeaway"[] | null
    ean?: string | null
    deleted: boolean
    madeToOrder: boolean
    tags?: Record<string, unknown>
    customTagDetails?: Array<{
        id: number
        key: string
        categoryHandle: string
      }>
    createdDate: string // date-time
    updatedAt: string // date-time
    ingredients?: string | null
    imageUrl?: string | null
    externalImageUrl?: string | null
    images?: Array<{
        url?: string
        name?: string
        size?: number
        type?: string
      }> | null
    integrationId?: number | null
    integrationItemId?: string | null
    context?: {
      gift_card?: {
        template_id?: integer
        preset_amounts_cents?: unknown[]
        allow_digital?: boolean
        allow_physical?: boolean
        allow_pickup?: boolean
        allow_delivery?: boolean
        delivery_fee_cents?: integer
        physical_card_fee_cents?: integer
        recipient_instructions?: string | null
      }
    } | null
    pricings?: Array<{
        id?: string // uuid
        price?: number
        name?: string
        order?: number
        metadata?: { … } | null
        validFrom?: string | null
        validTo?: string | null
        scheduleId?: string | null
        channelTypes?: unknown[] | null
        scheduleActive?: boolean
      }>
    purchaseCostCents?: number | null
    ministock?: number | null
    ministockCents?: number | null
    isAlcohol?: boolean
    variablePricing?: boolean | null
    minPriceCents?: number | null
    maxPriceCents?: number | null
    variableQuantity?: boolean
    tagIds?: number[]
    accountIds?: string[]
    variantGroupIds?: number[]
    printerGroupIds?: string[]
    servingCenterId?: number | null
    titlePrep?: string | null
    prepTimeMinutes?: number | null
    coursingOrder?: number | null
    preparation?: string | null
    printPreparation?: boolean
    color?: string | null
    kitchenSortOrder?: integer | null
    scheduleRules?: Array<{
        id?: string
        dayOfWeek?: integer
        startTime?: string
        endTime?: string
        active?: boolean
      }> | null
    isInherited?: boolean
    inheritToChildren?: boolean
    parentItemId?: number | null
    sourceLocationId?: number | null
    overriddenFields?: string[] | null
    allowedOverrides?: string[] | null
    inheritanceUpdatedAt?: string | null // date-time
    isPosSynced?: boolean
    posOverriddenFields?: string[]
    channelAvailability?: {
      byChannel?: {
        KIOSK?: boolean
        POS?: boolean
        ONLINE?: boolean
        TAKEAWAY?: boolean
        WOLT?: boolean
        FOODORA?: boolean
        UBER_EATS?: boolean
      }
      online?: boolean
      qr?: boolean
      kiosk?: boolean
      pos?: boolean
      wolt?: boolean
      foodora?: boolean
      uberEats?: boolean
    }
    posIntegration?: {
      integrationConfigId: number
      provider: string
      externalItemId: string | null
      isMapped: boolean
    }
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## GET /api/v1/inventory/{id}/overrides

Get item overrides

Get all location-specific overrides for an inherited inventory item

**Permissions:** 🔵 `inventory.read`

Query parameters:
- `locationId` (number, optional) — Filter overrides by location ID

Path parameters:
- `id` (string, required) — Inventory item ID

Response 200 — Default Response

```ts
{
  status?: "success"
  data?: {
    title?: string | null
    description?: string | null
    ingredients?: string | null
    isAvailable?: boolean | null
    imageUrl?: string | null
    pricingGroupId?: string | null // uuid
    pricings?: Record<string, unknown>[] | null
    ministock?: number | null
    measuredStockAtoms?: integer | null
    ministockCents?: number | null
    ministockEnabled?: boolean | null
    variantGroupsAdded?: number[]
    variantGroupsRemoved?: number[]
    printerGroupIds?: string[] | null
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## PUT /api/v1/inventory/{id}/overrides

Update item overrides

Update location-specific overrides for an inherited inventory item

**Permissions:** 🟠 `inventory.update`

Query parameters:
- `locationId` (number, optional) — Location ID for the override

Path parameters:
- `id` (string, required) — Inventory item ID

Request body:

```ts
{
  title?: string | null
  description?: string | null
  ingredients?: string | null
  isAvailable?: boolean | null
  imageUrl?: string | null
  pricingGroupId?: string | null // uuid
  pricings?: Array<{
      id?: string
      price: number
      name: string
      order?: number
      metadata?: Record<string, unknown> | null
      validFrom?: string | null
      validTo?: string | null
      scheduleId?: string | null
      channelTypes?: string[] | null
    }> | null
  ministock?: number | null
  measuredStockAtoms?: integer | null
  ministockCents?: number | null
  ministockEnabled?: boolean | null
  variantGroupsAdded?: number[] | null
  variantGroupsRemoved?: number[] | null
  printerGroupIds?: string[] | null
}
```

Response 200 — Default Response

```ts
{
  status?: "success"
  data?: {
    title?: string | null
    description?: string | null
    ingredients?: string | null
    isAvailable?: boolean | null
    imageUrl?: string | null
    pricingGroupId?: string | null // uuid
    pricings?: Record<string, unknown>[] | null
    ministock?: number | null
    measuredStockAtoms?: integer | null
    ministockCents?: number | null
    ministockEnabled?: boolean | null
    variantGroupsAdded?: number[]
    variantGroupsRemoved?: number[]
    printerGroupIds?: string[] | null
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v1/inventory/{itemId}/availability

Toggle item availability

Enable or disable an inventory item for customer ordering

**Permissions:** 🟠 `inventory.update`

Path parameters:
- `itemId` (string, required) — Inventory item ID

Request body:

```ts
{
  action: "ACTIVATE" | "DEACTIVATE"
}
```

Response 200 — Default Response

```ts
{
  status: "success"
  data: {
    itemId: number
    action: "ACTIVATE" | "DEACTIVATE"
    isAvailable: boolean
  }
  meta: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## DELETE /api/v1/inventory/bulk

Bulk delete inventory items

Soft-deletes up to 500 items at a location.

**Permissions:** 🔴 `inventory.delete`

Request body:

```ts
{
  locationId: integer
  itemIds: integer[]
}
```

Response 200 — Default Response

```ts
Record<string, unknown>
```

## PATCH /api/v1/inventory/bulk

Bulk update inventory items

Applies one partial update to up to 500 items at a location.

**Permissions:** 🟠 `inventory.update`

Request body:

```ts
{
  locationId: integer
  itemIds: integer[]
  patch: {
    customFields?: unknown | unknown[]
    isAvailable?: boolean
    ministock?: number
    basePrice?: number
  }
}
```

Response 200 — Default Response

```ts
Record<string, unknown>
```

## POST /api/v1/inventory/bulk-upsert

Bulk create or update inventory items

Creates items without an id and updates items with an explicit id. An id must belong to the supplied location.

**Permissions:** 🟢 `inventory.create`

Request body:

```ts
{
  locationId: integer
  items: Array<{
      customFields?: …[]
      id?: integer
      title: string
      description?: string | null
      articleNumber?: string | null
      itemType?: string | null
      ministock?: number | null
      basePrice?: number
      isAvailable?: boolean
    }>
}
```

Response 200 — Default Response

```ts
Record<string, unknown>
```

## POST /api/v1/inventory/validate

Validate inventory items

Validate inventory items and variants before creating an order. Returns validation results for each item.

Request body:

```ts
{
  locationId: number
  items: Array<{
      itemId: number
      variants?: { … }[]
      quantity?: number
      menuId?: string
    }>
  validateMenu?: boolean
}
```

Response 200 — Validation response containing valid and invalid items with nested variant structures

```ts
Record<string, unknown>
```

## GET /api/v1/price-clusters

List price clusters owned by or assigned to a location


Query parameters:
- `locationId` (integer, required)
- `limit` (integer, optional)
- `offset` (integer, optional)
- `search` (string, optional)
- `itemId` (integer, optional)
- `version` (integer, optional)

Response 200 — Default Response

```ts
{
  status?: "success"
  data?: {
    items?: Array<{
        id?: string // uuid
        name?: string
        version?: integer
        ownerLocationId?: integer
        ownerLocationName?: string
        inherited?: boolean
        entries?: unknown[]
        locations?: unknown[]
      }>
    hasMore?: boolean
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v1/price-clusters

Create a price cluster

Each entry uses either a direct price in currency units or a shared pricing group with its rules. Entries and descendant locations replace the complete cluster. Saving immediately writes the existing item override table. Removing an assignment restores its previous pricing override. Updates require the current version.

Query parameters:
- `locationId` (integer, required)
- `limit` (integer, optional)
- `offset` (integer, optional)
- `search` (string, optional)
- `itemId` (integer, optional)
- `version` (integer, optional)

Request body:

```ts
{
  name: string
  version?: integer
  entries: Array<{
      itemId: integer
      price?: number
      pricingGroupId?: string // uuid
    }>
  locationIds: integer[]
}
```

Response 201 — Default Response

```ts
{
  status?: "success"
  data?: {
    id?: string // uuid
    version?: integer
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## PUT /api/v1/price-clusters/{id}

Replace a price cluster

Each entry uses either a direct price in currency units or a shared pricing group with its rules. Entries and descendant locations replace the complete cluster. Saving immediately writes the existing item override table. Removing an assignment restores its previous pricing override. Updates require the current version.

Query parameters:
- `locationId` (integer, required)
- `limit` (integer, optional)
- `offset` (integer, optional)
- `search` (string, optional)
- `itemId` (integer, optional)
- `version` (integer, optional)

Path parameters:
- `id` (string, required)

Request body:

```ts
{
  name: string
  version: integer
  entries: Array<{
      itemId: integer
      price?: number
      pricingGroupId?: string // uuid
    }>
  locationIds: integer[]
}
```

Response 200 — Default Response

```ts
{
  status?: "success"
  data?: {
    id?: string // uuid
    version?: integer
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## DELETE /api/v1/price-clusters/{id}

Delete a price cluster and restore child prices


Query parameters:
- `locationId` (integer, required)
- `limit` (integer, optional)
- `offset` (integer, optional)
- `search` (string, optional)
- `itemId` (integer, optional)
- `version` (integer, required)

Path parameters:
- `id` (string, required)

Response 200 — Default Response

```ts
{
  status?: "success"
  data?: {
    deleted?: boolean
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## GET /api/v1/price-clusters/active

Find the cluster controlling an item price at a location


Query parameters:
- `locationId` (integer, required)
- `limit` (integer, optional)
- `offset` (integer, optional)
- `search` (string, optional)
- `itemId` (integer, required)
- `version` (integer, optional)

Response 200 — Default Response

```ts
{
  status?: "success"
  data?: {
    cluster?: {
      id?: string // uuid
      name?: string
    } | null
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## GET /api/v1/price-clusters/descendants

List descendant locations eligible for a price cluster


Query parameters:
- `locationId` (integer, required)
- `limit` (integer, optional)
- `offset` (integer, optional)
- `search` (string, optional)
- `itemId` (integer, optional)
- `version` (integer, optional)

Response 200 — Default Response

```ts
{
  status?: "success"
  data?: {
    items?: Array<{
        id?: integer
        name?: string
      }>
    hasMore?: boolean
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## GET /api/v1/prices

List local and inherited reusable prices


Query parameters:
- `locationId` (integer, required)
- `limit` (integer, optional)
- `offset` (integer, optional)
- `search` (string, optional)

Response 200 — Default Response

```ts
{
  status?: "success"
  data?: {
    items?: Array<{
        id?: string // uuid
        name?: string
        locationId?: integer
        locationName?: string
        inherited?: boolean
        inheritToChildren?: boolean
        version?: integer
        itemCount?: integer
        overrideCount?: integer
        pricings?: unknown[]
      }>
    hasMore?: boolean
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## POST /api/v1/prices

Create a reusable price

Requires grouped_pricing and inventory create/update permission at the owning location. Children may select inherited definitions but cannot edit them. Rules replace the complete definition, beginning with an unconditional BASE. Assign the returned ID using pricingGroupId on inventory create/update or child override endpoints. Updates require the latest version and immediately affect every linked item.

Query parameters:
- `locationId` (integer, required)
- `limit` (integer, optional)
- `offset` (integer, optional)
- `search` (string, optional)

Request body:

```ts
{
  name: string
  inheritToChildren: boolean
  version?: integer
  pricings: Array<{
      id?: string
      name: "BASE" | "FIXED" | "PERCENT" | "FORMULA" | "TIME" | "DISCOUNT" | "DYNAMIC"
      price: number
      order?: integer
      metadata?: Record<string, unknown> | null
      scheduleId?: string | null // uuid
      channelTypes?: "KIOSK" | "POS" | "ONLINE" | "TAKEAWAY" | "WOLT" | "FOODORA" | "UBER_EATS"[] | null
      validFrom?: string | null // date-time
      validTo?: string | null // date-time
    }>
}
```

Response 201 — Default Response

```ts
{
  status?: "success"
  data?: {
    id?: string // uuid
    version?: integer
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```

## PUT /api/v1/prices/{id}

Update a reusable price and all linked items

Requires grouped_pricing and inventory create/update permission at the owning location. Children may select inherited definitions but cannot edit them. Rules replace the complete definition, beginning with an unconditional BASE. Assign the returned ID using pricingGroupId on inventory create/update or child override endpoints. Updates require the latest version and immediately affect every linked item.

Query parameters:
- `locationId` (integer, required)
- `limit` (integer, optional)
- `offset` (integer, optional)
- `search` (string, optional)

Path parameters:
- `id` (string, required)

Request body:

```ts
{
  name: string
  inheritToChildren: boolean
  version: integer
  pricings: Array<{
      id?: string
      name: "BASE" | "FIXED" | "PERCENT" | "FORMULA" | "TIME" | "DISCOUNT" | "DYNAMIC"
      price: number
      order?: integer
      metadata?: Record<string, unknown> | null
      scheduleId?: string | null // uuid
      channelTypes?: "KIOSK" | "POS" | "ONLINE" | "TAKEAWAY" | "WOLT" | "FOODORA" | "UBER_EATS"[] | null
      validFrom?: string | null // date-time
      validTo?: string | null // date-time
    }>
}
```

Response 200 — Default Response

```ts
{
  status?: "success"
  data?: {
    id?: string // uuid
    version?: integer
  }
  meta?: {
    requestId: string
    timestamp: string // date-time
    executionTimeMs: number
  }
}
```
