Loading Data
Your catalogue, pricelists and prices come from your own systems, usually a PIM or an ERP. This page covers getting them into Quantaprice: the first full load, the changes after it, and deletes.
What to Load, in Order
| Order | Entity | Endpoint | Identified by |
|---|---|---|---|
| 1 | Articles | POST /article/batch | sku |
| 2 | Pricelists, parents before children | POST /pricelist/batch | code |
| 3 | Prices | POST /price/batch | sku + pricelist |
| 4 | VAT classes of articles that are not standard-rated | POST /article-taxclass | sku_code + tax_area_code |
Currencies, exchange rates, tax areas and VAT rates are already there in a new tenant; see Account & Tenant Setup.
Every entity also has a single-item endpoint (POST /article, POST /pricelist, POST /price). Use the batch endpoints for anything more than a handful of items.
Articles
POST /article/batch
[
{ "sku": "SKU-CHAIR-001", "name": "Office chair", "metadata": { "category": ["chairs"], "brand": ["ergo"] } },
{ "sku": "KIT-OFFICE", "name": "Office kit", "type": "bundle",
"components": [ { "sku": "SKU-CHAIR-001", "quantity": 1 }, { "sku": "SKU-DESK-002", "quantity": 1 } ] }
]
skuis required, at most 64 characters.metadataholds lists of values per key. Price rules and pricelist filters select by it; see Metadata.- A bundle lists its components, which must be existing articles that are not bundles. Write the components first. See Bundles.
- Writing an article again replaces it, metadata included, from its
valid_from: now when absent, or a later moment to schedule the change. The version it replaces stays readable withasOf. See Historical Pricing.
A price can be written for a SKU without an article. It is then priced, but price rules do not apply to it, and its VAT class is STANDARD everywhere. Turn on strict.article.validation to refuse such prices.
Pricelists
POST /pricelist/batch
[
{ "code": "base-sek", "name": "Base SEK", "currency_code": "SEK", "vat_mode": "NET" },
{ "code": "wholesale-sek", "currency_code": "SEK", "vat_mode": "NET", "parent_code": "base-sek",
"price_rules": [ { "code": "wholesale", "filter": "", "expression": "sales * 0.85" } ] }
]
code,currency_codeandvat_mode(NETorGROSS) are required. The currency must exist and be active.parent_codemust name an existing pricelist, or one earlier in the same batch. A child listed before its parent fails.start_dateandend_datelimit when the pricelist applies. Outside that window it prices nothing, and a query that names it is refused.price_rulesreplaces the pricelist's rules as a whole. A rule that does not compile fails the item. See Price Rules.- Writing a pricelist again replaces it from its
valid_from: now when absent, or a later moment to schedule the change. Prices its rules or metadata affect are recalculated from that moment. The version it replaces stays readable withasOf.
Prices
POST /price/batch
[
{ "sku": "SKU-CHAIR-001", "pricelist": "base-sek", "price": { "fields": { "fixed": 2390, "cost": 1400 } } },
{ "sku": "SKU-CHAIR-001", "pricelist": "base-sek", "start_date": "2026-12-01T00:00:00+01:00",
"price": { "fields": { "fixed": 2490, "cost": 1400 } },
"quantity_breaks": [ { "quantity_break": 10, "fixed_price": 2290 } ] }
]
- Send the price fields in
price.fields. Without rules,fixedis the sales price; with rules, the rules compute it from the fields. See Price Structures. - Every write is a new version from its
start_date, now when absent. A start date in the past is moved up to now: history is never rewritten. remove_future_changes: truealso removes the versions already scheduled after this one.- A version identical to the current one is accepted but stores nothing, and records no change.
- A bundle cannot be priced directly; its price comes from its components and its bundle rule.
A price for a pricelist code that does not exist yet is accepted, and appears once the pricelist is created. Turn on strict.pricelist.validation to refuse it.
Batch Results
A batch validates each item on its own and writes the valid ones together:
{
"accepted": 1,
"failed": [
{ "code": "child", "message": "Unknown parent_code: later" }
]
}
| Status | Meaning |
|---|---|
202 | At least one item was written, or the batch was empty. failed lists the others and why. |
400 | Every item failed. |
413 | The batch holds more items than your plan allows in one request, or its body is too large. Split it. |
422 | Your plan's article, pricelist or price quota is full. Nothing was written. |
429 | Your ingest rate limit is used up. Wait Retry-After seconds and send it again. |
A batch is not atomic: when some items fail, the rest are still written. Fix the failed items and send only those again. Sending an accepted item again is harmless.
Each item costs one unit of your ingest rate limit. See Rate Limits and Plan and Limits.
Keeping Data in Sync
After the first load, send changes as they happen in your source system: one batch per change set, or a batch every few seconds or minutes. Prices you send unchanged cost a rate-limit unit but store nothing, so a periodic full re-send of a pricelist works too, at the cost of rate limit.
To follow changes the other way, from Quantaprice to your systems, see Change Events.
Deleting
Deletes are soft: the entity stops taking part from the delete's moment on, and its history stays readable with as_of. A delete can be scheduled with a future time.
| Delete | Endpoint |
|---|---|
| A price | DELETE /price with {"sku": ..., "pricelist": ...}, or DELETE /price/batch with a list |
| A pricelist | DELETE /pricelist/{code} |
| An article | DELETE /article/{sku} |
Deleting a price also removes the prices other pricelists inherited from it. Deleting a pricelist deletes its prices; creating the pricelist again brings back the pricelist, not its prices: send them again. Deleting an article deletes its prices on every pricelist with it, and a bundle that contains it can no longer be priced. Writing the article again brings the article back, not its prices: send them again. A price sent for a deleted article is never served; with strict.article.validation on, it is refused.
Large First Loads
- Send batches of a few thousand items, and run a few in parallel. More parallel requests do not load faster once your ingest rate limit is reached.
- On
429, waitRetry-Afterseconds and retry the same batch. - For a load of millions of prices, turn on
ingest.skip.dedupfor the duration of the load and turn it off afterwards. It skips the check for unchanged prices, so an identical re-send then stores a new version. - A large load records a change per price in the change log, and sends each one to your webhooks. Systems that follow them should expect the volume, or resynchronise after the load; see Change Events.