Price lists, bulk edits and imports
Price lists and quantity tiers, the bulk product editor, and the CSV import pipeline.
The price-list model, its rules and the per-variant quantity ladders, together with the two ways a merchant changes many products at once: the bulk editor and the CSV importer. Nothing on the storefront or in the checkout reads a price list in this release. See Price lists and quantity tiers for exactly what does and does not take effect, and Importing for the whole import procedure.
List price lists
/api/v1/kahunacart/price-lists
Every price list with its header and its rules. A price list is a named set of prices that applies to the shoppers its rules name — a trade list, a wholesale list, a members' list — which is what makes it a different thing from a sale: a sale is a discount every shopper gets and the catalog advertises, a price list is a price. kind is override (what this variant costs for this shopper) or sale (a cut, which is barred from supplying the strikethrough reference price). live says whether the list is active and inside its window right now. Price rows are not listed here; ask get_price_list for one list's.
Get a price list
/api/v1/kahunacart/price-lists/{id}
One price list with its rules in order and every price row it holds, variant by variant and rung by rung. Amounts come back both as amount_minor and as a decimal amount.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | Price list id |
Create a price list
/api/v1/kahunacart/price-lists
Create a named set of prices. name is required and is never shown to a shopper. Rules decide who the list applies to and are joined with AND, so a list with no rules applies to everyone; each rule is an attribute from list_rule_attributes, an operator that attribute offers, and a value. An attribute the vocabulary does not publish is refused, naming the key. Prices are the rows the list charges: one per variant per quantity range, with min_quantity starting the rung and max_quantity ending it or omitted for an open-ended top rung. A rung is a price, not a discount. Rungs for one variant may not overlap. A row names its variant by variant_id or by sku in its place, never both; a SKU no variant carries, or one two variants share, is refused with 422 naming it. On a store that sells in more than one currency, currency makes the list in that currency, and every price on it is in it.
Parameters
| Name | Type | Description |
|---|---|---|
| name required | string | The merchant-facing name of the list |
| currency optional | string | On a store that sells in more than one currency, the currency this list's prices are in (the store's base currency when left out). Fixed once the list is made: a price list is only used for a cart in its own currency, so for trade prices in dollars and in euro make two lists. A currency the store does not sell in is refused. Amounts are read with that currency's own decimal places, so yen takes whole numbers |
| kind optional | string |
One of:
override, saleDefault:
override |
| priority optional | integer | Higher wins when two lists match a variant equally well |
| starts_at optional | integer or null | UTC epoch seconds, inclusive |
| expires_at optional | integer or null | UTC epoch seconds, exclusive |
| active optional | boolean |
Default:
true |
| show_in_catalog optional | boolean |
Show this list's price in the catalog as a discount off the catalog price — struck through with a badge, like a sale — for a shopper the list applies to. Off for a trade list whose price should read as the price.
Default:
false |
| catalog_label optional | string | The badge beside that price, e.g. "Founders price". Empty uses the list name. |
| rules optional | array of object | Joined with AND; an empty list applies the price list to everyone. Keys: `attribute`, `operator`, `value` |
| prices optional | array of object | Keys: `variant_id`, `sku`, `min_quantity`, `max_quantity`, `amount`, `amount_minor` |
Update a price list
/api/v1/kahunacart/price-lists/{id}
Change a price list's header, its rules, its prices, or any combination. Sending rules replaces the whole set, empty list included, which is how the last rule is removed; omitting it leaves the rules standing. prices follows the same three-state contract, so switching a list off is one field and not a round trip through every price. A price row names its variant by variant_id or by sku, as create_price_list describes. A list's currency cannot be changed once it is made: its prices are in it, and a currency naming a different one is refused with 422.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | Price list id |
| name optional | string | |
| currency optional | string | Only on a store that sells in more than one currency. Naming the list's own currency changes nothing; a different one is refused, because a list stays in the currency it was made in |
| kind optional | string |
One of:
override, sale |
| priority optional | integer | |
| starts_at optional | integer or null | |
| expires_at optional | integer or null | |
| active optional | boolean | |
| show_in_catalog optional | boolean | |
| catalog_label optional | string | |
| rules optional | array of object | Replaces the whole set when present; an empty list removes every rule. Keys: `attribute`, `operator`, `value` |
| prices optional | array of object | Replaces every price row when present; an empty list removes them all. Keys: `variant_id`, `sku`, `min_quantity`, `max_quantity`, `amount`, `amount_minor` |
Delete a price list
/api/v1/kahunacart/price-lists/{id}
Delete a price list, its rules and every price row it holds. Prices go back to what the variant says the moment it is gone.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | Price list id |
List a variant's quantity tiers
/api/v1/kahunacart/price-tiers
One variant's own quantity ladder — the "buy ten and save" breaks that belong to no price list and apply to every shopper. Each rung is a unit price at a quantity range, lowest rung first. A price list's rows beat these; these beat the variant's own price. Name the variant by variant_id or by sku. On a store that sells in more than one currency each currency has its own ladder, and currency says which one (the base currency when left out).
Parameters
| Name | Type | Description |
|---|---|---|
| variant_id optional | integer | Variant id; send this or sku |
| sku optional | string | The variant's SKU, in place of variant_id |
| currency optional | string | The currency of the ladder to read, on a store that sells in more than one. Left out, the base currency. A currency the store does not sell in is refused |
Set a variant's quantity tiers
/api/v1/kahunacart/price-tiers
Replace one variant's quantity ladder. An empty tiers list removes it. Rungs may not overlap and a rung starts at 1 or more; leave the top rung's max_quantity out so it covers every larger order. A gap between rungs is allowed and means a quantity in the gap is priced by the variant's own price. Name the variant by variant_id or by sku. On a store that sells in more than one currency this replaces the ladder in one currency, named by currency (the base currency when left out), and leaves the ladders in the others standing. A variant with no ladder in a currency has no quantity breaks in it, and nothing is worked out from another currency's ladder.
Parameters
| Name | Type | Description |
|---|---|---|
| variant_id optional | integer | Variant id; send this or sku |
| sku optional | string | The variant's SKU, in place of variant_id |
| currency optional | string | The currency of the ladder being replaced, on a store that sells in more than one. Left out, the base currency. Amounts are read with that currency's decimal places and a number with more decimals than it has is refused, not rounded |
| tiers required | array of object | Keys: `min_quantity`, `max_quantity`, `amount`, `amount_minor` |
Preview a bulk edit
/api/v1/kahunacart/products/bulk/preview
Say what a bulk edit would do without doing any of it — how many products and variants it would touch, and the before and after of the first ten. Writes nothing at all. Same body as bulk_update_products.
Parameters
| Name | Type | Description |
|---|---|---|
| product_ids required | array of integer | The products to change. |
| operations required | array of object | What to change. Each entry names a field (price, compare_price, stock_quantity, status, visibility, featured, min_qty, max_qty, qty_step, sold_individually, exclude_order_rules, exclude_category_rules), a mode (set, increase, decrease), a unit (fixed or percent — numeric fields only, and percent cannot be used with set) and a value. Money is a decimal string in the store currency; a percentage is a decimal string such as "10" or "12.5". A quantity limit can only be set, and 0 removes it. Two instructions may not name the same field. On a store that sells in more than one currency, currency_price sets a price in one of them and currency_fill works it out from each variant's base price (see `currency`, `target`, `rate` and `rounding` below); two instructions may not write the same price in the same currency. Keys: `field`, `mode`, `unit`, `value`, `currency`, `target`, `rate`, `rounding` |
Bulk edit products
/api/v1/kahunacart/products/bulk
Change price, compare price, stock quantity, status, visibility, featured or the quantity limits across many products in one transaction, using Change to / Increase by / Decrease by with a fixed amount or a percentage. Writes one history entry per product recording what changed from what to what. Stock changes go through the stock service so they fire the usual events. Refused past admin.bulk.max_rows rather than truncated. Preview it with preview_bulk_update first.
Parameters
| Name | Type | Description |
|---|---|---|
| product_ids required | array of integer | The products to change. |
| operations required | array of object | What to change. Each entry names a field (price, compare_price, stock_quantity, status, visibility, featured, min_qty, max_qty, qty_step, sold_individually, exclude_order_rules, exclude_category_rules), a mode (set, increase, decrease), a unit (fixed or percent — numeric fields only, and percent cannot be used with set) and a value. Money is a decimal string in the store currency; a percentage is a decimal string such as "10" or "12.5". A quantity limit can only be set, and 0 removes it. Two instructions may not name the same field. On a store that sells in more than one currency, currency_price sets a price in one of them and currency_fill works it out from each variant's base price (see `currency`, `target`, `rate` and `rounding` below); two instructions may not write the same price in the same currency. Keys: `field`, `mode`, `unit`, `value`, `currency`, `target`, `rate`, `rounding` |
Read a product's history
/api/v1/kahunacart/products/{id}/history
Read what has happened to one product — every bulk edit and every import that changed it, newest first, each carrying the fields that changed with their before and after values in minor units and the person who made the change. Useful for answering "why is this price different".
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | The product to read. |
| per_page optional | integer | How many entries to return (default 50). |
List importable fields
/api/v1/kahunacart/import/fields
List what the CSV importer can import — the five entities (categories, products, variants, customers, orders) in the order a whole-store migration has to run them, the fields each accepts with their types and the column headings they are usually exported under, which keys rows can be matched on, and which fields are required. Fields contributed by add-on plugins are included. Call this before writing a mapping. In an orders file the currency field says which currency each order is in (the store's own when the cell is empty, or any currency the store sells in), and that row's amounts are read in it, so 1500 in a yen row is 1500 yen. A row in a currency the store does not sell in fails on its own.
List saved import mappings
/api/v1/kahunacart/import/mappings
List the saved column mappings, each with the entity it describes, the key rows are matched on, what it does with rows that already exist, and the column-to-field map itself. The same mappings the admin importer and bin/plugin kahunacart import --mapping= both use.
Parameters
| Name | Type | Description |
|---|---|---|
| entity optional | string |
Narrow to one entity.
One of:
categories, products, variants, customers, orders |
Save an import mapping
/api/v1/kahunacart/import/mappings
Save a reusable column mapping so the same export can be imported again next month without describing the file twice. columns maps each column heading in the file to one field name from list_import_fields; a heading you leave out or map to an empty string is ignored. The match key must be one of the columns, and every required field for the entity must be mapped.
Parameters
| Name | Type | Description |
|---|---|---|
| name required | string | What to call it. Unique across the store. |
| entity required | string |
One of:
categories, products, variants, customers, orders |
| match_key required | string | The field rows are looked up by, which is what makes the import idempotent. Products accept sku, slug or id; categories slug or id; variants sku or id; customers email or id; orders number or id. |
| on_existing optional | string |
What to do with a row whose match key is already in the store.
One of:
update, skipDefault:
update |
| columns required | object | Column heading in the file to field name, for example {"Regular price": "compare_price", "SKU": "sku"}. On a store that sells in more than one currency, products and variants files may also map columns to price_EUR and compare_price_EUR (one pair per currency the store sells in); an empty cell leaves that amount alone and the cell "remove" takes it away. |
Update an import mapping
/api/v1/kahunacart/import/mappings/{id}
Change a saved mapping. Anything you leave out keeps its stored value, so renaming one does not mean posting its whole column map back.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | |
| name optional | string | |
| entity optional | string |
One of:
categories, products, variants, customers, orders |
| match_key optional | string | |
| on_existing optional | string |
One of:
update, skip |
| columns optional | object | Column heading in the file to field name. |
Delete an import mapping
/api/v1/kahunacart/import/mappings/{id}
Delete a saved column mapping. Import runs that used it keep their own copy of the columns, so nothing already run or running is affected.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer |
Rehearse an import
/api/v1/kahunacart/import/dry-run
Rehearse an import of an already-uploaded file: the same writers, the same values, inside a transaction that is always rolled back. Answers what would be created, updated, skipped and failed, with each failure carrying the line number of the merchant's own file. Writes nothing at all — no rows, no run record. Give upload (the token a file upload answered with) and either mapping_id or an inline mapping. An orders row keeps its own currency, and a currency the store does not sell in is reported as that row's failure.
Parameters
| Name | Type | Description |
|---|---|---|
| upload required | string | The upload token, from the admin's file upload. |
| mapping_id optional | integer | A saved mapping to use. |
| mapping optional | object | An inline mapping instead of a saved one. Keys: `entity`, `match_key`, `on_existing`, `columns` |
| limit optional | integer | Rehearse only the first N rows. |
Start an import
/api/v1/kahunacart/import/runs
Queue an import of an already-uploaded file. The work happens in the background job queue a batch at a time, so read its progress with get_import_run. Nothing is ever deleted and the run is idempotent on the mapping's match key, so running the same file twice does what running it once does. Importing customers or orders needs the orders permission as well.
Parameters
| Name | Type | Description |
|---|---|---|
| upload required | string | The upload token, from the admin's file upload. |
| mapping_id optional | integer | A saved mapping to use. |
| mapping optional | object | An inline mapping instead of a saved one. Keys: `entity`, `match_key`, `on_existing`, `columns` |
| filename optional | string | The merchant's own name for the file, for the runs list. |
List import runs
/api/v1/kahunacart/import/runs
List recent import runs, newest first, with their entity, status, progress and counts of rows created, updated, skipped and failed.
Parameters
| Name | Type | Description |
|---|---|---|
| per_page optional | integer | How many runs to return (default 25). |
Read an import run
/api/v1/kahunacart/import/runs/{id}
Read one import run's progress and its first fifty failed rows, each with the line number of the merchant's own file and the reason it failed. The full error report is downloadable as a CSV from the admin.
Parameters
| Name | Type | Description |
|---|---|---|
| id required | integer | The run to read. |
Upload an import CSV
/api/v1/kahunacart/import/uploads
Store a CSV and answer with its header row and a guess at each column
Download an import error report
/api/v1/kahunacart/import/runs/{id}/errors.csv
The error report, as a CSV to correct and import again
Saving a price list queues every product for reindexing, because its rules are evaluated against a cart rather than matched by a query. Replacing a variant's tier ladder queues that variant's product alone.
preview answers with what a bulk edit would change and writes nothing, which is the call to make first. admin.bulk.max_rows caps how many products one request may touch.
A dry run of an import writes nothing. A run that stops can be continued rather than restarted, and the errors route answers a CSV carrying your own file's line numbers.