Skip to main content

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​

OrderEntityEndpointIdentified by
1ArticlesPOST /article/batchsku
2Pricelists, parents before childrenPOST /pricelist/batchcode
3PricesPOST /price/batchsku + pricelist
4VAT classes of articles that are not standard-ratedPOST /article-taxclasssku_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 } ] }
]
  • sku is required, at most 64 characters.
  • metadata holds 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 with asOf. 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_code and vat_mode (NET or GROSS) are required. The currency must exist and be active.
  • parent_code must name an existing pricelist, or one earlier in the same batch. A child listed before its parent fails.
  • start_date and end_date limit when the pricelist applies. Outside that window it prices nothing, and a query that names it is refused.
  • price_rules replaces 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 with asOf.

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, fixed is 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: true also 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" }
]
}
StatusMeaning
202At least one item was written, or the batch was empty. failed lists the others and why.
400Every item failed.
413The batch holds more items than your plan allows in one request, or its body is too large. Split it.
422Your plan's article, pricelist or price quota is full. Nothing was written.
429Your 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.

DeleteEndpoint
A priceDELETE /price with {"sku": ..., "pricelist": ...}, or DELETE /price/batch with a list
A pricelistDELETE /pricelist/{code}
An articleDELETE /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, wait Retry-After seconds and retry the same batch.
  • For a load of millions of prices, turn on ingest.skip.dedup for 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.