Shipping Price Matrix

Introduction

The shipping price matrix is the tariff reference used by DJUST to calculate shipping costs at checkout. Each row in the matrix defines a price for a unique combination of dimensions: logistic family, shipping zone, shipping type, and optionally supplier (in marketplace contexts).

Operators manage this matrix entirely via the API — creating, reading, updating, and deleting price lines in bulk.


Key Concepts

Price Line Dimensions

Each price line is scoped by a combination of four dimensions:

DimensionDescription
logisticFamilyIdThe logistic family (e.g. "fragile-items", "standard"). See Logistic Families.
shippingZoneIdThe geographical zone (e.g. "eu-west", "north-america"). See Shipping Zones and Types.
shippingTypeIdThe delivery mode (e.g. "express-24h", "standard"). See Shipping Zones and Types.
supplierIdThe supplier (marketplace context only). Optional — omit for single-vendor setups.
⚠️

Warning: The combination of these four dimensions must be unique across the matrix. Attempting to create a duplicate returns error F-E-003.

Price and Franco Amount

FieldTypeRules
pricenumberThe shipping cost. Must be >= 0.
francoAmountnumber or nullThe order amount threshold above which shipping is free. Must be > 0 if provided. Set to null for no free-shipping threshold.

How Shipping Fees Are Calculated at Checkout

When the Shipping module is enabled (shippingEnabled: true), DJUST resolves shipping fees dynamically from the price matrix throughout the checkout. No API contract change is required on your side: the cart v2 and order DTOs expose the same fields and structure — only the values are now driven by the matrix.

Logistic Orders

At checkout, the cart is split into logistic orders, and each logistic order carries its own shipping fees:

  • E-commerce — one logistic order per logistic family.
  • Marketplace — one logistic order per supplier × logistic family combination.

The matrix is evaluated per logistic order along the dimensions logistic family × shipping zone × shipping type (plus supplier in marketplace).

Shipping Zone Resolution

The shipping zone is derived from the customer's shipping address:

  • Priority is given to the address department.
  • If no department is available, the zone is resolved from the postal code.

Free Shipping (Franco)

The francoAmount threshold is applied per logistic order, never on the global cart. When a logistic order reaches its threshold, its shipping fees drop to 0 — independently of the other logistic orders in the same cart.

Dynamic Recalculation

Shipping fees are recomputed on every cart readGET /v2/shop/carts/{cartId} and GET /v2/shop/carts/{cartId}/lines — so the amounts always reflect the current matrix and the current shipping address in real time.

Selection and Snapshot

  • Selecting a shipping type and committing the addressPUT /v2/shop/commercial-orders/{id}/shipping-information (ORDER-215) applies the matrix price to the corresponding logistic order.
  • Validating the order — the validation step (ORDER-212) freezes (snapshots) the shipping fees on each logistic order in the fields totalShippingFeesWithTax, totalShippingFeesWithoutTax, and totalShippingTaxAmount. If the matrix changed between selection and validation, the price is recomputed at validation time and it is this new value that is frozen.
flowchart LR
  %% Styles (Readme)
  classDef create   fill:#e8f1ff,stroke:#2f6feb,stroke-width:2px,color:#0b3d91;
  classDef read     fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#1e1b4b;
  classDef update   fill:#e0f7fa,stroke:#06b6d4,stroke-width:2px,color:#0c4a6e;
  classDef add      fill:#ecfdf5,stroke:#10b981,stroke-width:2px,color:#064e3b;
  classDef remove   fill:#fee2e2,stroke:#ef4444,stroke-width:2px,color:#7f1d1d;
  classDef decision fill:#fff4e5,stroke:#f59e0b,stroke-width:2px,color:#7a3e00;
  classDef place    fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d;
  classDef sys      fill:#f2f4f7,stroke:#475569,stroke-width:2px,color:#111827;
  classDef ok       fill:#ecfdf5,stroke:#10b981,stroke-width:2px,color:#064e3b;
  classDef stop     fill:#fee2e2,stroke:#ef4444,stroke-width:2px,color:#7f1d1d;

  A["🧾 Read cart<br>GET /v2/shop/carts/{cartId}<br>fees recomputed"]:::read
  B["📦 Split into<br>logistic orders"]:::sys
  C{"Matrix line for<br>zone x type x family"}:::decision
  D["🔄 Select shipping type<br>PUT .../shipping-information"]:::update
  E["✅ Validate order<br>fees snapshot frozen"]:::place
  F["⛔ Checkout blocked<br>no shipping type offered"]:::stop

  A --> B --> C
  C -->|"line found"| D --> E
  C -->|"out of zone / no line"| F

  style A rx:8,ry:8
  style B rx:8,ry:8
  style C rx:8,ry:8
  style D rx:8,ry:8
  style E rx:8,ry:8
  style F rx:8,ry:8

Blocking Cases at Checkout

SituationResult
Shipping address is outside every configured zone, or the combination has no matrix lineNo shipping type is returned — checkout is blocked
The selected shipping type no longer matches a matrix line (line deleted during checkout)404
No matrix line is available for the combination, at selection or at validation409

Warnings During Checkout

Non-fatal and fatal conditions are surfaced through blockedLineInformation:

  • Blocking — a reference cannot be found, or a shipping type has been made inactive.
  • Informational — the price changed following a matrix recalculation.
⚠️

Warning: Because fees are recomputed on every cart read and re-evaluated at validation, a matrix change made mid-checkout can alter the amount the customer finally pays. The value frozen at validation is the source of truth for the order.

Marketplace

In a marketplace context, shipping fees are paid out to the supplier in the payment split. The marketplace does not take a commission on shipping.

Coexistence with Legacy Behavior

Module stateBehavior
shippingEnabled: falseLegacy behavior is unchanged — shipping is driven by the offer custom fields shippingTaxe / shippingCode.
shippingEnabled: trueThe price matrix is used exclusively; the legacy offer custom fields are ignored.

Key Endpoints

ActionMethodPathOperationIdResponse
Create price lines (bulk)POST/v1/shipping-pricesADM-SHIPPING-PRICE-150201 Created
Get a price line by IDGET/v1/shipping-prices/{id}ADM-SHIPPING-PRICE-500200 OK
List price linesGET/v1/shipping-pricesADM-SHIPPING-PRICE-550200 OK
Update price lines (bulk)PUT/v1/shipping-pricesADM-SHIPPING-PRICE-250200 OK
Delete price lines (bulk)DELETE/v1/shipping-pricesADM-SHIPPING-PRICE-350200 OK

All endpoints require dj-client: OPERATOR and dj-api-key headers.


Typical Workflow

flowchart LR
  %% Styles (Readme)
  classDef create   fill:#e8f1ff,stroke:#2f6feb,stroke-width:2px,color:#0b3d91;
  classDef read     fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#1e1b4b;
  classDef update   fill:#e0f7fa,stroke:#06b6d4,stroke-width:2px,color:#0c4a6e;
  classDef add      fill:#ecfdf5,stroke:#10b981,stroke-width:2px,color:#064e3b;
  classDef remove   fill:#fee2e2,stroke:#ef4444,stroke-width:2px,color:#7f1d1d;
  classDef decision fill:#fff4e5,stroke:#f59e0b,stroke-width:2px,color:#7a3e00;
  classDef place    fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d;
  classDef sys      fill:#f2f4f7,stroke:#475569,stroke-width:2px,color:#111827;
  classDef ok       fill:#ecfdf5,stroke:#10b981,stroke-width:2px,color:#064e3b;
  classDef stop     fill:#fee2e2,stroke:#ef4444,stroke-width:2px,color:#7f1d1d;

  A["➕ Create price lines<br>POST /v1/shipping-prices"]:::create
  B["📘 List / Get prices<br>GET /v1/shipping-prices"]:::read
  C["🔄 Update prices<br>PUT /v1/shipping-prices"]:::update
  D["➖ Delete obsolete lines<br>DELETE /v1/shipping-prices"]:::remove
  E["✅ Matrix ready<br>for checkout"]:::place

  A --> E
  E --> B
  E --> C --> E
  E --> D --> E

  style A rx:8,ry:8
  style B rx:8,ry:8
  style C rx:8,ry:8
  style D rx:8,ry:8
  style E rx:8,ry:8

Creating Price Lines (Bulk)

Create one or more shipping price lines in a single request. Each line is validated independently — valid lines are created and invalid lines are rejected with detailed error messages.

POST /v1/shipping-prices
dj-client: OPERATOR
dj-api-key: {{apiKey}}
Content-Type: application/json

Request Body

An array of price line objects:

FieldTypeRequiredDescription
logisticFamilyIdstringYesLogistic family identifier
shippingZoneIdstringYesShipping zone identifier
shippingTypeIdstringYesShipping type identifier
supplierIdstringNoSupplier identifier (marketplace context)
pricenumberYesShipping cost (>= 0)
francoAmountnumberNoFree-shipping threshold (> 0 if provided, null = no threshold)

Example

[
  {
    "logisticFamilyId": "standard",
    "shippingZoneId": "eu-west",
    "shippingTypeId": "express-24h",
    "supplierId": "supplier-001",
    "price": 12.50,
    "francoAmount": 150.00
  },
  {
    "logisticFamilyId": "standard",
    "shippingZoneId": "eu-west",
    "shippingTypeId": "standard",
    "supplierId": "supplier-001",
    "price": 5.99,
    "francoAmount": null
  }
]

Response — 201 Created

The response contains both successfully created lines and rejected lines with error details.

Errors (per line)

HTTPCodeDescription
409F-E-003Duplicate dimension combination
422F-E-001Required field missing or invalid
400F-E-010Invalid value (e.g. negative price, francoAmount <= 0)
404F-E-002Referenced entity not found (logistic family, zone, type, or supplier)
400F-E-012Invalid ID format

Getting a Price Line

GET /v1/shipping-prices/{id}
dj-client: OPERATOR
dj-api-key: {{apiKey}}

Response — 200 OK

{
  "id": "sp-001",
  "logisticFamilyId": "standard",
  "logisticFamilyName": "Standard",
  "shippingZoneId": "eu-west",
  "shippingZoneName": "EU West",
  "shippingTypeId": "express-24h",
  "shippingTypeName": "Express 24h",
  "supplierId": "supplier-001",
  "supplierName": "Acme Supplies",
  "price": 12.50,
  "francoAmount": 150.00,
  "createdAt": "2026-06-01T10:00:00Z",
  "updatedAt": "2026-06-01T10:00:00Z"
}

Tip: The response includes the name of each dimension (logistic family, zone, type, supplier) alongside its ID, making it easy to display in a UI without additional lookups.


Listing Price Lines

Retrieve a paginated, filterable list of shipping price lines.

GET /v1/shipping-prices
dj-client: OPERATOR
dj-api-key: {{apiKey}}

Query Parameters

ParameterTypeRequiredDescription
pageintegerNoPage number (0-based). Default: 0
sizeintegerNoItems per page. Default: 20
sortstringNoFormat: field:direction (e.g. price:asc)
logisticFamilyIdsstringNoComma-separated logistic family IDs
shippingZoneIdsstringNoComma-separated shipping zone IDs
shippingTypeIdsstringNoComma-separated shipping type IDs
supplierIdsstringNoComma-separated supplier IDs
createdAtFromdatetimeNoFilter by creation date (inclusive, from)
createdAtTodatetimeNoFilter by creation date (inclusive, to)
updatedAtFromdatetimeNoFilter by update date (inclusive, from)
updatedAtTodatetimeNoFilter by update date (inclusive, to)

All filters are combined with AND logic. Multi-valued filters (e.g. logisticFamilyIds=a,b) match any of the listed values (OR within the filter).

Response — 200 OK

{
  "content": [
    {
      "id": "sp-001",
      "logisticFamilyId": "standard",
      "logisticFamilyName": "Standard",
      "shippingZoneId": "eu-west",
      "shippingZoneName": "EU West",
      "shippingTypeId": "express-24h",
      "shippingTypeName": "Express 24h",
      "supplierId": "supplier-001",
      "supplierName": "Acme Supplies",
      "price": 12.50,
      "francoAmount": 150.00,
      "createdAt": "2026-06-01T10:00:00Z",
      "updatedAt": "2026-06-01T10:00:00Z"
    }
  ],
  "totalElements": 1,
  "totalPages": 1,
  "number": 0,
  "size": 20
}

Updating Price Lines (Bulk)

Update one or more existing price lines. This is a full replacement of all modifiable fields. Each line is validated independently.

PUT /v1/shipping-prices
dj-client: OPERATOR
dj-api-key: {{apiKey}}
Content-Type: application/json

Request Body

An array of objects, each including the id of the line to update and the new field values:

FieldTypeRequiredDescription
idstringYesIdentifier of the price line to update
logisticFamilyIdstringYesLogistic family identifier
shippingZoneIdstringYesShipping zone identifier
shippingTypeIdstringYesShipping type identifier
supplierIdstringNoSupplier identifier
pricenumberYesUpdated shipping cost (>= 0)
francoAmountnumberNoUpdated free-shipping threshold (> 0 if provided, null to remove)

Example

[
  {
    "id": "sp-001",
    "logisticFamilyId": "standard",
    "shippingZoneId": "eu-west",
    "shippingTypeId": "express-24h",
    "supplierId": "supplier-001",
    "price": 14.00,
    "francoAmount": 200.00
  }
]

Errors (per line)

Same validation rules and error codes as creation. Additionally, F-E-002 is returned if the price line ID does not exist.


Deleting Price Lines (Bulk)

Delete one or more price lines by ID. Each deletion is processed independently — a failure on one line does not block the others.

DELETE /v1/shipping-prices
dj-client: OPERATOR
dj-api-key: {{apiKey}}
Content-Type: application/json

Request Body

An array of price line IDs to delete:

["sp-001", "sp-002"]

Tip: Deleting a price line is allowed even if past orders reference it. Only future checkout calculations are affected.

Errors

HTTPCodeDescription
404F-E-002Price line not found
401F-E-032Missing or invalid authentication token
403F-E-030Caller is not OPERATOR

Best Practices

  1. Use bulk operations — create, update, and delete operations all support bulk payloads. Batch your changes to minimize API calls.
  2. Set up dimensions first — create your logistic families, shipping zones, and shipping types before populating the price matrix. Missing references cause F-E-002 errors.
  3. Use francoAmount for free shipping — instead of setting price: 0 for large orders, use the francoAmount threshold to automatically waive shipping fees above a certain order value.
  4. Filter with dimension IDs — when reviewing the matrix, use the multi-valued filters (e.g. shippingZoneIds=eu-west,eu-east) to narrow results efficiently.

Common Mistakes

MistakeConsequenceHow to avoid
Creating a duplicate dimension combination409 Conflict (F-E-003)Ensure each (logisticFamilyId + shippingZoneId + shippingTypeId + supplierId) is unique
Setting price to a negative value400 error (F-E-010)Price must be >= 0
Setting francoAmount to 0 or negative400 error (F-E-010)Franco amount must be strictly > 0, or null for no threshold
Referencing a non-existent dimension ID404 error (F-E-002)Create logistic families, zones, types, and suppliers before creating price lines
Deleting a shipping type that is still used in the matrix409 Conflict (F-E-042) on the type deletionRemove price lines referencing the type before deleting it

Error Reference

For the complete list of DJUST error and warning codes, refer to the dedicated page:
Error / Warning codes


Related Documentation



Did this page help you?