---
title: "Upsert a price tier"
method: POST
path: "/public/v1/price-tiers"
tags: ["PriceTier"]
---

# Upsert a price tier

`POST /public/v1/price-tiers`

## Request

- Base URL: `https://app.distru.com`
- URL: `https://app.distru.com/public/v1/price-tiers`
- Auth: HTTP bearer

Create a price tier when `id` is absent, update it in place when present. Every write records a
new immutable version and points the tier's `current_version_id` at it, so order items priced
by an earlier version keep their frozen pricing (see `price_tier_version` on the
[sales order item](#model-salesorderitem)).

Updates are sparse: only the fields you send change, and anything you omit is left as-is. This
applies to `conditions` too — send only the conditions you want to change, and send an empty
array (or null) for a `one_of_*`/`not_one_of_*` list to clear that one. Omitting `menu_ids` leaves the
tier's current menus untouched; send the full list to replace them. When you send the
`min_quantity` or `total_thc_percentage_range` object, include every sub-field — a present
sub-field may be null, but a missing one is rejected. Send the object as null to clear it.

`conditions` decides whether the tier can apply to a sales order item — every populated
condition must be met together (AND), or the tier is not applicable. Within a single "one of"
list any listed match qualifies; a "not one of" list disqualifies any listed match;
`min_quantity` is compared against the combined quantity of the order's matching items; and
`total_thc_percentage_range` applies only where selling by potency is enabled. A create must
resolve to at least one condition, otherwise it is rejected with a 400. Creating a tier also
requires your company to be set up for price tiers; if it is not, the create fails with a 400
and a message to contact Distru support.

Saving a tier also refreshes buyer-facing and statewide marketplace price discovery, which
happens asynchronously — a 200/201 reflects the saved tier itself, not yet its propagation to
every menu or marketplace surface that shows it. When a promo-enabled tier newly becomes
eligible for a menu, that menu gets a fresh promo entry.

Promo card fields default sensibly on create: omit `menu_promo_card_type` and it becomes `TEXT`,
and a `TEXT` card fills `menu_promo_card_background_hex` and `menu_promo_card_text_hex` with
default colors when you omit them. Send `menu_promo_card_type: "IMAGE"` for an image card, where
the hex colors are optional. `menu_promo_enabled` (default false) controls whether the card shows
on menus.

Required permission: `settings_permissions_price_tiers`, and price tier visibility other than
"Restrict access" on your teams (admins are always allowed).

## Request body

- object
  - `id` string: Price tier ID to update. Omit to create a new tier; when present, that tier is updated in place and a new immutable version is recorded. An unknown or soft-deleted tier returns 404, and one your team restrictions hide returns 403.
  - `name` string, required: Internal name of the tier, up to 255 characters. Required.
  - `external_name` string: Buyer-facing name shown on menus and marketplace surfaces, up to 255 characters. When omitted or null, buyer-facing surfaces fall back to `name`.
  - `price_or_percent` 'PRICE' | 'PERCENT', required: Whether the discount is a fixed amount (`PRICE`, uses `price`) or a percentage (`PERCENT`, uses `percent`). SCREAMING_CASE. A tier carries exactly one — setting one clears the other. Required.
  - `price` string: Discount amount, as a decimal string with up to 5 decimals (e.g. "10.00"). Its effect depends on `is_flat`: when `is_flat` is false it is subtracted from the order item's base price; when `is_flat` is true it replaces the base price outright. Must be zero or greater. Required when `price_or_percent` is `PRICE`; leave unset for `PERCENT`, which clears it automatically.
  - `percent` string: Discount percentage off the base price, as a decimal string with up to 3 decimals (e.g. "12.5", 0-100). Required when `price_or_percent` is `PERCENT`; switching to `PRICE` clears it automatically.
  - `is_flat` boolean: Whether `price` is a flat replacement price (true) rather than an amount subtracted from the base price (false). Defaults to false. Can only be true with `price_or_percent` `PRICE` — true with `PERCENT` is rejected.
  - `valid_from_datetime` string: ISO8601 UTC start of the active window (e.g. "2026-01-01T00:00:00Z"). Null or omitted means no start bound. Outside the active window the tier is still listed but never applies to orders.
  - `valid_until_datetime` string: ISO8601 UTC end of the active window (e.g. "2026-12-31T23:59:59Z"). Null or omitted means no end bound (never expires). Must be after `valid_from_datetime` when both are set.
  - `menu_mode` 'ALL' | 'NONE' | 'SPECIFIC', required: Which menus the tier appears on: `ALL` every menu, `NONE` no menus, `SPECIFIC` only the menus in `menu_ids`. Required, SCREAMING_CASE. Setting `ALL` or `NONE` clears any specific menu selection.
  - `menu_ids` string[]: IDs of the menus the tier appears on, used only when `menu_mode` is `SPECIFIC` (ignored for `ALL`/`NONE`, whose selection is cleared). Omit to leave the tier's current menus unchanged; send the full list to replace them — a sent list is the complete set, so menus not in it are removed.
  - `menu_promo_enabled` boolean: Whether a promo card for this tier shows on menus. Defaults to false. When true, `menu_promo_card_type` is required, and a `TEXT` card additionally requires both hex colors.
  - `menu_promo_card_type` 'TEXT' | 'IMAGE': Promo card style, SCREAMING_CASE. Defaults to `TEXT` when omitted on create. A `TEXT` card requires both hex colors (filled with defaults if omitted on create); an `IMAGE` card does not use the hex colors. `TEXT` is the standard style.
  - `menu_promo_card_background_hex` string: Promo card background color as a hex string (e.g. "#9F60FF"). Required for a `TEXT` card — defaults to a standard color when omitted on create; ignored for `IMAGE`.
  - `menu_promo_card_text_hex` string: Promo card text color as a hex string (e.g. "#0D1D23"). Required for a `TEXT` card — defaults to a standard color when omitted on create; ignored for `IMAGE`.
  - `menu_promo_card_emoji` string: Emoji shown on the promo card. Optional; null for none.
  - `owner_id` string: ID of the user that owns this tier. Must be a user you are allowed to assign (within your team-visibility scope); an unassignable id is rejected. Send null to clear ownership; omit to leave it unchanged.
  - `conditions` PriceTierConditionsInput: The conditions to set on a price tier. Send id arrays for each criterion the tier should apply to (one_of_*) or be excluded from (not_one_of_*). On update this is sparse: only the conditions you include change, and sending an empty array or null clears that one. A create must resolve to at least one condition.
    - `min_quantity` object: order_item.quantity must be at least this much. When sent, include both quantity and unit_type_id (unit_type_id may be null for unit-agnostic). Send the object as null to clear the condition.
      - `quantity` integer: Minimum quantity (at least 1)
      - `unit_type_id` string: Unit type the minimum is measured in; omit for unit-agnostic
    - `not_one_of_company_ids` string[]: order_item.order.company must not be one of these
    - `not_one_of_company_relationship_group_ids` string[]: order_item.order.company.group must not be one of these
    - `not_one_of_product_brand_ids` string[]: order_item.product.brand must not be one of these
    - `not_one_of_product_category_ids` string[]: order_item.product.category must not be one of these
    - `not_one_of_product_group_ids` string[]: order_item.product.group must not be one of these
    - `not_one_of_product_ids` string[]: order_item.product must not be one of these
    - `not_one_of_product_subcategory_ids` string[]: order_item.product.subcategory must not be one of these
    - `not_one_of_product_tag_ids` string[]: order_item.product must carry none of these tags. A product with no tags always matches. Call [GET /public/v1/tags](#get-tags) to list the available tag IDs.
    - `one_of_company_ids` string[]: order_item.order.company must be one of these
    - `one_of_company_relationship_group_ids` string[]: order_item.order.company.group must be one of these
    - `one_of_product_brand_ids` string[]: order_item.product.brand must be one of these
    - `one_of_product_category_ids` string[]: order_item.product.category must be one of these
    - `one_of_product_group_ids` string[]: order_item.product.group must be one of these
    - `one_of_product_ids` string[]: order_item.product must be one of these
    - `one_of_product_subcategory_ids` string[]: order_item.product.subcategory must be one of these
    - `one_of_product_tag_ids` string[]: order_item.product must carry at least one of these tags. A product with no tags never matches. Call [GET /public/v1/tags](#get-tags) to list the available tag IDs.
    - `total_thc_percentage_range` object: order_item.product's total THC % must fall in this window. When sent, include both min and max (either may be null for an open-ended range, but not both). Send the object as null to clear the condition.
      - `max` number: Maximum total THC %
      - `min` number: Minimum total THC %

## Response `200`

The updated price tier

- PriceTierResponse: A single price tier
  - `data` PriceTier: A price tier lowers the price of a single sales order item when the item meets the tier's conditions. It is applied per order item, not at the order level.
    - `conditions` PriceTierConditions, required: What decides whether a tier can apply to a sales order item. Every populated condition must be met by the item, or the tier is not applicable. An item satisfies a one_of_* condition when it matches at least one entity in the list, and a not_one_of_* condition when it matches none. Unused lists are empty.
      - `min_quantity` object: order_item.quantity must be at least this much, or null
        - `quantity` string: Minimum quantity, e.g. "10"
        - `unit_type` UnitType: A unit of measure used for quantities and pricing (e.g. Gram, Each, Pound). This is the compact reference carrying just id and name; the full unit type (its measurement category and conversion factor) is available from the unit types endpoint.
          - `id` string, required: ID for this unit type
          - `name` string, required: Human readable name for this unit type
      - `not_one_of_companies` CompanyCompact[], required: order_item.order.company must not be one of these
        - `id` string, required: ID for this company
        - `name` string, required: Human readable name for this company
        - `tasks` TaskCompact[], required: The tasks linked to this company that the authenticated user can see: none without `tasks_permissions_view`, and only those their team restrictions allow. An empty array when there are none.
          - `due_datetime` string, required: When the task is due, as a UTC ISO8601 datetime.
          - `id` string, required: ID of the task.
          - `name` string, required: The task's name.
          - `status` 'ACTIVE' | 'COMPLETED', required: The task's status.
        - `updated_datetime` string, required: ISO 8601 datetime this company was last updated at
      - `not_one_of_company_relationship_groups` CompanyGroup[], required: order_item.order.company.group must not be one of these
        - `id` string, required: ID for this company group
        - `name` string, required: Name of the company group
      - `not_one_of_product_brands` CompanyCompact[], required: order_item.product.brand must not be one of these
        - `id` string, required: ID for this company
        - `name` string, required: Human readable name for this company
        - `tasks` TaskCompact[], required: The tasks linked to this company that the authenticated user can see: none without `tasks_permissions_view`, and only those their team restrictions allow. An empty array when there are none.
          - `due_datetime` string, required: When the task is due, as a UTC ISO8601 datetime.
          - `id` string, required: ID of the task.
          - `name` string, required: The task's name.
          - `status` 'ACTIVE' | 'COMPLETED', required: The task's status.
        - `updated_datetime` string, required: ISO 8601 datetime this company was last updated at
      - `not_one_of_product_categories` ProductCategoryCompact[], required: order_item.product.category must not be one of these
        - `id` string, required: ID for this category
        - `name` string, required: Human readable name for this category
        - `official_product_category_id` string: The ID of Distru's standardized (official) category this maps to, used to normalize categories across companies. Null when this category is not mapped to a standardized category.
      - `not_one_of_product_groups` ProductGroupCompact[], required: order_item.product.group must not be one of these
        - `id` string, required: ID for this product group
        - `name` string, required: The name of this product group
      - `not_one_of_product_subcategories` ProductSubcategoryCompact[], required: order_item.product.subcategory must not be one of these
        - `id` string, required: ID for this subcategory
        - `name` string, required: Human readable name for this subcategory
      - `not_one_of_product_tags` ProductTagRef[], required: order_item.product must carry none of these tags. A product with no tags always matches.
        - `id` string, required: ID for this tag
        - `name` string, required: The name of this tag
      - `not_one_of_products` Product[], required: order_item.product must not be one of these
        - `batches_with_active_quantity_by_location` BatchesWithActiveQuantityByLocation[]: The product's batches that hold active quantity, grouped by location. Present only when the request passes `include_batches_with_active_quantity_by_location=true`; the field is absent otherwise. Empty array when the product is not batch-tracked or holds no active batches.
          - `batches` BatchCompactWithQuantityActive[], required: The product's batches with active quantity at this location, each carrying its active quantity there, ordered by id.
            - `batch_number` string: The batch number for this batch, or null when none is set
            - `id` string, required: ID for this batch
            - `name` string, required: Human readable name for this batch
            - `quantity_active` string, required: The batch's active on-hand quantity at this location, as a decimal string (e.g. "100"). Always positive.
          - `location` LocationCompact, required: A compact reference to a [location](#model-location) as nested inside another entity in Distru. Use its `id` to fetch the full location from the locations endpoint.
            - `address` string, required: The full address as a single formatted line, built from the street, apt, city, state, zip, and country fields
            - `company_id` string: ID of the [company](#model-company) that owns this location
            - `id` string, required: ID for this location
            - `license_id` string: ID of the [license](#model-license) this location is associated with, or null when the location has no license
            - `name` string, required: Human readable name for this location
        - `bill_of_materials` BillOfMaterials: A product's bill of materials (recipe of inputs and additional costs)
          - `costs` BillOfMaterialsCost[], required: The additional (non-material) costs this recipe applies, ordered oldest first. Empty when none.
            - `cost_type` BillOfMaterialsCostType: The cost type applied by a bill-of-materials cost
              - …
            - `description` string: Free-text description of this cost, or null when none was entered
            - `id` string, required: ID for this cost
            - `quantity` string, required: How many units of the cost type this line applies, as a decimal string (e.g. "3"). Multiply by the cost type's `cost_per_unit` to get the line total.
          - `description` string: Free-text description of this bill of materials, or null when none was entered
          - `dynamic_inputs` BillOfMaterialsDynamicInput[], required: The inputs that select products by attribute (category, strain, tag, and so on) rather than naming a specific product. Empty when this recipe has none.
            - `id` string, required: ID for this input
            - `product_categories` ProductCategoryCompact[], required: The product categories this input matches on, or empty when it does not match on category. A product must match every non-empty criterion.
              - …
            - `product_groups` ProductGroupCompact[], required: The product groups this input matches on, or empty when it does not match on group.
              - …
            - `product_subcategories` ProductSubcategoryCompact[], required: The product subcategories this input matches on, or empty when it does not match on subcategory.
              - …
            - `quantity` string, required: How much this recipe consumes of whatever product matches, as a decimal string (e.g. "2.5").
            - `strains` Strain[], required: The strains this input matches on, or empty when it does not match on strain.
              - …
            - `tags` ProductTagRef[], required: The tags this input matches on, or empty when it does not match on tag.
              - …
            - `unit_type` UnitType: A unit of measure used for quantities and pricing (e.g. Gram, Each, Pound). This is the compact reference carrying just id and name; the full unit type (its measurement category and conversion factor) is available from the unit types endpoint.
              - …
          - `id` string, required: ID for this bill of materials
          - `name` string, required: Human readable name for this bill of materials
          - `product_inputs` BillOfMaterialsProductInput[], required: The inputs that name a specific product to consume. Empty when this recipe has none. See `dynamic_inputs` for inputs that select products by attribute instead.
            - `id` string, required: ID for this input
            - `product` Product (recursive)
            - `quantity` string, required: How much of the product this recipe consumes, in the product's unit, as a decimal string (e.g. "2.5").
        - `brand` CompanyCompact: A lightweight reference to a company — just its identity — embedded on other entities (orders, invoices, products, etc.) to point at the full company without inlining it. The company is a trading partner (a customer or vendor) in your Distru network. Use the id to fetch its full details from the companies endpoint.
          - `id` string, required: ID for this company
          - `name` string, required: Human readable name for this company
          - `tasks` TaskCompact[], required: The tasks linked to this company that the authenticated user can see: none without `tasks_permissions_view`, and only those their team restrictions allow. An empty array when there are none.
            - `due_datetime` string, required: When the task is due, as a UTC ISO8601 datetime.
            - `id` string, required: ID of the task.
            - `name` string, required: The task's name.
            - `status` 'ACTIVE' | 'COMPLETED', required: The task's status.
          - `updated_datetime` string, required: ISO 8601 datetime this company was last updated at
        - `category` ProductCategoryCompact: The top-level classification of a product (e.g. Flower, Edibles, Concentrates). Categories are defined per company and can be organized into subcategories.
          - `id` string, required: ID for this category
          - `name` string, required: Human readable name for this category
          - `official_product_category_id` string: The ID of Distru's standardized (official) category this maps to, used to normalize categories across companies. Null when this category is not mapped to a standardized category.
        - `creator` User: A member of your Distru team — the account behind actions like owning or creating records.
          - `banned` boolean: True when this user has been banned and can no longer sign in
          - `deleted_at` string: ISO 8601 datetime this user was deleted at, or null when the user is not deleted
          - `email` string, required: The email address of this user
          - `full_name` string: The full name of this user, or null when not set
          - `id` string, required: ID for this user
          - `inserted_datetime` string, required: ISO 8601 datetime this user was created at
          - `role` Role: A permission role that determines what a [user](#model-user) can do in Distru.
            - `id` string, required: ID for this role
            - `name` string, required: Name of the role (e.g. Admin, Sales)
          - `updated_datetime` string, required: ISO 8601 datetime this user was last updated at. It also moves on changes not shown here, such as the user signing in.
        - `custom_data` CustomField[], required: The custom data for this product
          - `id` integer, required: The ID of this custom field's definition (matches an `id` from `GET /public/v1/custom-fields`). An integer.
          - `name` string, required: The name of this custom field
          - `value` string: This record's value for the custom field, as a string, or null when no value has been set for this record
        - `deleted_at` string: ISO 8601 datetime the product was soft-deleted at, or null when the product has not been deleted
        - `description` string: Plain-text description of this product, or null when none was entered. Always null together with `description_markdown` and non-null together with it — one is never set without the other.
        - `description_markdown` string: The same description in Markdown. Null when `description` is null; populated whenever `description` is populated.
        - `external_name` string: Customer-facing name shown on DistruCommerce menus and the Order Tracker, or null when none is set (it does not fall back to `name`)
        - `gross_weight` string: The gross weight of one unit including packaging, as a decimal string, measured in `gross_weight_unit_type`. Null when not set; it is always set together with `gross_weight_unit_type` and null together with it.
        - `gross_weight_unit_type` UnitType: A unit of measure used for quantities and pricing (e.g. Gram, Each, Pound). This is the compact reference carrying just id and name; the full unit type (its measurement category and conversion factor) is available from the unit types endpoint.
          - `id` string, required: ID for this unit type
          - `name` string, required: Human readable name for this unit type
        - `id` string, required: ID for this product
        - `images` Image[]: The images associated with this product, in display order: by `rank`, lowest first (the first is the primary image), then by id. Images with no rank come last. Empty array when the product has no images.
          - `id` string, required: ID for this image
          - `name` string: Original file name of this image, or null when unknown
          - `rank` integer: Display slot of this image among the product's images, 0 to 3; the lowest rank is the primary image. Null when unranked
          - `url` string: URL to the full-size (original) image file, or null when no file is available
        - `inserted_datetime` string, required: The datetime this product was created at
        - `inventory_tracking_method` 'PACKAGE' | 'BATCH' | 'PRODUCT', required: How this product's inventory is tracked. One of: `BATCH` (grouped into batches sharing traits such as expiration dates and test results), `PACKAGE` (inventory is defined by packages), `PRODUCT` (ungrouped; inventory exists directly on the product).
        - `is_active` boolean, required: True when the product is active. False when it has been marked inactive (archived), which hides it from most product pickers.
        - `is_featured` boolean, required: True when the product is flagged as featured
        - `leaflink_product_id` integer: The LeafLink product ID this product is synced to, or null if not synced
        - `menu_visibility` 'DO_NOT_INCLUDE' | 'INCLUDE_IN_ALL' | 'INCLUDE_IN_SELECT': Which menus this product is shown in. One of `DO_NOT_INCLUDE`, `INCLUDE_IN_ALL`, `INCLUDE_IN_SELECT` (same values accepted by the upsert endpoint); defaults to `DO_NOT_INCLUDE`. The `menus` field below lists the specific menus the product belongs to — this is how you see which menus when the value is `INCLUDE_IN_SELECT`. Setting `INCLUDE_IN_ALL` adds the product to every menu, and a menu created later gets it too, except a copy of a menu it isn't on, or a new (not copied) menu made by a user who can't see the product: read `menus` for where it really is.
        - `menus` CompactMenu[]: Menus this product is on, ordered by menu creation time then id (includes inactive menus). Follows `menu_visibility`: the chosen menus for `INCLUDE_IN_SELECT`, every menu but the exceptions listed under `menu_visibility` for `INCLUDE_IN_ALL`, and usually none for `DO_NOT_INCLUDE`. A change made by a team-restricted user never removes the product from menus that user can't see, so those stay listed.
          - `id` string, required: ID of the menu
          - `name` string, required: Display name of the menu
        - `msrp` string: Manufacturer's suggested retail price, as a decimal string (e.g. "19.99"), or null when not set
        - `name` string, required: Human readable name for this product
        - `owner` User: A member of your Distru team — the account behind actions like owning or creating records.
          - `banned` boolean: True when this user has been banned and can no longer sign in
          - `deleted_at` string: ISO 8601 datetime this user was deleted at, or null when the user is not deleted
          - `email` string, required: The email address of this user
          - `full_name` string: The full name of this user, or null when not set
          - `id` string, required: ID for this user
          - `inserted_datetime` string, required: ISO 8601 datetime this user was created at
          - `role` Role: A permission role that determines what a [user](#model-user) can do in Distru.
            - `id` string, required: ID for this role
            - `name` string, required: Name of the role (e.g. Admin, Sales)
          - `updated_datetime` string, required: ISO 8601 datetime this user was last updated at. It also moves on changes not shown here, such as the user signing in.
        - `packages_with_active_quantity_by_location` PackagesWithActiveQuantityByLocation[]: The product's packages that hold active quantity, grouped by location. Present only when the request passes `include_packages_with_active_quantity_by_location=true`; the field is absent otherwise. Empty array when the product is not package-tracked or holds no active packages.
          - `location` LocationCompact, required: A compact reference to a [location](#model-location) as nested inside another entity in Distru. Use its `id` to fetch the full location from the locations endpoint.
            - `address` string, required: The full address as a single formatted line, built from the street, apt, city, state, zip, and country fields
            - `company_id` string: ID of the [company](#model-company) that owns this location
            - `id` string, required: ID for this location
            - `license_id` string: ID of the [license](#model-license) this location is associated with, or null when the location has no license
            - `name` string, required: Human readable name for this location
          - `packages` Package[], required: The product's packages held at this location that have active quantity, ordered by id.
            - `batch_number` string: A free-text batch number set on the package, separate from any compliance tag. Null when none is set.
            - `compliance_label` string: The unique tag assigned by the state compliance system (e.g. the Metrc package tag). Always present for Metrc-tracked packages; may be null for BioTrack packages, and null when the package is not compliance-tracked.
            - `distru_status` 'ACTIVE' | 'ASSEMBLING' | 'DESTROYED' | 'DISCONTINUED' | 'FINISHED' | 'ONHOLD' | 'RETURNING' | 'SELLING' | 'SOLD' | 'TRANSFERRED', required: The package's inventory lifecycle status. One of:• ACTIVE: on hand and available.• ASSEMBLING: allocated to a pending assembly.• SELLING: reserved on an open sales order.• SOLD: consumed by a completed sale.• RETURNING: on an in-progress return.• TRANSFERRED: sent out on a compliance transfer.• ONHOLD: placed on hold in the compliance system.• FINISHED: finished in the compliance system.• DISCONTINUED: discontinued.• DESTROYED: destroyed.
            - `id` string, required: ID for this package in Distru
            - `license_id` string, required: The ID of the [license](#model-license) this package is held under
            - `location_id` string, required: The ID of the [location](#model-location) where this package is physically stored
            - `metrc_id` integer: The Metrc package ID for this package; null for non-Metrc packages
            - `quantity` string, required: The total on-hand quantity of this package, in the package's `unit_type`, as a decimal string (e.g. "100"). Always equals `quantity_active` + `quantity_assembling`. Never negative.
            - `quantity_active` string, required: The freely usable portion of `quantity` — what can be used as an assembly input, moved to another location, added to a sales order, or adjusted down. Equals `quantity` minus `quantity_assembling`, in the package's `unit_type`, as a decimal string. Never negative.
        - `product_group` ProductGroupCompact: A named grouping of products, defined per company (e.g. a brand line or product family).
          - `id` string, required: ID for this product group
          - `name` string, required: The name of this product group
        - `quantity_active` string, required: Total active on-hand quantity of this product across all locations, as a decimal string (e.g. "100"). Sums the product's active stock — positive quantity held at a location. Equals the sum of the per-location amounts in `quantity_active_by_location`, so the two always reconcile. "0" when the product has no active stock. Reserved stock is still physically on-hand, so it is included here; subtract `quantity_reserved` to get `quantity_available`. Sold stock and in-transit stock (held by a user rather than a location) is excluded.
        - `quantity_active_by_location` QuantityActiveByLocation[], required: The product's active on-hand quantity broken down by location — one entry per location holding active stock, ordered by location id. Empty array when the product has no active stock. The entries sum to `quantity_active`.
          - `location` LocationCompact, required: A compact reference to a [location](#model-location) as nested inside another entity in Distru. Use its `id` to fetch the full location from the locations endpoint.
            - `address` string, required: The full address as a single formatted line, built from the street, apt, city, state, zip, and country fields
            - `company_id` string: ID of the [company](#model-company) that owns this location
            - `id` string, required: ID for this location
            - `license_id` string: ID of the [license](#model-license) this location is associated with, or null when the location has no license
            - `name` string, required: Human readable name for this location
          - `quantity` string, required: Active on-hand quantity at this location, as a decimal string (e.g. "100"). Always positive.
        - `quantity_available` string, required: Active on-hand quantity minus reserved quantity, as a decimal string (e.g. "100"). Equals `quantity_active` − `quantity_reserved` — what is on-hand and not already spoken for by unfulfilled sales orders or draft assemblies. Can be negative when more is reserved than is on-hand at a location. "0" when the product has no active stock and nothing reserved.
        - `quantity_available_threshold_max` string: Over-stock alert threshold: Distru flags the product when its available quantity rises above this value. Decimal string, or null when no over-stock alert is configured. When both thresholds are set, this is strictly greater than `quantity_available_threshold_min`.
        - `quantity_available_threshold_min` string: Low-stock alert threshold: Distru flags the product when its available quantity drops below this value. Decimal string, or null when no low-stock alert is configured. When both thresholds are set, this is strictly less than `quantity_available_threshold_max`.
        - `quantity_reserved` string, required: Total quantity of this product currently reserved across all locations, as a decimal string (e.g. "100"). Reserved quantity is a soft hold tracked separately from on-hand stock (`quantity_active`): it rises as unfulfilled sales order items and draft assembly inputs are created, and falls as they are deleted or fulfilled. "0" when nothing is reserved.
        - `sku` string, required: The stock keeping unit (SKU) configured for this product
        - `strain` Strain: A cannabis strain (its genetics), such as "Blue Dream". Products can be linked to a strain to carry its name and type.
          - `id` string, required: ID for this strain
          - `inserted_datetime` string, required: The datetime this strain was created at
          - `name` string, required: Name of the strain
          - `strain_type` 'INDICA' | 'INDICA_DOMINANT' | 'SATIVA' | 'SATIVA_DOMINANT' | 'HYBRID' | 'HIGH_CBD': The genetic classification of the strain. Null when unset.
          - `updated_datetime` string, required: The datetime this strain was last updated at
        - `subcategory` ProductSubcategoryCompact: A finer classification within a product category (e.g. "Pre-Rolls" under Flower).
          - `id` string, required: ID for this subcategory
          - `name` string, required: Human readable name for this subcategory
        - `tags` ProductTagRef[]: The tags associated with this product. Empty array when the product has no tags.
          - `id` string, required: ID for this tag
          - `name` string, required: The name of this tag
        - `tasks` TaskCompact[], required: The tasks linked to this product that the authenticated user can see: none without `tasks_permissions_view`, and only those their team restrictions allow. An empty array when there are none.
          - `due_datetime` string, required: When the task is due, as a UTC ISO8601 datetime.
          - `id` string, required: ID of the task.
          - `name` string, required: The task's name.
          - `status` 'ACTIVE' | 'COMPLETED', required: The task's status.
        - `total_cannabinoid_unit` string: The unit `total_thc` and `total_cbd` are measured in — one of MG (milligrams per unit) or PERCENT (percent by weight). Null when neither potency value is set.
        - `total_cbd` string: Total CBD potency, as a decimal string, measured in `total_cannabinoid_unit`. Null when not set. Non-negative; when the unit is PERCENT it is between 0 and 100. Whenever this is set, `total_cannabinoid_unit` is also set. This is a static label on the product, not a lab-test value.
        - `total_thc` string: Total THC potency, as a decimal string, measured in `total_cannabinoid_unit`. Null when not set. Non-negative; when the unit is PERCENT it is between 0 and 100. Whenever this is set, `total_cannabinoid_unit` is also set. This is a static label on the product, not a lab-test value.
        - `treez_wholesale_price` string: The Treez wholesale price of this product, as a decimal string, or null when not set
        - `unit_cost` string: The cost (purchase price) of one unit of this product, as a decimal string, or null when not set
        - `unit_net_weight` string: The net contents of one unit of this product (weight, volume, or count of the contents), as a decimal string, measured in `unit_net_weight_serving_size_unit_type`. Null when not set.
        - `unit_net_weight_serving_size_unit_type` UnitType: A unit of measure used for quantities and pricing (e.g. Gram, Each, Pound). This is the compact reference carrying just id and name; the full unit type (its measurement category and conversion factor) is available from the unit types endpoint.
          - `id` string, required: ID for this unit type
          - `name` string, required: Human readable name for this unit type
        - `unit_price` string, required: The list (sale) price of one unit of this product, as a decimal string (e.g. "25.00")
        - `unit_serving_size` string: The serving size per unit, as a decimal string, measured in `unit_net_weight_serving_size_unit_type` (the same unit as `unit_net_weight`). Null when not set. When both are set, this is at most `unit_net_weight`.
        - `unit_type` UnitType: A unit of measure used for quantities and pricing (e.g. Gram, Each, Pound). This is the compact reference carrying just id and name; the full unit type (its measurement category and conversion factor) is available from the unit types endpoint.
          - `id` string, required: ID for this unit type
          - `name` string, required: Human readable name for this unit type
        - `units_per_case` string: The number of units of this product packed in one case, as a decimal string (e.g. "12"), or null when not set
        - `upc` string: The UPC barcode of this product, or null when not set
        - `updated_datetime` string, required: The datetime this product was last updated at
        - `vendor` CompanyCompact: A lightweight reference to a company — just its identity — embedded on other entities (orders, invoices, products, etc.) to point at the full company without inlining it. The company is a trading partner (a customer or vendor) in your Distru network. Use the id to fetch its full details from the companies endpoint.
          - `id` string, required: ID for this company
          - `name` string, required: Human readable name for this company
          - `tasks` TaskCompact[], required: The tasks linked to this company that the authenticated user can see: none without `tasks_permissions_view`, and only those their team restrictions allow. An empty array when there are none.
            - `due_datetime` string, required: When the task is due, as a UTC ISO8601 datetime.
            - `id` string, required: ID of the task.
            - `name` string, required: The task's name.
            - `status` 'ACTIVE' | 'COMPLETED', required: The task's status.
          - `updated_datetime` string, required: ISO 8601 datetime this company was last updated at
        - `wholesale_unit_price` number: The wholesale unit price of this product, as a JSON number (e.g. 12.5) rather than a decimal string — unlike `unit_price`. Null when not set.
      - `one_of_companies` CompanyCompact[], required: order_item.order.company must be one of these
        - `id` string, required: ID for this company
        - `name` string, required: Human readable name for this company
        - `tasks` TaskCompact[], required: The tasks linked to this company that the authenticated user can see: none without `tasks_permissions_view`, and only those their team restrictions allow. An empty array when there are none.
          - `due_datetime` string, required: When the task is due, as a UTC ISO8601 datetime.
          - `id` string, required: ID of the task.
          - `name` string, required: The task's name.
          - `status` 'ACTIVE' | 'COMPLETED', required: The task's status.
        - `updated_datetime` string, required: ISO 8601 datetime this company was last updated at
      - `one_of_company_relationship_groups` CompanyGroup[], required: order_item.order.company.group must be one of these
        - `id` string, required: ID for this company group
        - `name` string, required: Name of the company group
      - `one_of_product_brands` CompanyCompact[], required: order_item.product.brand must be one of these
        - `id` string, required: ID for this company
        - `name` string, required: Human readable name for this company
        - `tasks` TaskCompact[], required: The tasks linked to this company that the authenticated user can see: none without `tasks_permissions_view`, and only those their team restrictions allow. An empty array when there are none.
          - `due_datetime` string, required: When the task is due, as a UTC ISO8601 datetime.
          - `id` string, required: ID of the task.
          - `name` string, required: The task's name.
          - `status` 'ACTIVE' | 'COMPLETED', required: The task's status.
        - `updated_datetime` string, required: ISO 8601 datetime this company was last updated at
      - `one_of_product_categories` ProductCategoryCompact[], required: order_item.product.category must be one of these
        - `id` string, required: ID for this category
        - `name` string, required: Human readable name for this category
        - `official_product_category_id` string: The ID of Distru's standardized (official) category this maps to, used to normalize categories across companies. Null when this category is not mapped to a standardized category.
      - `one_of_product_groups` ProductGroupCompact[], required: order_item.product.group must be one of these
        - `id` string, required: ID for this product group
        - `name` string, required: The name of this product group
      - `one_of_product_subcategories` ProductSubcategoryCompact[], required: order_item.product.subcategory must be one of these
        - `id` string, required: ID for this subcategory
        - `name` string, required: Human readable name for this subcategory
      - `one_of_product_tags` ProductTagRef[], required: order_item.product must carry at least one of these tags. A product with no tags never matches.
        - `id` string, required: ID for this tag
        - `name` string, required: The name of this tag
      - `one_of_products` Product[], required: order_item.product must be one of these
        - `batches_with_active_quantity_by_location` BatchesWithActiveQuantityByLocation[]: The product's batches that hold active quantity, grouped by location. Present only when the request passes `include_batches_with_active_quantity_by_location=true`; the field is absent otherwise. Empty array when the product is not batch-tracked or holds no active batches.
          - `batches` BatchCompactWithQuantityActive[], required: The product's batches with active quantity at this location, each carrying its active quantity there, ordered by id.
            - `batch_number` string: The batch number for this batch, or null when none is set
            - `id` string, required: ID for this batch
            - `name` string, required: Human readable name for this batch
            - `quantity_active` string, required: The batch's active on-hand quantity at this location, as a decimal string (e.g. "100"). Always positive.
          - `location` LocationCompact, required: A compact reference to a [location](#model-location) as nested inside another entity in Distru. Use its `id` to fetch the full location from the locations endpoint.
            - `address` string, required: The full address as a single formatted line, built from the street, apt, city, state, zip, and country fields
            - `company_id` string: ID of the [company](#model-company) that owns this location
            - `id` string, required: ID for this location
            - `license_id` string: ID of the [license](#model-license) this location is associated with, or null when the location has no license
            - `name` string, required: Human readable name for this location
        - `bill_of_materials` BillOfMaterials: A product's bill of materials (recipe of inputs and additional costs)
          - `costs` BillOfMaterialsCost[], required: The additional (non-material) costs this recipe applies, ordered oldest first. Empty when none.
            - `cost_type` BillOfMaterialsCostType: The cost type applied by a bill-of-materials cost
              - …
            - `description` string: Free-text description of this cost, or null when none was entered
            - `id` string, required: ID for this cost
            - `quantity` string, required: How many units of the cost type this line applies, as a decimal string (e.g. "3"). Multiply by the cost type's `cost_per_unit` to get the line total.
          - `description` string: Free-text description of this bill of materials, or null when none was entered
          - `dynamic_inputs` BillOfMaterialsDynamicInput[], required: The inputs that select products by attribute (category, strain, tag, and so on) rather than naming a specific product. Empty when this recipe has none.
            - `id` string, required: ID for this input
            - `product_categories` ProductCategoryCompact[], required: The product categories this input matches on, or empty when it does not match on category. A product must match every non-empty criterion.
              - …
            - `product_groups` ProductGroupCompact[], required: The product groups this input matches on, or empty when it does not match on group.
              - …
            - `product_subcategories` ProductSubcategoryCompact[], required: The product subcategories this input matches on, or empty when it does not match on subcategory.
              - …
            - `quantity` string, required: How much this recipe consumes of whatever product matches, as a decimal string (e.g. "2.5").
            - `strains` Strain[], required: The strains this input matches on, or empty when it does not match on strain.
              - …
            - `tags` ProductTagRef[], required: The tags this input matches on, or empty when it does not match on tag.
              - …
            - `unit_type` UnitType: A unit of measure used for quantities and pricing (e.g. Gram, Each, Pound). This is the compact reference carrying just id and name; the full unit type (its measurement category and conversion factor) is available from the unit types endpoint.
              - …
          - `id` string, required: ID for this bill of materials
          - `name` string, required: Human readable name for this bill of materials
          - `product_inputs` BillOfMaterialsProductInput[], required: The inputs that name a specific product to consume. Empty when this recipe has none. See `dynamic_inputs` for inputs that select products by attribute instead.
            - `id` string, required: ID for this input
            - `product` Product (recursive)
            - `quantity` string, required: How much of the product this recipe consumes, in the product's unit, as a decimal string (e.g. "2.5").
        - `brand` CompanyCompact: A lightweight reference to a company — just its identity — embedded on other entities (orders, invoices, products, etc.) to point at the full company without inlining it. The company is a trading partner (a customer or vendor) in your Distru network. Use the id to fetch its full details from the companies endpoint.
          - `id` string, required: ID for this company
          - `name` string, required: Human readable name for this company
          - `tasks` TaskCompact[], required: The tasks linked to this company that the authenticated user can see: none without `tasks_permissions_view`, and only those their team restrictions allow. An empty array when there are none.
            - `due_datetime` string, required: When the task is due, as a UTC ISO8601 datetime.
            - `id` string, required: ID of the task.
            - `name` string, required: The task's name.
            - `status` 'ACTIVE' | 'COMPLETED', required: The task's status.
          - `updated_datetime` string, required: ISO 8601 datetime this company was last updated at
        - `category` ProductCategoryCompact: The top-level classification of a product (e.g. Flower, Edibles, Concentrates). Categories are defined per company and can be organized into subcategories.
          - `id` string, required: ID for this category
          - `name` string, required: Human readable name for this category
          - `official_product_category_id` string: The ID of Distru's standardized (official) category this maps to, used to normalize categories across companies. Null when this category is not mapped to a standardized category.
        - `creator` User: A member of your Distru team — the account behind actions like owning or creating records.
          - `banned` boolean: True when this user has been banned and can no longer sign in
          - `deleted_at` string: ISO 8601 datetime this user was deleted at, or null when the user is not deleted
          - `email` string, required: The email address of this user
          - `full_name` string: The full name of this user, or null when not set
          - `id` string, required: ID for this user
          - `inserted_datetime` string, required: ISO 8601 datetime this user was created at
          - `role` Role: A permission role that determines what a [user](#model-user) can do in Distru.
            - `id` string, required: ID for this role
            - `name` string, required: Name of the role (e.g. Admin, Sales)
          - `updated_datetime` string, required: ISO 8601 datetime this user was last updated at. It also moves on changes not shown here, such as the user signing in.
        - `custom_data` CustomField[], required: The custom data for this product
          - `id` integer, required: The ID of this custom field's definition (matches an `id` from `GET /public/v1/custom-fields`). An integer.
          - `name` string, required: The name of this custom field
          - `value` string: This record's value for the custom field, as a string, or null when no value has been set for this record
        - `deleted_at` string: ISO 8601 datetime the product was soft-deleted at, or null when the product has not been deleted
        - `description` string: Plain-text description of this product, or null when none was entered. Always null together with `description_markdown` and non-null together with it — one is never set without the other.
        - `description_markdown` string: The same description in Markdown. Null when `description` is null; populated whenever `description` is populated.
        - `external_name` string: Customer-facing name shown on DistruCommerce menus and the Order Tracker, or null when none is set (it does not fall back to `name`)
        - `gross_weight` string: The gross weight of one unit including packaging, as a decimal string, measured in `gross_weight_unit_type`. Null when not set; it is always set together with `gross_weight_unit_type` and null together with it.
        - `gross_weight_unit_type` UnitType: A unit of measure used for quantities and pricing (e.g. Gram, Each, Pound). This is the compact reference carrying just id and name; the full unit type (its measurement category and conversion factor) is available from the unit types endpoint.
          - `id` string, required: ID for this unit type
          - `name` string, required: Human readable name for this unit type
        - `id` string, required: ID for this product
        - `images` Image[]: The images associated with this product, in display order: by `rank`, lowest first (the first is the primary image), then by id. Images with no rank come last. Empty array when the product has no images.
          - `id` string, required: ID for this image
          - `name` string: Original file name of this image, or null when unknown
          - `rank` integer: Display slot of this image among the product's images, 0 to 3; the lowest rank is the primary image. Null when unranked
          - `url` string: URL to the full-size (original) image file, or null when no file is available
        - `inserted_datetime` string, required: The datetime this product was created at
        - `inventory_tracking_method` 'PACKAGE' | 'BATCH' | 'PRODUCT', required: How this product's inventory is tracked. One of: `BATCH` (grouped into batches sharing traits such as expiration dates and test results), `PACKAGE` (inventory is defined by packages), `PRODUCT` (ungrouped; inventory exists directly on the product).
        - `is_active` boolean, required: True when the product is active. False when it has been marked inactive (archived), which hides it from most product pickers.
        - `is_featured` boolean, required: True when the product is flagged as featured
        - `leaflink_product_id` integer: The LeafLink product ID this product is synced to, or null if not synced
        - `menu_visibility` 'DO_NOT_INCLUDE' | 'INCLUDE_IN_ALL' | 'INCLUDE_IN_SELECT': Which menus this product is shown in. One of `DO_NOT_INCLUDE`, `INCLUDE_IN_ALL`, `INCLUDE_IN_SELECT` (same values accepted by the upsert endpoint); defaults to `DO_NOT_INCLUDE`. The `menus` field below lists the specific menus the product belongs to — this is how you see which menus when the value is `INCLUDE_IN_SELECT`. Setting `INCLUDE_IN_ALL` adds the product to every menu, and a menu created later gets it too, except a copy of a menu it isn't on, or a new (not copied) menu made by a user who can't see the product: read `menus` for where it really is.
        - `menus` CompactMenu[]: Menus this product is on, ordered by menu creation time then id (includes inactive menus). Follows `menu_visibility`: the chosen menus for `INCLUDE_IN_SELECT`, every menu but the exceptions listed under `menu_visibility` for `INCLUDE_IN_ALL`, and usually none for `DO_NOT_INCLUDE`. A change made by a team-restricted user never removes the product from menus that user can't see, so those stay listed.
          - `id` string, required: ID of the menu
          - `name` string, required: Display name of the menu
        - `msrp` string: Manufacturer's suggested retail price, as a decimal string (e.g. "19.99"), or null when not set
        - `name` string, required: Human readable name for this product
        - `owner` User: A member of your Distru team — the account behind actions like owning or creating records.
          - `banned` boolean: True when this user has been banned and can no longer sign in
          - `deleted_at` string: ISO 8601 datetime this user was deleted at, or null when the user is not deleted
          - `email` string, required: The email address of this user
          - `full_name` string: The full name of this user, or null when not set
          - `id` string, required: ID for this user
          - `inserted_datetime` string, required: ISO 8601 datetime this user was created at
          - `role` Role: A permission role that determines what a [user](#model-user) can do in Distru.
            - `id` string, required: ID for this role
            - `name` string, required: Name of the role (e.g. Admin, Sales)
          - `updated_datetime` string, required: ISO 8601 datetime this user was last updated at. It also moves on changes not shown here, such as the user signing in.
        - `packages_with_active_quantity_by_location` PackagesWithActiveQuantityByLocation[]: The product's packages that hold active quantity, grouped by location. Present only when the request passes `include_packages_with_active_quantity_by_location=true`; the field is absent otherwise. Empty array when the product is not package-tracked or holds no active packages.
          - `location` LocationCompact, required: A compact reference to a [location](#model-location) as nested inside another entity in Distru. Use its `id` to fetch the full location from the locations endpoint.
            - `address` string, required: The full address as a single formatted line, built from the street, apt, city, state, zip, and country fields
            - `company_id` string: ID of the [company](#model-company) that owns this location
            - `id` string, required: ID for this location
            - `license_id` string: ID of the [license](#model-license) this location is associated with, or null when the location has no license
            - `name` string, required: Human readable name for this location
          - `packages` Package[], required: The product's packages held at this location that have active quantity, ordered by id.
            - `batch_number` string: A free-text batch number set on the package, separate from any compliance tag. Null when none is set.
            - `compliance_label` string: The unique tag assigned by the state compliance system (e.g. the Metrc package tag). Always present for Metrc-tracked packages; may be null for BioTrack packages, and null when the package is not compliance-tracked.
            - `distru_status` 'ACTIVE' | 'ASSEMBLING' | 'DESTROYED' | 'DISCONTINUED' | 'FINISHED' | 'ONHOLD' | 'RETURNING' | 'SELLING' | 'SOLD' | 'TRANSFERRED', required: The package's inventory lifecycle status. One of:• ACTIVE: on hand and available.• ASSEMBLING: allocated to a pending assembly.• SELLING: reserved on an open sales order.• SOLD: consumed by a completed sale.• RETURNING: on an in-progress return.• TRANSFERRED: sent out on a compliance transfer.• ONHOLD: placed on hold in the compliance system.• FINISHED: finished in the compliance system.• DISCONTINUED: discontinued.• DESTROYED: destroyed.
            - `id` string, required: ID for this package in Distru
            - `license_id` string, required: The ID of the [license](#model-license) this package is held under
            - `location_id` string, required: The ID of the [location](#model-location) where this package is physically stored
            - `metrc_id` integer: The Metrc package ID for this package; null for non-Metrc packages
            - `quantity` string, required: The total on-hand quantity of this package, in the package's `unit_type`, as a decimal string (e.g. "100"). Always equals `quantity_active` + `quantity_assembling`. Never negative.
            - `quantity_active` string, required: The freely usable portion of `quantity` — what can be used as an assembly input, moved to another location, added to a sales order, or adjusted down. Equals `quantity` minus `quantity_assembling`, in the package's `unit_type`, as a decimal string. Never negative.
        - `product_group` ProductGroupCompact: A named grouping of products, defined per company (e.g. a brand line or product family).
          - `id` string, required: ID for this product group
          - `name` string, required: The name of this product group
        - `quantity_active` string, required: Total active on-hand quantity of this product across all locations, as a decimal string (e.g. "100"). Sums the product's active stock — positive quantity held at a location. Equals the sum of the per-location amounts in `quantity_active_by_location`, so the two always reconcile. "0" when the product has no active stock. Reserved stock is still physically on-hand, so it is included here; subtract `quantity_reserved` to get `quantity_available`. Sold stock and in-transit stock (held by a user rather than a location) is excluded.
        - `quantity_active_by_location` QuantityActiveByLocation[], required: The product's active on-hand quantity broken down by location — one entry per location holding active stock, ordered by location id. Empty array when the product has no active stock. The entries sum to `quantity_active`.
          - `location` LocationCompact, required: A compact reference to a [location](#model-location) as nested inside another entity in Distru. Use its `id` to fetch the full location from the locations endpoint.
            - `address` string, required: The full address as a single formatted line, built from the street, apt, city, state, zip, and country fields
            - `company_id` string: ID of the [company](#model-company) that owns this location
            - `id` string, required: ID for this location
            - `license_id` string: ID of the [license](#model-license) this location is associated with, or null when the location has no license
            - `name` string, required: Human readable name for this location
          - `quantity` string, required: Active on-hand quantity at this location, as a decimal string (e.g. "100"). Always positive.
        - `quantity_available` string, required: Active on-hand quantity minus reserved quantity, as a decimal string (e.g. "100"). Equals `quantity_active` − `quantity_reserved` — what is on-hand and not already spoken for by unfulfilled sales orders or draft assemblies. Can be negative when more is reserved than is on-hand at a location. "0" when the product has no active stock and nothing reserved.
        - `quantity_available_threshold_max` string: Over-stock alert threshold: Distru flags the product when its available quantity rises above this value. Decimal string, or null when no over-stock alert is configured. When both thresholds are set, this is strictly greater than `quantity_available_threshold_min`.
        - `quantity_available_threshold_min` string: Low-stock alert threshold: Distru flags the product when its available quantity drops below this value. Decimal string, or null when no low-stock alert is configured. When both thresholds are set, this is strictly less than `quantity_available_threshold_max`.
        - `quantity_reserved` string, required: Total quantity of this product currently reserved across all locations, as a decimal string (e.g. "100"). Reserved quantity is a soft hold tracked separately from on-hand stock (`quantity_active`): it rises as unfulfilled sales order items and draft assembly inputs are created, and falls as they are deleted or fulfilled. "0" when nothing is reserved.
        - `sku` string, required: The stock keeping unit (SKU) configured for this product
        - `strain` Strain: A cannabis strain (its genetics), such as "Blue Dream". Products can be linked to a strain to carry its name and type.
          - `id` string, required: ID for this strain
          - `inserted_datetime` string, required: The datetime this strain was created at
          - `name` string, required: Name of the strain
          - `strain_type` 'INDICA' | 'INDICA_DOMINANT' | 'SATIVA' | 'SATIVA_DOMINANT' | 'HYBRID' | 'HIGH_CBD': The genetic classification of the strain. Null when unset.
          - `updated_datetime` string, required: The datetime this strain was last updated at
        - `subcategory` ProductSubcategoryCompact: A finer classification within a product category (e.g. "Pre-Rolls" under Flower).
          - `id` string, required: ID for this subcategory
          - `name` string, required: Human readable name for this subcategory
        - `tags` ProductTagRef[]: The tags associated with this product. Empty array when the product has no tags.
          - `id` string, required: ID for this tag
          - `name` string, required: The name of this tag
        - `tasks` TaskCompact[], required: The tasks linked to this product that the authenticated user can see: none without `tasks_permissions_view`, and only those their team restrictions allow. An empty array when there are none.
          - `due_datetime` string, required: When the task is due, as a UTC ISO8601 datetime.
          - `id` string, required: ID of the task.
          - `name` string, required: The task's name.
          - `status` 'ACTIVE' | 'COMPLETED', required: The task's status.
        - `total_cannabinoid_unit` string: The unit `total_thc` and `total_cbd` are measured in — one of MG (milligrams per unit) or PERCENT (percent by weight). Null when neither potency value is set.
        - `total_cbd` string: Total CBD potency, as a decimal string, measured in `total_cannabinoid_unit`. Null when not set. Non-negative; when the unit is PERCENT it is between 0 and 100. Whenever this is set, `total_cannabinoid_unit` is also set. This is a static label on the product, not a lab-test value.
        - `total_thc` string: Total THC potency, as a decimal string, measured in `total_cannabinoid_unit`. Null when not set. Non-negative; when the unit is PERCENT it is between 0 and 100. Whenever this is set, `total_cannabinoid_unit` is also set. This is a static label on the product, not a lab-test value.
        - `treez_wholesale_price` string: The Treez wholesale price of this product, as a decimal string, or null when not set
        - `unit_cost` string: The cost (purchase price) of one unit of this product, as a decimal string, or null when not set
        - `unit_net_weight` string: The net contents of one unit of this product (weight, volume, or count of the contents), as a decimal string, measured in `unit_net_weight_serving_size_unit_type`. Null when not set.
        - `unit_net_weight_serving_size_unit_type` UnitType: A unit of measure used for quantities and pricing (e.g. Gram, Each, Pound). This is the compact reference carrying just id and name; the full unit type (its measurement category and conversion factor) is available from the unit types endpoint.
          - `id` string, required: ID for this unit type
          - `name` string, required: Human readable name for this unit type
        - `unit_price` string, required: The list (sale) price of one unit of this product, as a decimal string (e.g. "25.00")
        - `unit_serving_size` string: The serving size per unit, as a decimal string, measured in `unit_net_weight_serving_size_unit_type` (the same unit as `unit_net_weight`). Null when not set. When both are set, this is at most `unit_net_weight`.
        - `unit_type` UnitType: A unit of measure used for quantities and pricing (e.g. Gram, Each, Pound). This is the compact reference carrying just id and name; the full unit type (its measurement category and conversion factor) is available from the unit types endpoint.
          - `id` string, required: ID for this unit type
          - `name` string, required: Human readable name for this unit type
        - `units_per_case` string: The number of units of this product packed in one case, as a decimal string (e.g. "12"), or null when not set
        - `upc` string: The UPC barcode of this product, or null when not set
        - `updated_datetime` string, required: The datetime this product was last updated at
        - `vendor` CompanyCompact: A lightweight reference to a company — just its identity — embedded on other entities (orders, invoices, products, etc.) to point at the full company without inlining it. The company is a trading partner (a customer or vendor) in your Distru network. Use the id to fetch its full details from the companies endpoint.
          - `id` string, required: ID for this company
          - `name` string, required: Human readable name for this company
          - `tasks` TaskCompact[], required: The tasks linked to this company that the authenticated user can see: none without `tasks_permissions_view`, and only those their team restrictions allow. An empty array when there are none.
            - `due_datetime` string, required: When the task is due, as a UTC ISO8601 datetime.
            - `id` string, required: ID of the task.
            - `name` string, required: The task's name.
            - `status` 'ACTIVE' | 'COMPLETED', required: The task's status.
          - `updated_datetime` string, required: ISO 8601 datetime this company was last updated at
        - `wholesale_unit_price` number: The wholesale unit price of this product, as a JSON number (e.g. 12.5) rather than a decimal string — unlike `unit_price`. Null when not set.
      - `total_thc_percentage_range` object: order_item.product's total THC % must fall in this window, or null
        - `max` string: Maximum total THC %, or null
        - `min` string: Minimum total THC %, or null
    - `creator` User: A member of your Distru team — the account behind actions like owning or creating records.
      - `banned` boolean: True when this user has been banned and can no longer sign in
      - `deleted_at` string: ISO 8601 datetime this user was deleted at, or null when the user is not deleted
      - `email` string, required: The email address of this user
      - `full_name` string: The full name of this user, or null when not set
      - `id` string, required: ID for this user
      - `inserted_datetime` string, required: ISO 8601 datetime this user was created at
      - `role` Role: A permission role that determines what a [user](#model-user) can do in Distru.
        - `id` string, required: ID for this role
        - `name` string, required: Name of the role (e.g. Admin, Sales)
      - `updated_datetime` string, required: ISO 8601 datetime this user was last updated at. It also moves on changes not shown here, such as the user signing in.
    - `current_version_id` string, required: ID of the tier's current version — the immutable snapshot taken at the tier's latest edit. Every edit of a tier produces a new version; a [sales order item](#model-salesorderitem) records the version that priced it in its `price_tier_version`, so its pricing stays frozen at that snapshot even after the tier changes. An order item reflects this tier's latest state exactly when its `price_tier_version.id` equals this value (its `price_tier_version.is_live` is true).
    - `external_name` string: Buyer-facing name shown on menus, or null (falls back to name)
    - `id` string, required: ID for this price tier
    - `inserted_datetime` string, required: When the tier was created (UTC ISO-8601)
    - `is_flat` boolean, required: When true the `price` replaces the list price outright instead of discounting off it. Only meaningful when `price_or_percent` is PRICE — never true with PERCENT.
    - `menu_mode` 'ALL' | 'NONE' | 'SPECIFIC', required: Which menus the tier appears on
    - `menu_promo_card_background_hex` string: Background color of a TEXT promo card, as a hex string (e.g. "#FF0000"); null for an IMAGE card
    - `menu_promo_card_emoji` string: Emoji shown on a TEXT promo card, or null when none is set
    - `menu_promo_card_text_hex` string: Text color of a TEXT promo card, as a hex string (e.g. "#FFFFFF"); null for an IMAGE card
    - `menu_promo_card_type` 'TEXT' | 'IMAGE', required: The promo card style (SCREAMING_CASE): TEXT renders a colored card using the hex/emoji fields, IMAGE renders an uploaded image instead.
    - `menu_promo_enabled` boolean: Whether the tier renders a promo card on menus. The `menu_promo_card_*` fields below are only shown to buyers when this is true.
    - `menus` CompactMenu[], required: Menus the tier applies to. Populated only when menu_mode is SPECIFIC
      - `id` string, required: ID of the menu
      - `name` string, required: Display name of the menu
    - `name` string, required: Internal name of the tier
    - `owner` User: A member of your Distru team — the account behind actions like owning or creating records.
      - `banned` boolean: True when this user has been banned and can no longer sign in
      - `deleted_at` string: ISO 8601 datetime this user was deleted at, or null when the user is not deleted
      - `email` string, required: The email address of this user
      - `full_name` string: The full name of this user, or null when not set
      - `id` string, required: ID for this user
      - `inserted_datetime` string, required: ISO 8601 datetime this user was created at
      - `role` Role: A permission role that determines what a [user](#model-user) can do in Distru.
        - `id` string, required: ID for this role
        - `name` string, required: Name of the role (e.g. Admin, Sales)
      - `updated_datetime` string, required: ISO 8601 datetime this user was last updated at. It also moves on changes not shown here, such as the user signing in.
    - `percent` string: The discount percentage used when `price_or_percent` is PERCENT, as a decimal string with up to 3 decimals (e.g. "12.5", 0-100); null when `price_or_percent` is PRICE
    - `price` string: The money amount used when `price_or_percent` is PRICE, as a decimal string with up to 5 decimals (e.g. "5.00"); null when `price_or_percent` is PERCENT. Interpreted as a per-unit discount off the list price, or — when `is_flat` is true — as the replacement per-unit price itself.
    - `price_or_percent` 'PRICE' | 'PERCENT', required: Whether the discount is a fixed amount or a percentage
    - `updated_datetime` string, required: When the tier was last updated (UTC ISO-8601)
    - `valid_from_datetime` string: ISO 8601 datetime the tier starts being applicable, or null for no start bound (applicable from any time up to `valid_until_datetime`)
    - `valid_until_datetime` string: ISO 8601 datetime the tier stops being applicable, or null for no end bound (applicable indefinitely from `valid_from_datetime`)

## Other responses

- `201`: The created price tier
- `400`: Invalid parameters
- `401`: Missing or invalid API token
- `403`: The API token lacks the required permission, or team restrictions hide the record
- `404`: Not Found

## Changes

- **2026-10-14** `e8b3a3d07ad5`: 12 info
  - added the required property `data/conditions/not_one_of_products/items/creator/updated_datetime` to the response with the `200` status
  - added the required property `data/conditions/not_one_of_products/items/creator/updated_datetime` to the response with the `201` status
  - added the required property `data/conditions/not_one_of_products/items/owner/updated_datetime` to the response with the `200` status
  - added the required property `data/conditions/not_one_of_products/items/owner/updated_datetime` to the response with the `201` status
  - …8 more
- **2026-09-26** `532edce931d0`: 6 info
  - added the new optional request property `conditions/not_one_of_product_tag_ids`
  - added the new optional request property `conditions/one_of_product_tag_ids`
  - added the required property `data/conditions/not_one_of_product_tags` to the response with the `200` status
  - added the required property `data/conditions/not_one_of_product_tags` to the response with the `201` status
  - …2 more
- **2026-09-15** `b5e11e00bdfb`: 3 breaking
  - the `percent` request property type changed from `integer` to `string`
  - the `data/percent` response's property type changed from `integer` to `string` for status `200`
  - the `data/percent` response's property type changed from `integer` to `string` for status `201`

[Operation history](https://skmtc.dev/distru/apis/distru-api/changes/public/v1/price-tiers/post.md)

---

[API](https://skmtc.dev/distru/apis/distru-api.md) - [All operations](https://skmtc.dev/distru/apis/distru-api/llms.txt) - [OpenAPI document](https://skmtc.dev/distru/apis/distru-api/revisions/9d851a88e82f?raw)
