Shipping rates
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
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
}'
| Field | Meaning |
|---|---|
code | Required. What a checkout and an order line store — the value every integration joins on. |
pricing_type | fixed, free or matrix. |
price, currency | The fixed price. |
matrix_basis | For matrix: weight, quantity, order_value or attribute. |
matrix_attribute | The named product attribute, when the basis is attribute. |
countries | Allow-list. Empty or null is unrestricted. |
free_above | Free-shipping threshold. |
quote_above | Above this, the method quotes rather than prices. |
carrier_id / carrier | The carrier it ships with. |
tax_class | A tax class code from the market. |
eta_days_min, eta_days_max | Override the carrier's transit promise. |
enabled | Defaults to false. |
position | The 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.
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.
| Route | Semantics |
|---|---|
POST …/tiers | Adds one row, leaving the rest alone — the edit for a merchant adding a heavier bracket. |
PUT …/tiers | Lays a whole table down at once. Set semantics. |
POST …/tiers/ladder | Generates 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
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
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:
| Key | Is |
|---|---|
rates | The 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. |
excluded | The 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. |
basis | How 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
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
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.