Price lists

Price lists and entries on revenexx, the bulk and ladder writes, and POST /v1/prices/resolve — the one route that answers a price, with the full resolution order.

Everything else in the prices app configures prices. POST /v1/prices/resolve is the one route that answers them, and a storefront reaches it on every listing, every product page and every cart.

Base path: https://api.revenexx.com/v1/prices.

The list

Request
curl "https://api.revenexx.com/v1/prices/lists?limit=50" \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..."
FieldMeaning
codeUnique per tenant. What an import, an ERP export and every integration addresses it by, and what the default_price_list_code setting names.
name, labels, descriptionOperator-facing. description is never shown to a buyer.
currencyISO 4217, and the currency of every amount in the list — entries carry none of their own.
tax_basisnet or gross. The one fact a price cannot be without. Null inherits the market's answer.
statusOnly active lists are candidates. inactive retires a list without deleting its prices.
valid_from, valid_untilThe whole list's validity window. Outside it, the list is not a candidate at all.
contact_idBuyer scope: this one contact. The most specific scope there is.
organization_idBuyer scope: buyers of this organization.
channel_idBuyer scope: this sales channel.
requires_authWhen true, the list resolves only for a buyer with a contact or organization context. An anonymous resolve never matches it.
priorityTie-break within one specificity group, higher first.
is_defaultThe fallback list. Within its group it deliberately sorts last.
metadataFree-form, never read by this app.

tax_included is a legacy mirror of tax_basis. Only true is read as a statement (gross); false is the column default and is not read as anybody having chosen net.

POST /v1/prices/lists/defaults seeds the standard list. POST /v1/prices/lists/{id}/make-default moves the default flag in one operation.

The entries

An entry is one rung of one item's quantity ladder.

Request
curl -X POST "https://api.revenexx.com/v1/prices/lists/{list_id}/entries" \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "Content-Type: application/json" \
  -d '{"sku":"ACME-4711-BLK","quantity_min":1,"unit_price":19.90,"unit":"pcs"}'
FieldMeaning
product_id / skuAn entry needs one of the two. sku is matched exactly — never normalised or case-folded.
quantity_minLowest quantity this price applies from.
unit_pricePrice for one unit of unit, in the list's currency and on the list's tax basis. A decimal in major units (19.90), never minor units.
unitFree text — pcs, m, kg, a packaging size. This app neither validates nor converts it.
price_typestandard is a number. on_request is the explicit no-price marker.
valid_from, valid_untilThe entry's own window. This is how a promo price is expressed: a second rung for the same item and quantity, live only inside its window.

The ladder for one item is the set of entries sharing its identity, and the rung with the highest quantity_min at or below the requested quantity wins. Below the first rung, the first rung applies.

Writing entries in bulk

RouteDoes
PUT /v1/prices/lists/{list_id}/entriesReplace all entries of the list.
POST /v1/prices/lists/{list_id}/entries/bulkBulk-upsert entries.
POST /v1/prices/lists/{list_id}/entries/adjustBulk-change prices — a percentage or absolute move. Only writes the rows whose price actually moved, so updated_at stays a real "the price changed here" marker.
POST /v1/prices/lists/{list_id}/entries/ladderGenerate a quantity ladder for one item, instead of posting each rung.

Resolving a price

Request
curl -X POST https://api.revenexx.com/v1/prices/resolve \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "X-Revenexx-Market: de" \
  -H "Content-Type: application/json" \
  -d '{
        "currency": "EUR",
        "contact_id": "…",
        "organization_id": "…",
        "channel_id": "…",
        "items": [
          { "sku": "ACME-4711-BLK", "quantity": 12 },
          { "sku": "ACME-9001", "quantity": 1 }
        ]
      }'

Up to 200 items per call — a whole cart or a whole listing in one round trip. The answer holds one entry per item, in request order, with the unit price this buyer pays, the net/gross pair, the tax rate, the list that decided it, and that item's full quantity ladder.

This is the designated tenant override point of the prices app. A tenant whose pricing really lives elsewhere replaces this one capability and keeps the rest. See From manifest to installed.

at sets the instant every validity window is evaluated at, which is how a promo price is previewed before it starts.

The resolution order, in full

This is the value of the app, and it is not guessable from the field types.

1. Candidates. A list is a candidate when it is active, its currency equals the currency of the call, the instant at falls inside its validity window, it is visible in the buyer's market, and its buyer scope matches or is open. A requires_auth list is dropped for a buyer with neither contact_id nor organization_id.

Nothing is ever converted. A list in another currency simply does not price the item. A wrong currency is not a rounding problem — it is an empty answer.

The X-Revenexx-Market header scopes which lists are visible; lists assigned to no market are global and always visible.

2. Specificity decides first, and priority never overrules it.

ScopeLevel
contact-scoped4
organization-scoped3
channel-scoped2
open0

An organization list at priority: 0 therefore wins over an open list at priority: 100.

3. Within one specificity level: priority descending, then non-default before default — the default list is deliberately last, so it prices only what nothing else did.

4. A genuine tie (same specificity, same priority, same default flag) is settled by the tenant's price_list_priority_tiebreak setting — lowest_price, highest_price, newest or code — never by the order the database happened to return rows in. The setting in force is echoed in basis.price_list_priority_tiebreak.

5. The first list that prices the item wins, and the search stops there — even if a later, less specific list is cheaper. Its full tier ladder comes back in tiers.

6. An on_request entry stops the search too, and inside a tie it outranks every price: a list that says "ask us" for this buyer is authoritative and cannot be undercut by a list that happens to sort after it.

7. Nothing found → on_request, never 0, with a reason:

text
not_priced    on_request_entry    anonymous_denied    no_identity

A storefront shows "price on request". It must never show €0.

Amounts

unit_price is per one unit of the entry's unit, in currency, as a decimal in major units (19.90) — never cents — and on the basis tax_basis names. tax_basis comes from the list's own column, else a legacy tax_included: true on it, else the tenant's answer.

Where to go next

  • Markets — the currency and tax class a price resolves against.
  • Channels — channel scope, specificity level 2.
  • Organizations — the price_list code on a company.
  • Cartsprice_snapshot_mode at conversion.
Was this page helpful?