Pricing & Costs API
API endpoints for operational costs and pricing tariffs. All endpoints require token_admin authentication.
Costs
| Method | Path | Description | Permission |
|---|---|---|---|
| GET | /services/{id}/costs | Get cost matrix | plans-menu |
| PUT | /services/{id}/costs | Update cost matrix | manage-service-costs |
GET /services/{id}/costs
Returns the full cost matrix with zones, weight ranges, extra weights, and metadata.
Response (200):
{
"locale_id": 1,
"currency": "MXN",
"weight_unit_code": "KG",
"weight_extra_cost": 1,
"fuel_type": "flat",
"fuel_percentage": 5,
"zones": [
{ "label": "Zona - 1", "identifier": 1 },
{ "label": "Zona - 2", "identifier": 2 }
],
"ranges": [
{ "label": "0 - 1", "data": ["120.00", "150.00"] },
{ "label": "1 - 5", "data": ["180.00", "220.00"] }
],
"extra_weights": [
{ "label": "5", "data": ["15.00", "18.00"] }
]
}Data structure:
zones: Array of zone objects. Each zone has alabel(display name) andidentifier(numeric ID).ranges: Array of weight range rows. Each row has alabel(e.g., "0 - 5") and adataarray with one cost value per zone (same order aszones).extra_weights: Same structure asranges, but for per-unit extra weight costs. Typically a single row.
PUT /services/{id}/costs
Replaces the entire cost matrix for a service. Atomic operation — deletes existing data and inserts new records.
Request:
{
"locale_id": 1,
"fuel_type": "flat",
"fuel_percentage": 5,
"weight_unit_code": "KG",
"weight_extra_cost": 1,
"zones": [
{
"identifier": 1,
"ranges": [
{ "start": 0, "end": 1, "cost": 120.00 },
{ "start": 1, "end": 5, "cost": 180.00 }
],
"extra_cost": 15.00
},
{
"identifier": 2,
"ranges": [
{ "start": 0, "end": 1, "cost": 150.00 },
{ "start": 1, "end": 5, "cost": 220.00 }
],
"extra_cost": 18.00
}
]
}Payload fields:
| Field | Type | Required | Description |
|---|---|---|---|
locale_id | number | Yes | Locale ID (determines currency) |
fuel_type | string | Yes | flat or dynamic |
fuel_percentage | number | Yes | Fuel surcharge percentage (min: 0) |
weight_unit_code | string | Yes | KG or LB |
weight_extra_cost | number | Yes | Extra weight unit step (min: 1) |
zones | array | Yes | Zone data (see below) |
Zone object:
| Field | Type | Required | Description |
|---|---|---|---|
identifier | number | Yes | Zone numeric ID |
ranges | array | Yes | Weight range objects |
extra_cost | number | Yes | Per-unit extra weight cost |
Range object:
| Field | Type | Required | Description |
|---|---|---|---|
start | number | Yes | Weight range start |
end | number | Yes | Weight range end (must be > 0) |
cost | number | Yes | Cost value (must be > 0) |
Max payload size: 10 MB (to support large matrices).
Pricing
| Method | Path | Description | Permission |
|---|---|---|---|
| GET | /services/{id}/price | Get pricing matrix (all tariff types) | plans-menu |
| PUT | /services/{id}/price | Update pricing for one tariff type | manage-service-pricing |
GET /services/{id}/price
Returns pricing data for all 4 tariff types in a single response.
Response (200):
{
"locale_id": 1,
"currency": "MXN",
"weight_unit_code": "KG",
"weight_extra_cost": 1,
"operator": 1,
"extended_zone": 50,
"zones": [
{ "label": "Zona - 1", "identifier": 1 },
{ "label": "Zona - 2", "identifier": 2 }
],
"ranges": {
"basic": [
{ "label": "0 - 1", "data": ["180.00", "220.00"] },
{ "label": "1 - 5", "data": ["250.00", "300.00"] }
],
"pro": [
{ "label": "0 - 1", "data": ["160.00", "200.00"] },
{ "label": "1 - 5", "data": ["230.00", "280.00"] }
],
"enterprise": [...],
"corporate": [...]
},
"extra_weights": {
"basic": [{ "label": "5", "data": ["22.00", "26.00"] }],
"pro": [...],
"enterprise": [...],
"corporate": [...]
}
}Key difference from costs: ranges and extra_weights are objects keyed by tariff type (basic, pro, enterprise, corporate), not flat arrays.
PUT /services/{id}/price
Updates pricing for one tariff type at a time. To update all 4, make 4 sequential calls.
Request:
{
"locale_id": 1,
"plan_type": "basic",
"operator": 1,
"weight_unit_code": "KG",
"weight_extra_cost": 1,
"extended_zone": 50,
"zones": [
{
"identifier": 1,
"ranges": [
{ "start": 0, "end": 1, "cost": 180.00 },
{ "start": 1, "end": 5, "cost": 250.00 }
],
"extra_cost": 22.00
},
{
"identifier": 2,
"ranges": [
{ "start": 0, "end": 1, "cost": 220.00 },
{ "start": 1, "end": 5, "cost": 300.00 }
],
"extra_cost": 26.00
}
]
}Payload fields:
| Field | Type | Required | Description |
|---|---|---|---|
locale_id | number | Yes | Locale ID |
plan_type | string | Yes | Tariff type: basic, pro, enterprise, or corporate (lowercase) |
operator | number | Yes | Pricing operator: 1 (Flat) or 2 (Margin) |
weight_unit_code | string | Yes | KG or LB |
weight_extra_cost | number | Yes | Extra weight unit step (min: 1) |
extended_zone | number | Yes | Extended zone surcharge amount |
zones | array | Yes | Zone data (same structure as costs) |
Note: In the zones[].ranges[].cost field, the value represents:
- For Flat operator (1): the absolute selling price
- For Margin operator (2): the markup percentage over graduated cost
Max payload size: 10 MB.
Common Error Responses
All errors follow the Boom format:
{
"statusCode": 400,
"error": "Bad Request",
"message": "Descriptive error message"
}| Status | When |
|---|---|
| 400 | Validation failed (Joi schema — e.g., missing locale, negative cost) |
| 401 | Missing or invalid JWT |
| 403 | Insufficient permissions |
| 404 | Service not found |
