Shipping rates

Shipping methods on revenexx — the three pricing models, matrix tiers and the ladder generator, POST /v1/shipping/rates, and the cross-app tax-class usage check.

A shipping method is the line a buyer picks in the checkout, and it owns the price. The delivery promise — tracking template, cut-off, handling and transit days — is inherited from the carrier wherever the method states none of its own.

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

Methods

Request
curl -X POST https://api.revenexx.com/v1/shipping/methods \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "Content-Type: application/json" \
  -d '{
        "code": "standard",
        "name": "Standard delivery",
        "pricing_type": "fixed",
        "price": 4.90,
        "currency": "EUR",
        "countries": ["DE", "AT"],
        "free_above": 250,
        "carrier_id": "…",
        "tax_class": "standard",
        "enabled": true
      }'
FieldMeaning
codeRequired. What a checkout and an order line store — the value every integration joins on.
pricing_typefixed, free or matrix.
price, currencyThe fixed price.
matrix_basisFor matrix: weight, quantity, order_value or attribute.
matrix_attributeThe named product attribute, when the basis is attribute.
countriesAllow-list. Empty or null is unrestricted.
free_aboveFree-shipping threshold.
quote_aboveAbove this, the method quotes rather than prices.
carrier_id / carrierThe carrier it ships with.
tax_classA tax class code from the market.
eta_days_min, eta_days_maxOverride the carrier's transit promise.
enabledDefaults to false.
positionThe order the checkout prints them in.

A new method is quoted by nobody until two things are true: it is enabled, and — for a matrix method — it has tiers. Until then it appears in excluded with "matrix has no rate tiers configured" rather than in the rates.

carrier_id and the legacy carrier code are both accepted, and neither is verified against the carrier table here: an unmatched code becomes a plain carrier name on the rate, not an error.

Matrix tiers

A tier is one row of a matrix method's price table: a from_value threshold and the price charged at or above it.

Request
curl -X POST "https://api.revenexx.com/v1/shipping/methods/{method_id}/tiers" \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "Content-Type: application/json" \
  -d '{"from_value":10,"price":8.90}'

The bound is inclusive, and the winning tier is the one with the highest from_value at or below the measured value — so a measure of exactly 10 is priced by the tier at 10. The last tier has no upper bound.

What the number measures is the method's matrix_basis: kilograms in the market's own weight unit, items, money in the method's currency, or a named attribute.

RouteSemantics
POST …/tiersAdds one row, leaving the rest alone — the edit for a merchant adding a heavier bracket.
PUT …/tiersLays a whole table down at once. Set semantics.
POST …/tiers/ladderGenerates an evenly stepped table.
GET/PUT/DELETE …/tiers/{id}One row.
PUT …/tiers and the ladder route discard the ids of the rows they replace. Anything holding a tier id has to re-read.

Two rows may not share method_id + from_value — that is the 409. method_id is taken from the path on every write, so a body naming a different method is ignored rather than obeyed.

The ladder generator

Request
curl -X POST "https://api.revenexx.com/v1/shipping/methods/{method_id}/tiers/ladder" \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "Content-Type: application/json" \
  -d '{"from":0,"to":30,"step":5,"base_price":4.90,"step_price":2}'

The table a merchant describes in words — "0 to 30 kg, every 5 kg, €4.90 plus €2 a step" — without typing every row. It replaces the method's tiers by default; send replace: false to append.

Resolving rates

Request
curl -X POST https://api.revenexx.com/v1/shipping/rates \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "X-Revenexx-Market: de" \
  -H "Content-Type: application/json" \
  -d '{
        "country": "DE",
        "order_value": 129.90,
        "weight": 7.4,
        "currency": "EUR"
      }'

The question a checkout asks, and the only route that answers a price. Hand in the destination country, the order value, and whatever the matrix methods measure — a weight, a quantity or a named product attribute.

Back comes:

KeyIs
ratesThe methods that apply, in position order — which is the order the checkout should print, not cheapest-first unless the merchant ordered them so. Each with its price, free-above threshold, the carrier's delivery promise and tax already applied. Empty is a legitimate answer.
excludedThe enabled methods that did not apply, each with the sentence saying why. This is the diagnostic half of the answer, and it is why an empty rates never needs a support ticket to explain.
basisHow the answer was measured — the tenant settings that shaped it, echoed so the numbers can be re-derived.

An excluded reason is in words: disabled, a country the method or its carrier does not serve, a carrier that is paused or retired, a matrix missing its measure in the request, a matrix with no tiers configured, or no tier covering the measure.

tax.resolved: false means the rates are UNKNOWN, not untaxed. A tax rate that cannot be resolved is not an error, and it is not zero.

X-Revenexx-Market genuinely restricts the candidates here — methods assigned to this market plus the unassigned ones — which is one of only two routes where the header affects a read.

Seeding a shop

Request
curl -X POST https://api.revenexx.com/v1/shipping/methods/defaults \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..." \
  -H "Content-Type: application/json" \
  -d '{}'

Runs the carrier seed first, then creates any missing method: the three lines a shop is expected to offer — standard, express and pickup. The app runs it on install; calling it by hand afterwards is how a tenant that deleted one gets it back, and calling it twice costs nothing because it reconciles rather than seeds.

The seeded methods deliberately name no carrier — which carrier carries the standard method is a contract, not a default.

The tax-class usage check

Request
curl "https://api.revenexx.com/v1/shipping/tax-classes/reduced/usage" \
  -H "X-Revenexx-Tenant: <TENANT_SLUG>" \
  -H "X-Revenexx-Api-Key: rvxk_..."

Markets is the source of record for a tax rate, and this app points at it by code from two places: a method's own tax_class, and the tenant's shipping_tax_class fallback. Neither is a foreign key, and neither could be — a cross-app foreign key is not something the platform allows. So integrity is a question one app asks the other, and this route is the answering half.

It is asked before a destructive edit: the markets app calls it when an operator tries to delete a tax class, and a count above zero is what stops the delete rather than leaving these methods pointing at a code nobody serves.

Matched as a code, not a row: a tax class is unique per market, so reduced may exist in several and a method naming it does not say which one it meant. It reports at most 500 methods and names the first 20. Every code answers — one nobody points at is in_use: false, never a 404.

That pattern is worth copying in your own app. When you point at another app's row by code, publish the usage route that lets it ask.

Where to go next

  • Carriers and tracking — who carries it, and the tracking link.
  • Markets — where tax classes and their rates live.
  • Orders — where a chosen method and its carrier land on a shipment.
Was this page helpful?