API Conventions and Errors
The rules every endpoint follows. The API Reference documents each endpoint on its own.
Requests
- Base URL:
https://YOUR_TENANT.quantaprice.com/api, withAuthorization: Bearer <token>on every request; see Authentication. - JSON field names and parameters are
snake_case. Two older parameters,asOfandafterSku, keep their camel case. - Identifiers are your business codes: SKU, pricelist code, currency code, tax area. Quantaprice has no ids of its own for you to keep.
- Timestamps are RFC 3339 instants. Send them with an offset,
Zor+01:00. A bare date,2026-12-01, means midnight UTC. Responses are in UTC, with milliseconds when they are not zero. - Amounts are decimal JSON numbers. A numeric string,
"12.50", is accepted too. Amounts are kept to four decimals, rounded half-up.
Time
Most data has a history:
- Reads take
as_of(in a request body) orasOf(as a parameter) and answer as of that moment; the default is now. - Writes take a start date and can be scheduled ahead. A price written with a start date in the past takes effect now.
- Deletes are soft. A deleted entity carries
deleted_at, and its history stays readable withas_of.
See Historical Pricing and Scheduled Updates.
Writes
Writes answer 202 Accepted. Single writes have an empty body. Batch writes say how many items were accepted, and list the ones that failed, each with its key and the reason:
{ "accepted": 1, "failed": [ { "sku": "KIT", "pricelist": "base-sek", "message": "Missing required fields" } ] }
A batch is not atomic: the valid items are written even when others fail. See Loading Data.
Paging
Listings come in two kinds.
Numbered pages, for listings such as GET /article, GET /pricelist and GET /price: pass page (from 1) and size. The answer has items, page, size and total.
Cursors, for change logs and GET /price/skus-for-pricelist/{code}: pass the previous answer's next_cursor back until has_more is false. A cursor is opaque; send it back unchanged. See Change Events.
Errors
A failed request answers with its HTTP status and this body:
{
"timestamp": "2026-10-13T09:50:39.798Z",
"status": 404,
"error": "Not Found",
"message": "Article 'SKU-NOPE' not found"
}
error is the status's name; message says what went wrong, for a person to read. Decide on the status code, not the message text.
| Status | Meaning | What to do |
|---|---|---|
400 | The request is invalid: a missing or malformed field, an unknown currency, a rule that does not compile | Fix the request. message says what is wrong. |
401 | No token, or one that is expired, malformed, or for another tenant | Fetch a new token and retry. |
403 | The request is not allowed: a setting your plan does not include, or an action only a signed-in person can take in the console | Do not retry. |
404 | The entity does not exist at as_of | |
410 | A change-log position that the log no longer holds | Resynchronise; see Change Events. |
413 | The request is larger than one request may be | Split it; retrying will not help. See Rate Limits. |
422 | Your plan's quota of articles, pricelists or prices is full | See Plan and Limits. |
429 | Your rate limit is used up for now | Wait Retry-After seconds and retry. |
500 | An error on our side | Retry later; contact support if it persists. |
503 | The engine is starting, restoring a backup or changing over, or (for a write) its storage is nearly full | Wait Retry-After seconds and retry. |
Retry 429 and 503 with the same request. Article, pricelist and price writes replace what is there, so sending one twice does no harm.
Caching
Do not cache prices. A price can change at any second: a scheduled change takes effect, a parent pricelist changes, an exchange rate is updated. A query is fast enough to make on every page view, and answers with the price in force now.
GET /price/{sku}/{pricelist}/{tax_area} answers with an ETag. Send it back in If-None-Match to get 304 Not Modified, without a body, while the price is unchanged.