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:
| Dimension | Description |
|---|---|
logisticFamilyId | The logistic family (e.g. "fragile-items", "standard"). See Logistic Families. |
shippingZoneId | The geographical zone (e.g. "eu-west", "north-america"). See Shipping Zones and Types. |
shippingTypeId | The delivery mode (e.g. "express-24h", "standard"). See Shipping Zones and Types. |
supplierId | The 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 errorF-E-003.
Price and Franco Amount
| Field | Type | Rules |
|---|---|---|
price | number | The shipping cost. Must be >= 0. |
francoAmount | number or null | The 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 read — GET /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 address —
PUT /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, andtotalShippingTaxAmount. 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
| Situation | Result |
|---|---|
| Shipping address is outside every configured zone, or the combination has no matrix line | No 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 validation | 409 |
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 state | Behavior |
|---|---|
shippingEnabled: false | Legacy behavior is unchanged — shipping is driven by the offer custom fields shippingTaxe / shippingCode. |
shippingEnabled: true | The price matrix is used exclusively; the legacy offer custom fields are ignored. |
Key Endpoints
| Action | Method | Path | OperationId | Response |
|---|---|---|---|---|
| Create price lines (bulk) | POST | /v1/shipping-prices | ADM-SHIPPING-PRICE-150 | 201 Created |
| Get a price line by ID | GET | /v1/shipping-prices/{id} | ADM-SHIPPING-PRICE-500 | 200 OK |
| List price lines | GET | /v1/shipping-prices | ADM-SHIPPING-PRICE-550 | 200 OK |
| Update price lines (bulk) | PUT | /v1/shipping-prices | ADM-SHIPPING-PRICE-250 | 200 OK |
| Delete price lines (bulk) | DELETE | /v1/shipping-prices | ADM-SHIPPING-PRICE-350 | 200 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/jsonRequest Body
An array of price line objects:
| Field | Type | Required | Description |
|---|---|---|---|
logisticFamilyId | string | Yes | Logistic family identifier |
shippingZoneId | string | Yes | Shipping zone identifier |
shippingTypeId | string | Yes | Shipping type identifier |
supplierId | string | No | Supplier identifier (marketplace context) |
price | number | Yes | Shipping cost (>= 0) |
francoAmount | number | No | Free-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)
| HTTP | Code | Description |
|---|---|---|
| 409 | F-E-003 | Duplicate dimension combination |
| 422 | F-E-001 | Required field missing or invalid |
| 400 | F-E-010 | Invalid value (e.g. negative price, francoAmount <= 0) |
| 404 | F-E-002 | Referenced entity not found (logistic family, zone, type, or supplier) |
| 400 | F-E-012 | Invalid 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 thenameof 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
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number (0-based). Default: 0 |
size | integer | No | Items per page. Default: 20 |
sort | string | No | Format: field:direction (e.g. price:asc) |
logisticFamilyIds | string | No | Comma-separated logistic family IDs |
shippingZoneIds | string | No | Comma-separated shipping zone IDs |
shippingTypeIds | string | No | Comma-separated shipping type IDs |
supplierIds | string | No | Comma-separated supplier IDs |
createdAtFrom | datetime | No | Filter by creation date (inclusive, from) |
createdAtTo | datetime | No | Filter by creation date (inclusive, to) |
updatedAtFrom | datetime | No | Filter by update date (inclusive, from) |
updatedAtTo | datetime | No | Filter 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/jsonRequest Body
An array of objects, each including the id of the line to update and the new field values:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Identifier of the price line to update |
logisticFamilyId | string | Yes | Logistic family identifier |
shippingZoneId | string | Yes | Shipping zone identifier |
shippingTypeId | string | Yes | Shipping type identifier |
supplierId | string | No | Supplier identifier |
price | number | Yes | Updated shipping cost (>= 0) |
francoAmount | number | No | Updated 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/jsonRequest 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
| HTTP | Code | Description |
|---|---|---|
| 404 | F-E-002 | Price line not found |
| 401 | F-E-032 | Missing or invalid authentication token |
| 403 | F-E-030 | Caller is not OPERATOR |
Best Practices
- Use bulk operations — create, update, and delete operations all support bulk payloads. Batch your changes to minimize API calls.
- Set up dimensions first — create your logistic families, shipping zones, and shipping types before populating the price matrix. Missing references cause
F-E-002errors. - Use
francoAmountfor free shipping — instead of settingprice: 0for large orders, use thefrancoAmountthreshold to automatically waive shipping fees above a certain order value. - 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
| Mistake | Consequence | How to avoid |
|---|---|---|
| Creating a duplicate dimension combination | 409 Conflict (F-E-003) | Ensure each (logisticFamilyId + shippingZoneId + shippingTypeId + supplierId) is unique |
Setting price to a negative value | 400 error (F-E-010) | Price must be >= 0 |
Setting francoAmount to 0 or negative | 400 error (F-E-010) | Franco amount must be strictly > 0, or null for no threshold |
| Referencing a non-existent dimension ID | 404 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 matrix | 409 Conflict (F-E-042) on the type deletion | Remove 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
Updated about 1 month ago

