Rate Card and Quotation System -- API Reference
**Platform:** iDrv5-MyFR8 Logistics Management **Version:** 2.0 (Post-Overhaul Schema) **Last Updated:** April 2026
Platform: iDrv5-MyFR8 Logistics Management
Version: 2.0 (Post-Overhaul Schema)
Last Updated: April 2026
Table of Contents
Section titled “Table of Contents”- Overview
- Rate Cards API
- Rate Entries API
- Zones API
- Service Levels API
- Warehouse Rates API
- Surcharges API
- Addons API
- Customers API
- Quotes API
- Data Model Reference
- Rate Types Reference
- Integration Guide
- Appendix
1. Overview
Section titled “1. Overview”System Architecture
Section titled “System Architecture”The rate computation pipeline follows this hierarchy:
Rate Card |-- Rate Entries (one per route: origin zone -> destination zone) | |-- Rate Entry Tiers (range-based pricing: weight, pallet count) | |-- Rate Entry Conditions (dimension/weight/transport config constraints) | |-- Rate Overrides (custom pricing per service level) | |-- Rate Entry Surcharges (per-entry addon overrides/exclusions) | |-- Rate Card Variations (transport config-specific pricing) |-- Addons (surcharges, discounts, taxes -- waterfall engine) |-- Transit Times (from profiles or custom overrides) |-- Warehouse Rates (activity-based pricing for warehouse-type cards) |Zones |-- Zone Suburbs (postcode/suburb mappings for zone resolution) |Service Levels (Express, Standard, Economy) |-- Multiplier-based pricing (base_cost_multiplier applied to base rates) |-- Custom overrides (rate_overrides table per entry+service level) |-- Cubic factor (for chargeable weight computation)Authentication
Section titled “Authentication”Most endpoints require Flask-Login session authentication. Endpoints marked with @login_required will return 401 Unauthorized if no valid session exists.
Development shortcuts for authentication:
GET /super-admin— auto-login as super adminGET /direct-operations— auto-login as operations user
Base URL Convention
Section titled “Base URL Convention”All API endpoints use the /api/ prefix:
- Rate Cards:
/api/rate-cards/ - Rate Entries:
/api/rate-entries/ - Zones:
/api/zones - Service Levels:
/api/service-levels - Warehouse Rates:
/api/warehouse-rates/ - Addons:
/api/addons/ - Customers:
/api/customers/ - Quotes:
/api/quotes/ - Surcharges:
/api/surcharges
Response Format Convention
Section titled “Response Format Convention”All endpoints return JSON. The standard envelope is:
{ "success": true, "data": { ... }}or on error:
{ "success": false, "error": "Description of the error"}Some older endpoints use alternative keys (rate_cards, zones, rate_entries, message, etc.) instead of the generic data wrapper. The specific response shape for each endpoint is documented below.
Date Format
Section titled “Date Format”- API requests/responses:
yyyy-mm-dd(ISO 8601 date) - Frontend display:
dd/mm/yyyy(Australian format) - Always convert on the frontend before sending to API.
2. Rate Cards API
Section titled “2. Rate Cards API”Blueprint prefix: /api/rate-cards
Source: api/rate_cards_api.py
2.1 List Rate Cards
Section titled “2.1 List Rate Cards”GET /api/rate-cards/Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | int | 1 | Page number |
per_page | int | 20 | Items per page |
status | string | all | Filter by status (all, active, suspended) |
Response:
{ "rate_cards": [ { "id": 1, "name": "JATT Per KG Rates", "rate_type": "actual_weight", "rate_calculation_method_id": 3, "effective_date": "2025-01-01", "expiry_date": null, "description": "Standard per-KG rates", "is_active": true, "transit_time_mode": "inherit", "transit_time_profile_id": null, "created_at": "2025-01-01T00:00:00", "updated_at": "2025-06-15T10:30:00", "status": "active", "customers": [ { "id": 5, "name": "Acme Logistics" } ], "cargo_types": [ { "id": 1, "name": "General (Dry)", "code": "DRY" } ] } ], "total": 25, "pages": 2, "current_page": 1, "statistics": { "total_rate_entries": 3500, "active_rate_entries": 3200, "total_rate_cards": 25, "active_rate_cards": 22, "total_zones": 150 }}2.2 Get Single Rate Card
Section titled “2.2 Get Single Rate Card”GET /api/rate-cards/:idResponse:
{ "success": true, "rate_card": { "id": 1, "name": "JATT Per KG Rates", "rate_type": "actual_weight", "rate_calculation_method_id": 3, "effective_date": "2025-01-01", "expiry_date": null, "description": "Standard per-KG rates", "is_active": true, "transit_time_mode": "inherit", "transit_time_profile_id": null, "transit_profile_name": null, "created_at": "2025-01-01T00:00:00", "updated_at": "2025-06-15T10:30:00", "status": "active", "customers": [ { "id": 5, "name": "Acme Logistics" } ] }}Error Codes: 404 — Rate card not found.
2.3 Create Rate Card
Section titled “2.3 Create Rate Card”POST /api/rate-cards/Requires: @login_required
Request Body:
{ "name": "New Rate Card", "rate_type": "pallet", "rate_calculation_method_id": 2, "effective_date": "2025-07-01", "expiry_date": "2026-06-30", "notes": "Optional description", "customers": ["Acme Logistics", "Beta Transport"], "cargo_types": [1, 3]}| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique name for the rate card |
effective_date | string | Yes | Format: yyyy-mm-dd |
rate_calculation_method_id | int | Yes | FK to rate_calculation_methods |
rate_type | string | No | One of: load, pallet, actual_weight, time, warehouse, quantity, flat_rate, weight. Default: load |
expiry_date | string | No | Must be after effective_date |
notes | string | No | Stored as description |
customers | string[] | No | Customer names to associate |
cargo_types | int[] | No | Cargo type IDs. Defaults to DRY if empty |
Response: 201 Created
{ "success": true, "message": "Rate card created successfully", "rate_card": { "id": 26, "name": "New Rate Card", "rate_type": "pallet", "customers": [{ "id": 5, "name": "Acme Logistics" }], "cargo_types": [{ "id": 1, "name": "General (Dry)", "code": "DRY" }] }}Error Codes:
400— Missing required fields409— Duplicate name or database constraint violation
2.4 Update Rate Card
Section titled “2.4 Update Rate Card”PUT /api/rate-cards/:idRequires: @login_required
Request Body (all fields optional):
{ "name": "Updated Name", "rate_calculation_method_id": 3, "effective_date": "2025-07-01", "expiry_date": "2026-12-31", "notes": "Updated description", "transit_time_mode": "profile", "transit_time_profile_id": 2, "customers": ["Acme Logistics"], "cargo_types": [1, 2]}| Field | Type | Description |
|---|---|---|
transit_time_mode | string | One of: inherit, profile, custom, none |
transit_time_profile_id | int/null | FK to transit_time_profiles (validated for existence) |
customers | string[] | Replaces all customer associations (names matched case-insensitively) |
cargo_types | int[] | Replaces all cargo type associations |
Response:
{ "success": true, "message": "Rate card updated successfully", "rate_card": { ... }}Error Codes:
400— Expiry date before effective date, invalid transit_time_mode404— Rate card or transit profile not found409— Duplicate name
2.5 Delete Rate Card
Section titled “2.5 Delete Rate Card”DELETE /api/rate-cards/:idRequires: @login_required
Soft-deletes by setting is_active = false and status = 'suspended'.
2.6 Suspend / Activate Rate Card
Section titled “2.6 Suspend / Activate Rate Card”POST /api/rate-cards/:id/suspendPOST /api/rate-cards/:id/activateRequires: @login_required
Suspend Request Body (optional):
{ "reason": "Rate review in progress"}Both create audit entries.
2.7 Get Calculation Methods
Section titled “2.7 Get Calculation Methods”GET /api/rate-cards/calculation-methodsResponse:
{ "success": true, "methods": [ { "id": 1, "display_name": "Per Load (FTL)", "symbol": "$", "order_no": 1 }, { "id": 2, "display_name": "Per Pallet", "symbol": "$/plt", "order_no": 2 }, { "id": 3, "display_name": "Per KG (Actual Weight)", "symbol": "$/kg", "order_no": 3 }, { "id": 4, "display_name": "Per Cubic Meter", "symbol": "$/m3", "order_no": 4 }, { "id": 5, "display_name": "Per Hour", "symbol": "$/hr", "order_no": 5 }, { "id": 10, "display_name": "Warehouse", "symbol": "$", "order_no": 10 } ]}2.8 Transit Times
Section titled “2.8 Transit Times”Get Transit Times
Section titled “Get Transit Times”GET /api/rate-cards/:id/transit-timesRequires: @login_required
Returns merged list from custom overrides (rate_card_transit_overrides) and profile entries (transit_time_entries). Each entry has a source field: "custom", "inherit", or "profile".
Response:
{ "success": true, "transit_times": [ { "id": 45, "origin_id": "1", "origin_code": "SYD", "origin_name": "Sydney Metro", "destination_id": "2", "destination_code": "MEL", "destination_name": "Melbourne Metro", "transit_hours": 24, "service_type": "Express", "last_updated": "2025-06-15T10:00:00", "source": "custom" } ]}Save Transit Time
Section titled “Save Transit Time”POST /api/rate-cards/:id/transit-timesRequires: @login_required
Upserts into rate_card_transit_overrides.
Request Body:
{ "origin_id": 1, "destination_id": 2, "transit_hours": 24, "service_type": "Express"}Delete Transit Time
Section titled “Delete Transit Time”DELETE /api/rate-cards/:id/transit-times/:origin_id/:destination_id?service_type=ExpressRequires: @login_required
2.9 Rate Card Addons
Section titled “2.9 Rate Card Addons”List Addons for Rate Card
Section titled “List Addons for Rate Card”GET /api/rate-cards/:id/addonsReturns combined list: addons assigned via form_target=rate_card, global addons (apply_to_all_rate_cards=true), and directly assigned via addon_rate_cards.
Each addon includes override_value, is_global, and is_directly_assigned flags.
Assign Addons
Section titled “Assign Addons”POST /api/rate-cards/:id/addonsRequest Body:
{ "addon_ids": [1, 2, 3], "override_values": { "1": "15.50", "3": "2.5" }}Update Addon Override
Section titled “Update Addon Override”PUT /api/rate-cards/:id/addons/:addon_idRequest Body:
{ "override_value": "18.00"}Remove Addon Assignment
Section titled “Remove Addon Assignment”DELETE /api/rate-cards/:id/addons/:addon_idCannot remove global addons (returns 400).
2.10 CSV Import/Export
Section titled “2.10 CSV Import/Export”Export Zones CSV
Section titled “Export Zones CSV”GET /api/rate-cards/:id/zones/exportReturns CSV file download.
Import Zones CSV
Section titled “Import Zones CSV”POST /api/rate-cards/:id/zones/importMultipart form upload with field zones_file (CSV file).
Export Transit Times CSV
Section titled “Export Transit Times CSV”GET /api/rate-cards/:id/transit-times/exportImport Transit Times CSV
Section titled “Import Transit Times CSV”POST /api/rate-cards/:id/transit-times/importMultipart form upload with field transit_times_file (CSV or XLSX).
3. Rate Entries API
Section titled “3. Rate Entries API”Blueprint prefix: /api/rate-entries
Source: api/rate_entries_api.py
3.1 Get Entries by Rate Card
Section titled “3.1 Get Entries by Rate Card”GET /api/rate-entries/rate-card/:rate_card_idReturns all rate entries for a specific rate card, one row per route (origin-destination pair).
Response:
{ "rate_entries": [ { "id": 101, "rate_card_id": 1, "origin_zone_id": 5, "destination_zone_id": 8, "base_rate": 45.00, "flat_rate": null, "minimum_rate": 25.00, "maximum_rate": 999999.00, "charging_type": "flat_rate", "display_name": null, "transit_time_hours": null, "vehicle_type_id": null, "distance_km": null, "rate_per_km": null, "rate_per_tonne": null, "minimum_hours": null, "load_rate": null, "drop_rate": null, "rate_calculation_method_name": "actual_weight", "origin_zone_code": "SYD", "origin_zone_name": "Sydney Metro", "destination_zone_code": "MEL", "destination_zone_name": "Melbourne Metro", "vehicle_type_name": null, "vehicle_type_code": null, "conditions_count": 1, "tiers_count": 3, "surcharges_count": 0, "overrides_count": 2, "variations": [ { "id": 10, "variation_name": "Pantech", "flat_rate": 550.00, "charging_type": "per_unit" } ], "base_rate_value": 45.00, "minimum_rate_value": 25.00, "maximum_rate_value": 999999.00 } ], "total_count": 1071, "variations_count": 15}3.2 Get Single Rate Entry
Section titled “3.2 Get Single Rate Entry”GET /api/rate-entries/:idReturns full detail including service levels (with multiplier-calculated effective rates and overrides), conditions, tiers, surcharges (merged inherited + per-entry), and variations.
Response (abbreviated):
{ "success": true, "rate_entry": { "id": 101, "rate_card_id": 1, "origin_zone_id": 5, "destination_zone_id": 8, "base_rate": 0.1234, "flat_rate": null, "minimum_rate": 25.00, "maximum_rate": 999999.00, "charging_type": "flat_rate", "display_name": null, "transit_time_hours": null, "origin_zone_code": "SYD", "origin_zone_name": "Sydney Metro", "destination_zone_code": "MEL", "destination_zone_name": "Melbourne Metro", "origin_zone_type": "METRO", "destination_zone_type": "REGIONAL", "service_levels": [ { "service_level_id": 1, "service_level_name": "Express", "multiplier": 1.50, "effective_base_rate": 0.1851, "effective_min_rate": 37.50, "is_overridden": false, "custom_base_charge": null, "custom_min_charge": null }, { "service_level_id": 2, "service_level_name": "Standard", "multiplier": 1.00, "effective_base_rate": 0.1234, "effective_min_rate": 25.00, "is_overridden": false } ], "conditions": [ { "id": 50, "applies_to": { "packaging_types": ["Pallet", "Skid"] }, "min_weight_kg": 0, "max_weight_kg": 5000, "min_length_cm": null, "max_length_cm": null } ], "tiers": [ { "id": 200, "tier_name": "0-500kg", "tier_range_start": 0, "tier_range_end": 500, "base_charge": 0.1500, "minimum_charge": 35.00, "tier_unit": "chargeable_weight" }, { "id": 201, "tier_name": "501-750kg", "tier_range_start": 501, "tier_range_end": 750, "base_charge": 0.1200, "minimum_charge": 30.00, "tier_unit": "chargeable_weight" } ], "surcharges": [ { "id": null, "name": "Fuel Levy", "action": "add", "rate_type": "percentage", "charge_by": "Surcharge", "amount": 22.5, "addon_id": 3, "is_linked": true, "is_inherited": true, "is_excluded": false, "is_overridden": false, "inherited_amount": 22.5, "addon_label": "Fuel Levy", "trigger_mode": "mandatory", "is_global": true, "calculation_order": 10 } ], "variations": [ { "id": 10, "variation_name": "Pantech 12-pallet", "variation_type": "custom", "variation_data": { "conditions": [{ "condition_type": "transport_configuration", "transport_config_ids": [5, 6] }], "base_charge": 550.00, "flat_rate": 0, "tiers": [] }, "display_order": 1, "is_active": true } ] }}3.3 Create Rate Entry
Section titled “3.3 Create Rate Entry”POST /api/rate-entries/createCreates a single rate entry for one route.
Request Body:
{ "rate_card_id": 1, "origin_zone_id": 5, "destination_zone_id": 8, "rate_calculation_method_id": 3, "base_rate": 0.1234, "minimum_rate": 25.00, "maximum_rate": 999999}| Field | Type | Required | Description |
|---|---|---|---|
rate_card_id | int | Yes | FK to rate_cards |
origin_zone_id | int | Yes | FK to zones |
destination_zone_id | int | Yes | FK to zones |
rate_calculation_method_id | int | Yes | FK to rate_calculation_methods |
base_rate | decimal | Yes | Base rate per unit |
minimum_rate | decimal | No | Minimum charge. Default: 0 |
maximum_rate | decimal | No | Maximum charge. Default: 999999 |
Response: 201 Created
{ "success": true, "message": "Rate entry created successfully", "rate_entry_id": 3501}3.4 Save Comprehensive Rate Entry
Section titled “3.4 Save Comprehensive Rate Entry”POST /api/rate-entries/save-comprehensiveCreates or updates rate entries for multiple destination zones with all associated data (conditions, tiers, surcharges, variations, service level overrides) in a single transaction.
Request Body:
{ "rate_card_id": 1, "rate_calculation_method_id": 3, "origin_zone_id": 5, "destination_zone_ids": [8, 9, 10], "service_level_pricing": { "2": { "service_level_id": 2, "base_charge": 0.1234, "minimum_charge": 25.00 }, "1": { "service_level_id": 1, "use_multiplier": false, "base_charge": 0.1800, "minimum_charge": 40.00 }, "3": { "service_level_id": 3, "use_multiplier": true } }, "flat_rate": null, "charging_type": "flat_rate", "display_name": "SYD-MEL Standard", "transit_time_hours": 24, "conditions": [ { "condition_type": "transport_configuration", "transport_config_ids": [1, 2, 3] }, { "condition_type": "packaging_type", "packaging_types": ["Pallet", "Skid"] }, { "min_weight_kg": 0, "max_weight_kg": 5000 } ], "tiers": [ { "tier_name": "0-500kg", "tier_range_start": 0, "tier_range_end": 500, "base_charge": 0.1500, "minimum_charge": 35.00, "tier_unit": "chargeable_weight" } ], "surcharges": [ { "addon_id": 3, "name": "Fuel Levy", "action": "add", "rate_type": "percentage", "amount": 25.0, "is_inherited": true, "is_overridden": true }, { "addon_id": 7, "is_inherited": true, "is_excluded": true } ], "variations": [ { "variation_name": "Pantech 12-pallet", "base_charge": 550.00, "flat_rate": 0, "minimum_charge": 0, "conditions": [ { "condition_type": "transport_configuration", "transport_config_ids": [5] } ], "tiers": [], "addons": [] } ]}Key Fields:
| Field | Description |
|---|---|
destination_zone_ids | Array of zone IDs — creates one entry per destination |
service_level_pricing | Keyed by service level ID. ID 2 = Standard (stored in rate_entries.base_rate). Others go to rate_overrides unless use_multiplier: true |
conditions | Array of condition objects (transport config, packaging type, dimensions, time windows) |
tiers | Array of tier objects for range-based pricing |
surcharges | Only overrides and exclusions are stored. Unmodified inherited addons are skipped |
variations | Transport-config-specific pricing alternatives stored in rate_card_variations |
Surcharge Storage Rules:
is_inherited: true, is_excluded: false, is_overridden: false— NOT stored (inherits dynamically)is_inherited: true, is_overridden: true— Stored with custom amountis_inherited: true, is_excluded: true— Stored as exclusion- Custom surcharges (no
addon_id) — Always stored
Response: 201 Created
{ "success": true, "message": "Successfully created 3 rate entries with all associated data", "created_entries_count": 3, "created_entries": [ { "rate_entry_id": 3501, "destination_zone_id": 8, "base_charge": 0.1234, "minimum_charge": 25.00, "conditions_count": 2, "tiers_count": 3, "surcharges_count": 2 } ], "overrides_created": 3, "overrides_deleted": 0, "variations_saved": 3}3.5 Update Rate Entry
Section titled “3.5 Update Rate Entry”PUT /api/rate-entries/:idRequest Body:
{ "rate_card_id": 1, "origin_zone_id": 5, "destination_zone_id": 8, "rate_calculation_method_id": 3, "base_rate": 0.1300, "flat_rate": null, "charging_type": "flat_rate", "display_name": "SYD-MEL", "transit_time_hours": 24, "minimum_rate": 28.00, "maximum_rate": 999999, "service_level_pricing": { "1": { "use_multiplier": false, "base_charge": 0.1900, "minimum_charge": 42.00 }, "3": { "use_multiplier": true } }, "conditions": [], "tiers": [], "variations": []}Conditions, tiers, and variations are replaced entirely on each save (delete + re-insert).
Response:
{ "success": true, "message": "Rate entry updated successfully", "overrides_created": 1, "overrides_deleted": 1, "conditions_saved": 0, "tiers_saved": 0, "variations_saved": 0}3.6 Delete Rate Entry
Section titled “3.6 Delete Rate Entry”DELETE /api/rate-entries/:idHard delete. Cascades to tiers, conditions, surcharges, and overrides.
Response:
{ "success": true, "message": "Rate entry deleted successfully"}3.7 Compute Rate (Primary Endpoint)
Section titled “3.7 Compute Rate (Primary Endpoint)”POST /api/rate-entries/compute-rateThis is the key endpoint for rate computation. It performs full server-side evaluation of tiers, conditions, service level multipliers, variations, chargeable weight, and addons.
Request Body:
{ "customer_id": 42, "pickup_zone_id": 5, "delivery_zone_id": 8, "service_level_id": 2, "transport_config_id": 3, "job_type": "standard", "charging_type": "actual_weight", "chargeable_weight_kg": 850, "items": [ { "quantity": 2, "packaging_type": "Pallet", "length_cm": 120, "width_cm": 120, "height_cm": 150, "weight_kg": 350 }, { "quantity": 1, "packaging_type": "Carton", "length_cm": 60, "width_cm": 40, "height_cm": 40, "weight_kg": 25 } ]}| Field | Type | Required | Description |
|---|---|---|---|
customer_id | int | Yes | Customer for rate card lookup |
pickup_zone_id | int | Conditional | Origin zone. Auto-resolved from pickup_postcode or pickup_suburb if not provided |
delivery_zone_id | int | Conditional | Destination zone. Auto-resolved similarly |
pickup_postcode | string | No | Fallback for zone resolution |
pickup_suburb | string | No | Fallback for zone resolution |
delivery_postcode | string | No | Fallback for zone resolution |
delivery_suburb | string | No | Fallback for zone resolution |
service_level_id | int | No | Service level for multiplier. Defaults to Standard |
transport_config_id | int | No | Transport configuration for variation matching |
job_type | string | No | "standard" or "hourly_hire". Hourly hire uses a separate computation path |
charging_type | string | No | Hint to prefer rate entries matching this method |
chargeable_weight_kg | float | No | Overridden by server-side computation from items |
items | array | No | Line items with dimensions and weight |
hours | float | No | Hours for hourly hire jobs |
Computation Pipeline:
- Rate entry lookup — Customer-specific rate cards first (via
rate_card_customers), then global cards - Variation matching — If
transport_config_idprovided, match againstrate_card_variationsconditions - Condition evaluation — Check packaging types, transport configs, dimensions, time windows
- Chargeable weight computation —
max(volumetric_weight, dead_weight)per item. Volumetric =(L x W x H / 1,000,000) x cubic_factor - Tier matching — Find matching tier by chargeable weight or pallet count
- Service level pricing — Apply multiplier or use custom override from
rate_overrides - Item charge calculation — Per-item charges based on tier rate and calculation method
- Total calculation — base_charge + flat_rate, with minimum charge enforcement
- Addon waterfall — Automatically calculates applicable addons (fuel levy, GST, etc.)
Response:
{ "success": true, "found": true, "computation": { "rate_card_name": "JATT Per KG Rates", "rate_card_id": 1, "rate_entry_id": 101, "customer_specific": true, "service_level": { "name": "Standard", "multiplier": 1.0, "is_override": false }, "chargeable_weight": { "cubic_factor": 250.0, "total_dead_weight": 725.00, "total_volumetric_weight": 1080.00, "total_chargeable_weight": 1080.00, "method_used": "volumetric" }, "conditions_evaluation": [ { "type": "packaging_type", "passed": true, "detail": "Packaging type matches: booking has pallet, carton, allowed: pallet, carton, skid" } ], "all_conditions_pass": true, "tier_matched": { "name": "751+kg", "range": "751-99999 chargeable_weight", "rate": 0.0950, "unit": "chargeable_weight", "minimum_charge": 28.00 }, "raw_base_rate": 0.1234, "base_rate": 0.0950, "flat_rate": 0, "initial_cost": 0.12, "base_rate_source": "751+kg (751-99999 chargeable_weight @ $0.0950)", "calculation_method": "actual_weight", "items_breakdown": [ { "description": "2x Pallet (120x120x150cm, 350kg)", "normal_charge": 205.20, "volumetric_weight": 540.00, "dead_weight": 350.00, "chargeable_weight": 540.00, "weight_method": "volumetric", "volume_m3": 2.16 } ], "calculation_steps": [ "Rate Card: JATT Per KG Rates (ID: 1)", "Rate Entry ID: 101", "Customer-specific: Yes", "Tier matched: 751+kg (751-99999 chargeable_weight @ $0.0950)", "Service level: Standard (1.00x -- no multiplier)", "--- TOTAL CALCULATION ---", "TOTAL: $102.60" ], "variation_matched": null, "transport_config_id": 3, "totals": { "initial_cost": 0.12, "base_charge": 102.60, "flat_rate_charge": 0, "subtotal": 102.60, "minimum_rate": 28.00, "minimum_applied": false, "final_total": 102.60 }, "addons": { "addons": [ { "addon_id": 3, "name": "Fuel Levy", "label": "Fuel Levy", "addon_type": "surcharge", "value_type": "percentage", "amount": 23.09, "trigger_mode": "mandatory", "applies_on": "subtotal" } ], "base_rate": 0.12, "flat_rate": 0, "subtotal": 0.12, "addon_total": 23.09, "grand_total": 23.21 } }}No Match Response:
{ "success": true, "found": false, "message": "No matching rate entry found for the specified zones"}3.8 Compute Rate — Hourly Hire Path
Section titled “3.8 Compute Rate — Hourly Hire Path”When job_type is "hourly_hire", the compute-rate endpoint uses a separate computation path:
Request Body:
{ "customer_id": 42, "job_type": "hourly_hire", "transport_config_id": 7, "hours": 4, "service_level_id": null}Computation:
- Resolves
vehicle_type_idfromtransport_config_idviatransport_configurationstable - Finds rate entries in
time-type rate cards matching the customer and vehicle type - Applies
minimum_hoursenforcement:effective_hours = max(hours, minimum_hours) - Calculates:
base_price = hourly_rate x effective_hours - Applies minimum charge if
base_price < minimum_rate - Runs addon waterfall
Response has the same structure as standard compute-rate, with additional fields:
{ "computation": { "job_type": "hourly_hire", "hourly_rate": 85.00, "hours": 4, "minimum_hours": 4, "effective_hours": 4, "base_rate_source": "Rigid Truck @ $85.00/hr", "calculation_method": "hourly" }}3.9 Check Zone
Section titled “3.9 Check Zone”POST /api/rate-entries/check-zoneResolves a zone from a suburb/postcode.
Request Body:
{ "suburb": "Parramatta", "postcode": "2150", "state": "NSW"}Response:
{ "success": true, "found": true, "zone_id": 5, "zone_name": "Sydney Metro", "message": "Pickup zone found: Sydney Metro"}3.10 Check Entry
Section titled “3.10 Check Entry”POST /api/rate-entries/check-entryChecks if a rate entry exists for a customer and zone pair. Searches customer-specific rate cards first, then global.
Request Body:
{ "customer_id": 42, "pickup_zone_id": 5, "delivery_zone_id": 8}Response (customer-specific match):
{ "success": true, "customer_specific_found": true, "found": true, "rate_card_count": 2, "customer_name": "Acme Logistics", "rate_entry_id": 1, "rate_card_name": "JATT Per KG Rates", "message": "Found matching rate entry in JATT Per KG Rates for Acme Logistics"}3.11 Get Company Rate Cards
Section titled “3.11 Get Company Rate Cards”GET /api/rate-entries/get-company-rate-cards/:company_idReturns all active rate cards for a company with their rate entries.
3.12 Get Rate Entry Tiers
Section titled “3.12 Get Rate Entry Tiers”GET /api/rate-entries/:id/tiersResponse:
{ "success": true, "data": [ { "id": 200, "rate_entry_id": 101, "tier_name": "0-500kg", "tier_range_start": 0, "tier_range_end": 500, "base_charge": 0.1500, "minimum_charge": 35.00, "tier_unit": "chargeable_weight", "tier_type": "weight" } ]}3.13 Get All Tiers for Rate Card
Section titled “3.13 Get All Tiers for Rate Card”GET /api/rate-entries/rate-card/:rate_card_id/tiersReturns tiers for all entries in a rate card, keyed by rate_entry_id.
3.14 Rate Calculation Methods
Section titled “3.14 Rate Calculation Methods”GET /api/rate-entries/rate-calculation-methodsReturns the same data as GET /api/rate-cards/calculation-methods but from the rate entries blueprint.
4. Zones API
Section titled “4. Zones API”Blueprint prefix: /api/zones
Source: api/zones_api.py
4.1 List Zones
Section titled “4.1 List Zones”GET /api/zonesQuery Parameters:
| Parameter | Type | Description |
|---|---|---|
include_suburbs | string | Set to "true" to include aggregated suburb_names and postcodes per zone |
Response:
{ "success": true, "zones": [ { "id": 1, "name": "Sydney Metro", "short_name": "SYD", "is_metro": 1, "is_no_service_flag": 0, "is_limited_service": 0, "zone_type": "Metro", "service_status": "Full Service", "is_active": true, "created_at": "2025-01-01T00:00:00", "country": "Australia", "has_boundary": true, "suburb_names": "Bankstown,Bondi,Burwood,...", "postcodes": "2000,2010,2020,..." } ]}4.2 Create Zone
Section titled “4.2 Create Zone”POST /api/zonesRequest Body:
{ "name": "Newcastle Regional", "short_name": "NCL", "is_metro": 0, "is_no_service_flag": 0, "is_limited_service": 0, "country_id": 13}| Field | Type | Required |
|---|---|---|
name | string | Yes |
short_name | string | Yes |
is_metro | int (0/1) | No (default 0) |
is_no_service_flag | int (0/1) | No (default 0) |
is_limited_service | int (0/1) | No (default 0) |
country_id | int | No |
Response:
{ "success": true, "message": "Zone created successfully", "zone_id": 151}4.3 Update Zone
Section titled “4.3 Update Zone”PUT /api/zones/:zone_idSame fields as create. Also accepts zone_listing_id and is_active.
4.4 Delete Zone
Section titled “4.4 Delete Zone”DELETE /api/zones/:zone_idHard delete.
4.5 Get Zone Suburbs
Section titled “4.5 Get Zone Suburbs”GET /api/zones/:zone_id/suburbsResponse:
{ "success": true, "suburbs": [ { "id": 1, "name": "Bankstown", "postcode": "2200", "state": "NSW" }, { "id": 2, "name": "Bondi", "postcode": "2026", "state": "NSW" } ], "zone_id": 1, "count": 45}4.6 Get All Suburbs
Section titled “4.6 Get All Suburbs”GET /api/suburbs/allReturns all suburbs across all zones.
4.7 Update Zone Suburbs
Section titled “4.7 Update Zone Suburbs”PUT /api/zones/:zone_id/suburbsRequest Body:
{ "suburb_ids": [1, 2, 3, 4]}Replaces all suburb assignments for the zone.
4.8 Update Zone Composition
Section titled “4.8 Update Zone Composition”PUT /api/zones/:zone_id/compositionRequest Body:
{ "boundary_geometry": { "type": "Polygon", "coordinates": [...] }, "selected_suburbs": ["Bankstown", "Bondi"], "coverage_type": "polygon"}4.9 Get Zone Geometry
Section titled “4.9 Get Zone Geometry”GET /api/zones/:zone_id/geometryReturns the GeoJSON boundary geometry for a zone.
4.10 Get Zone Listings
Section titled “4.10 Get Zone Listings”GET /api/zone-listingsReturns all zone listing groups.
5. Service Levels API
Section titled “5. Service Levels API”Blueprint prefix: /api/service-levels
Source: api/service_levels_api.py
5.1 List Service Levels
Section titled “5.1 List Service Levels”GET /api/service-levelsResponse:
{ "service_levels": [ { "id": 1, "name": "Express", "description": "Next-day delivery", "priority": 1, "base_cost_multiplier": 1.50, "transit_time_multiplier": 0.50, "cubic_factor": 250.0, "is_active": true }, { "id": 2, "name": "Standard", "description": "Standard delivery", "priority": 2, "base_cost_multiplier": 1.00, "transit_time_multiplier": 1.00, "cubic_factor": 250.0, "is_active": true }, { "id": 3, "name": "Economy", "description": "Budget delivery", "priority": 3, "base_cost_multiplier": 0.85, "transit_time_multiplier": 1.50, "cubic_factor": 250.0, "is_active": true } ]}5.2 Get Service Level
Section titled “5.2 Get Service Level”GET /api/service-levels/:id5.3 Create Service Level
Section titled “5.3 Create Service Level”POST /api/service-levelsRequest Body:
{ "name": "Priority", "description": "Same-day priority service", "priority": 0, "base_cost_multiplier": 2.00, "transit_time_multiplier": 0.25, "cubic_factor": 300.0, "is_active": true}5.4 Update Service Level
Section titled “5.4 Update Service Level”PUT /api/service-levels/:idSame fields as create, all optional.
5.5 Delete Service Level
Section titled “5.5 Delete Service Level”DELETE /api/service-levels/:id6. Warehouse Rates API
Section titled “6. Warehouse Rates API”Blueprint prefix: /api/warehouse-rates
Source: api/warehouse_rates_api.py
Warehouse rates provide activity-based pricing for warehouse type rate cards. Rates are grouped by category (e.g., INBOUND, STORAGE, OUTBOUND, VALUE_ADDED).
6.1 Get Warehouse Rates by Rate Card
Section titled “6.1 Get Warehouse Rates by Rate Card”GET /api/warehouse-rates/rate-card/:rate_card_idResponse:
{ "success": true, "data": { "rate_card_id": 5, "categories": { "INBOUND": [ { "id": 1, "activity_name": "Container Unload (20ft)", "activity_code": "IN-CU20", "rate": 385.00, "rate_unit": "per_container", "minimum_charge": null, "notes": "Standard unload, palletise and put away", "sort_order": 1, "is_active": true } ], "STORAGE": [ { "id": 15, "activity_name": "Pallet Storage", "activity_code": "ST-PLT", "rate": 8.50, "rate_unit": "per_pallet_per_week", "minimum_charge": 50.00, "notes": "Weekly storage rate per pallet position", "sort_order": 1, "is_active": true } ] }, "total_items": 61 }}6.2 Create Warehouse Rate
Section titled “6.2 Create Warehouse Rate”POST /api/warehouse-rates/Request Body:
{ "rate_card_id": 5, "category": "OUTBOUND", "activity_name": "Pick & Pack (per order)", "activity_code": "OUT-PP", "rate": 12.50, "rate_unit": "per_order", "minimum_charge": 25.00, "notes": "Standard pick and pack per order", "sort_order": 1}| Field | Type | Required |
|---|---|---|
rate_card_id | int | Yes |
category | string | Yes |
activity_name | string | Yes |
rate | decimal | Yes |
rate_unit | string | Yes |
activity_code | string | No |
minimum_charge | decimal | No |
notes | string | No |
sort_order | int | No (default 0) |
Response: 201 Created
{ "success": true, "data": { "id": 62 }}6.3 Update Warehouse Rate
Section titled “6.3 Update Warehouse Rate”PUT /api/warehouse-rates/:rate_idAll fields optional, uses COALESCE to preserve existing values.
6.4 Delete Warehouse Rate
Section titled “6.4 Delete Warehouse Rate”DELETE /api/warehouse-rates/:rate_idHard delete.
7. Surcharges API
Section titled “7. Surcharges API”Blueprint prefix: /api/surcharges
Source: modules/freight_management/surcharges_routes.py
This is the legacy surcharges system. For the newer, more flexible surcharge/addon system, see Section 8 (Addons API). Both systems coexist; the Addons system is preferred for new integrations.
7.1 List Surcharges
Section titled “7.1 List Surcharges”GET /api/surchargesRequires: @login_required
Response:
{ "success": true, "surcharges": [ { "id": 1, "name": "Fuel Levy", "description": "Monthly fuel surcharge percentage", "surcharge_type": "FUEL", "apply_per": "CONSIGNMENT", "value_type": "PERCENTAGE", "value": 22.5 } ]}7.2 Get Surcharge
Section titled “7.2 Get Surcharge”GET /api/surcharges/:id7.3 Create Surcharge
Section titled “7.3 Create Surcharge”POST /api/surchargesRequires: @login_required
Request Body:
{ "name": "Dangerous Goods Surcharge", "description": "Applied to DG classified freight", "surcharge_type": "DG", "apply_per": "CONSIGNMENT", "value_type": "FIXED", "value": 75.00}| Field | Type | Required | Values |
|---|---|---|---|
name | string | Yes | |
surcharge_type | string | Yes | See Appendix for enum values |
apply_per | string | Yes | See Appendix for enum values |
value_type | string | Yes | FIXED, PERCENTAGE |
value | decimal | Yes | Amount or percentage |
description | string | No |
7.4 Update Surcharge
Section titled “7.4 Update Surcharge”PUT /api/surcharges/:idAll fields optional.
7.5 Delete Surcharge
Section titled “7.5 Delete Surcharge”DELETE /api/surcharges/:id7.6 Initialize Default Surcharges
Section titled “7.6 Initialize Default Surcharges”POST /api/surcharges/initializeSeeds the database with default surcharge records.
8. Addons API
Section titled “8. Addons API”Blueprint prefix: /api/addons
Source: api/addons_api.py + controllers/addons_controller.py
The Addons system is the unified engine for surcharges, discounts, taxes, and document additions. It supports conditional logic, customer/rate-card scoping, waterfall calculation, per-unit pricing, and external resolver integration.
8.1 List Addons
Section titled “8.1 List Addons”GET /api/addons/Requires: @login_required
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
addon_type | string | surcharge, discount, tax, document_addition |
form_target | string | booking, rate_card, pickup, delivery, etc. |
is_active | string | true or false |
search | string | Searches name, label, description, alias |
page | int | Default: 1 |
per_page | int | Default: 50 |
Response:
{ "success": true, "data": [ { "id": 1, "name": "Fuel Levy", "label": "Fuel Levy", "alias": "fuel_levy", "help_text": "Applied monthly based on fuel index", "description": "Percentage-based fuel surcharge", "addon_type": "surcharge", "addon_type_id": 1, "form_target": "Booking", "form_target_id": 1, "form_target_ids": [1, 2], "value_type": "percentage", "value_type_id": 2, "default_value": "22.5", "rate_calculation_method_id": null, "is_active": true, "apply_to_all_rate_cards": true, "apply_to_all_customers": true, "display_order": 1, "trigger_mode": "mandatory", "client_visible": true, "ui_binding": null, "calculation_order": 10, "category": "system", "applies_on": "subtotal", "application_scope": "per_booking", "unit_type": null, "minimum_charge": null, "maximum_charge": null, "is_taxable": true, "tax_category": "standard", "tax_code": null, "tax_inclusive": false, "region": "AU", "resolver_url": null, "fallback_value": 0 } ], "total": 15, "page": 1, "per_page": 50, "total_pages": 1}8.2 Get Single Addon
Section titled “8.2 Get Single Addon”GET /api/addons/:idReturns full addon details with conditions, customer assignments, and rate card assignments.
8.3 Create Addon
Section titled “8.3 Create Addon”POST /api/addons/Requires: @login_required
Request Body:
{ "name": "Tailgate Pickup", "label": "Tailgate Pickup", "alias": "tailgate_pickup", "help_text": "Hydraulic tailgate required at pickup", "description": "Extra charge for tailgate service", "addon_type_id": 1, "form_target_ids": [1], "value_type_id": 1, "default_value": "45.00", "trigger_mode": "automatic", "ui_binding": "pickup_tailgate", "client_visible": true, "calculation_order": 50, "category": "handling", "applies_on": "subtotal", "application_scope": "per_booking", "is_active": true, "apply_to_all_rate_cards": false, "apply_to_all_customers": true, "tax_category": "standard", "region": "AU"}| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique name |
addon_type_id | int | Yes | FK to addon_types |
form_target_ids | int[] | Yes | FK to form_targets (supports multiple) |
value_type_id | int | Yes | FK to value_types |
default_value | string | No | Default amount/percentage |
trigger_mode | string | No | mandatory, automatic, manual |
ui_binding | string | No | Matches UI checkbox name for automatic triggers |
calculation_order | int | No | Lower = calculated first |
applies_on | string | No | base_rate, subtotal, running_total |
application_scope | string | No | per_booking, per_unit |
unit_type | string | No | For per_unit: pallet, kg, km, cubic_meter, item |
minimum_charge | decimal | No | Floor for calculated amount |
maximum_charge | decimal | No | Ceiling for calculated amount |
tax_category | string | No | standard, gst_free, zero_rated, input_taxed |
region | string | No | AU, US, DXB, PH, GLOBAL |
resolver_url | string | No | External pricing API URL |
resolver_secret | string | No | Secret header for external API |
fallback_value | decimal | No | Fallback when resolver fails |
8.4 Update Addon
Section titled “8.4 Update Addon”PUT /api/addons/:idSame fields as create, all optional.
8.5 Delete Addon
Section titled “8.5 Delete Addon”DELETE /api/addons/:idHard delete with cascade.
8.6 Addon Conditions
Section titled “8.6 Addon Conditions”Conditions provide conditional logic for when addons should apply.
List Conditions
Section titled “List Conditions”GET /api/addons/:id/conditionsCreate Condition
Section titled “Create Condition”POST /api/addons/:id/conditionsRequest Body:
{ "condition_type": "weight_range", "condition_operator": "between", "condition_value": [0, 5000], "logic_operator": "AND"}| Field | Type | Values |
|---|---|---|
condition_type | string | job_type, service_level, weight_range, distance_range, zone_type, etc. |
condition_operator | string | equals, not_equals, in, greater_than, less_than, between |
condition_value | any | Single value or array |
logic_operator | string | AND or OR (default: AND) |
Update Condition
Section titled “Update Condition”PUT /api/addons/:id/conditions/:condition_idDelete Condition
Section titled “Delete Condition”DELETE /api/addons/:id/conditions/:condition_id8.7 Customer Assignments
Section titled “8.7 Customer Assignments”List Customers
Section titled “List Customers”GET /api/addons/:id/customersAssign Customers
Section titled “Assign Customers”POST /api/addons/:id/customersRequest Body:
{ "customer_ids": [1, 2, 3]}Unassign Customer
Section titled “Unassign Customer”DELETE /api/addons/:id/customers/:customer_id8.8 Rate Card Assignments
Section titled “8.8 Rate Card Assignments”List Rate Cards
Section titled “List Rate Cards”GET /api/addons/:id/rate-cardsAssign Rate Cards
Section titled “Assign Rate Cards”POST /api/addons/:id/rate-cardsRequest Body:
{ "rate_card_ids": [1, 2, 3]}Unassign Rate Card
Section titled “Unassign Rate Card”DELETE /api/addons/:id/rate-cards/:rate_card_id8.9 Get Addons for Context
Section titled “8.9 Get Addons for Context”GET /api/addons/for-context?form_target=booking&customer_id=42&rate_card_id=1POST /api/addons/for-contextRequires: @login_required
Returns only the addons applicable to the given context after evaluating:
- Form target matching
- Region filtering (tenant-locked)
- Customer assignment check (unless
apply_to_all_customers) - Rate card assignment check (unless
apply_to_all_rate_cards) - Conditional logic evaluation
Each addon in the response may include customer_override_value and rate_card_override_value if overrides exist.
8.10 Calculate Single Addon
Section titled “8.10 Calculate Single Addon”POST /api/addons/calculateRequires: @login_required
Request Body:
{ "addon_id": 3, "context": { "base_amount": 500.00, "weight": 1200, "distance": 450, "time": 6, "load_count": 4 }}8.11 Calculate Addons Batch (Waterfall Engine)
Section titled “8.11 Calculate Addons Batch (Waterfall Engine)”POST /api/addons/calculate-batchRequires: @login_required
This is the core calculation endpoint for the addon waterfall engine. It evaluates all applicable addons in order, building up a running total.
Request Body:
{ "form_target": "Booking", "customer_id": 42, "rate_card_id": 1, "base_rate": 100.00, "flat_rate": 50.00, "ui_context": { "pickup_tailgate": true, "delivery_tailgate": false }, "selected_addon_ids": [5, 8], "quantity_context": { "chargeable_weight": 1200, "actual_weight": 850, "distance": 450, "load_count": 4, "item_count": 6, "cubic_meters": 3.5 }}| Field | Type | Required | Description |
|---|---|---|---|
form_target | string | Yes | e.g., Booking, Rate Card |
customer_id | int | No | For customer-specific overrides |
rate_card_id | int | No | For rate-card-specific overrides |
base_rate | float | No | Base rate for percentage calculations |
flat_rate | float | No | Flat rate component |
ui_context | object | No | Maps ui_binding keys to booleans for automatic triggers |
selected_addon_ids | int[] | No | Manually selected addon IDs for manual triggers |
quantity_context | object | No | Values for per-unit calculations |
Waterfall Calculation Logic:
- Fetch applicable addons via
get_addons_for_context - Filter by trigger mode:
mandatory— always includedautomatic— included ifui_context[ui_binding]is truemanual— included if addon ID is inselected_addon_ids
- Two-pass engine: non-tax addons first (sorted by
calculation_order), then tax addons - For each addon, calculate amount based on
value_typeandapplication_scope:fixed_amount+per_booking= flat feepercentage+per_booking= percentage of base amountfixed_amount+per_unit= rate x quantity (fromunit_type)percentage+per_unit= percentage x quantityexternal_resolver= call external API
- Apply min/max charge guardrails
- Track taxable vs non-taxable running totals
- Tax addons apply to the taxable running total
Response:
{ "success": true, "data": { "addons": [ { "addon_id": 3, "alias": "fuel_levy", "name": "Fuel Levy", "label": "Fuel Levy", "addon_type": "surcharge", "value_type": "percentage", "trigger_mode": "mandatory", "calculation_order": 10, "default_value": "22.5", "raw_value": "22.5", "amount": 33.75, "applied_on_amount": 150.00, "applies_on": "subtotal", "application_scope": "per_booking", "is_taxable": true, "is_tax_addon": false, "tax_category": "standard", "region": "AU", "client_visible": true }, { "addon_id": 10, "alias": "gst", "name": "GST", "label": "GST (10%)", "addon_type": "tax", "value_type": "percentage", "trigger_mode": "mandatory", "calculation_order": 900, "amount": 18.38, "applied_on_amount": 183.75, "applies_on": "running_total", "is_taxable": false, "is_tax_addon": true, "tax_code": "GST", "tax_inclusive": false } ], "base_rate": 100.00, "flat_rate": 50.00, "subtotal": 150.00, "taxable_subtotal": 183.75, "non_taxable_total": 0, "addon_total": 52.13, "grand_total": 202.13 }}8.12 Test External Resolver
Section titled “8.12 Test External Resolver”POST /api/addons/:id/test-resolverRequires: @login_required
Tests an external resolver endpoint with sample or provided data.
Request Body (optional):
{ "origin_lat": -33.86, "origin_lng": 151.20, "dest_lat": -37.81, "dest_lng": 144.96, "weight_kg": 500, "vehicle_type": "rigid_truck", "route_distance_km": 880}8.13 Reference Data
Section titled “8.13 Reference Data”GET /api/addons/reference-dataReturns all lookup data for the addon management UI:
{ "success": true, "data": { "addon_types": [ { "id": 1, "name": "surcharge" }, { "id": 2, "name": "discount" }, { "id": 3, "name": "tax" }, { "id": 4, "name": "document_addition" } ], "form_targets": [ { "id": 1, "name": "Booking" }, { "id": 2, "name": "Rate Card" }, { "id": 3, "name": "Pickup" }, { "id": 4, "name": "Delivery" } ], "value_types": [ { "id": 1, "name": "fixed_amount" }, { "id": 2, "name": "percentage" }, { "id": 3, "name": "per_unit" }, { "id": 4, "name": "text_input" }, { "id": 5, "name": "external_resolver" } ], "rate_calculation_methods": [...], "trigger_modes": [...], "categories": [...], "applies_on_options": [...], "application_scope_options": [...], "unit_type_options": [...], "tax_code_options": [...], "tax_category_options": [...], "region_options": [...] }}9. Customers API
Section titled “9. Customers API”Blueprint prefix: /api/customers
Source: api/customers_api.py
9.1 List Customers
Section titled “9.1 List Customers”GET /api/customers/Returns all active customers from the customers table.
Response:
{ "success": true, "customers": [ { "id": 1, "name": "Acme Logistics", "email": "info@acme.com", "phone": "0412345678", "address": "123 Main St", "city": "Sydney", "state": "NSW", "postcode": "2000", "is_active": true } ], "total": 42}9.2 Get Customer
Section titled “9.2 Get Customer”GET /api/customers/:customer_id9.3 Get Company Customer (Detailed)
Section titled “9.3 Get Company Customer (Detailed)”GET /api/customers/company/:company_idReturns detailed customer information from the companies table with default site address.
9.4 Update Company Customer
Section titled “9.4 Update Company Customer”PUT /api/customers/company/:company_idUpdates company details and default site address.
9.5 Company Sites
Section titled “9.5 Company Sites”List Sites
Section titled “List Sites”GET /api/customers/company/:company_id/sitesAdd Site
Section titled “Add Site”POST /api/customers/company/:company_id/sitesRequest Body:
{ "site_name": "Melbourne Warehouse", "line_1": "456 Industry Rd", "suburb": "Dandenong", "city": "Melbourne", "state": "VIC", "postcode": "3175", "country": "Australia", "is_default": false, "is_billing": false, "is_depot": true, "is_head_office": false, "site_contact_name": "John Smith", "site_phone": "0398765432"}Update Site
Section titled “Update Site”PUT /api/customers/company/:company_id/sites/:site_idDelete Site
Section titled “Delete Site”DELETE /api/customers/company/:company_id/sites/:site_id10. Quotes API
Section titled “10. Quotes API”Blueprint prefix: /api/quotes
Source: api/quotes_api.py
10.1 List Quotes
Section titled “10.1 List Quotes”GET /api/quotes/Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
status | string | none | Filter by status label |
customer_id | int | none | Filter by customer |
search | string | none | Search in quote_number or customer name |
limit | int | 100 | Records per page |
offset | int | 0 | Skip records |
Response:
{ "success": true, "data": [ { "id": 1, "quote_number": "Q-001", "customer_id": 42, "customer_name": "Acme Logistics", "pickup_date": "2025-07-15", "delivery_date": "2025-07-17", "freight_type": "STANDARD", "weight": 1200, "volume": 3.5, "pallets": 4, "pieces": 6, "value": 350.00, "status": "1", "status_label": "Pending", "origin_zone": "Sydney Metro", "destination_zone": "Melbourne Metro", "base_rate": 250.00 } ], "total": 156, "limit": 100, "offset": 0}10.2 Get Quote Statistics
Section titled “10.2 Get Quote Statistics”GET /api/quotes/statisticsResponse:
{ "success": true, "data": { "Pending": 45, "Rejected": 12, "Processed": 99, "total": 156 }}10.3 Get Quote
Section titled “10.3 Get Quote”GET /api/quotes/:idReturns all fields from the unified_quotes table.
10.4 Create Quote
Section titled “10.4 Create Quote”POST /api/quotes/Request Body:
{ "origin_location": "Sydney", "destination_location": "Melbourne", "customer_id": 42, "customer_name": "Acme Logistics", "customer_email": "info@acme.com", "service_type": "Express", "freight_type": "STANDARD", "job_type": "LTL", "weight_kg": 1200, "volume_m3": 3.5, "quantity": 4, "packaging_type": "Pallet", "length_cm": 120, "width_cm": 120, "height_cm": 150, "pickup_date": "2025-07-15", "delivery_date": "2025-07-17", "dangerous_goods": false, "tailgate_pickup": true, "tailgate_delivery": false, "express_delivery": true, "source_portal": "OPERATIONS", "base_rate": 250.00, "fuel_surcharge": 56.25, "gst_amount": 30.63, "total_amount": 336.88}| Field | Type | Required |
|---|---|---|
origin_location | string | Yes |
destination_location | string | Yes |
All other fields are optional. Auto-generates quote_reference in format {portal_initial}Q-{YYYYMMDD}-{NNNN}.
Response: 201 Created
{ "success": true, "message": "Quote created successfully", "data": { "id": 157, "quote_reference": "OQ-20250715-0001" }}10.5 Update Quote
Section titled “10.5 Update Quote”PUT /api/quotes/:idAccepts any updatable field. Only provided fields are updated.
10.6 Update Quote Status
Section titled “10.6 Update Quote Status”PUT /api/quotes/:id/statusRequest Body:
{ "status": "APPROVED", "status_notes": "Approved by operations manager"}Valid statuses: PENDING, QUOTED, APPROVED, REJECTED, EXPIRED, CONVERTED
10.7 Delete Quote
Section titled “10.7 Delete Quote”DELETE /api/quotes/:idSoft delete: sets status to REJECTED with notes “Deleted by user”.
11. Data Model Reference
Section titled “11. Data Model Reference”rate_cards
Section titled “rate_cards”| Column | Type | Constraints | Description |
|---|---|---|---|
id | SERIAL | PK | |
name | VARCHAR(255) | NOT NULL, UNIQUE | Rate card name |
rate_type | VARCHAR(50) | NOT NULL | load, pallet, actual_weight, time, warehouse |
rate_calculation_method_id | INTEGER | NOT NULL, FK | Reference to rate_calculation_methods |
effective_date | DATE | Start date | |
expiry_date | DATE | End date (must be after effective_date) | |
description | TEXT | Description/notes | |
is_active | BOOLEAN | DEFAULT TRUE | Active flag |
status | VARCHAR(20) | DEFAULT ‘active’ | active, suspended |
transit_time_mode | VARCHAR(20) | DEFAULT ‘inherit’ | inherit, profile, custom, none |
transit_time_profile_id | INTEGER | FK | Reference to transit_time_profiles |
created_at | TIMESTAMP | ||
updated_at | TIMESTAMP |
rate_entries
Section titled “rate_entries”| Column | Type | Constraints | Description |
|---|---|---|---|
id | SERIAL | PK | |
rate_card_id | INTEGER | NOT NULL, FK | Reference to rate_cards |
origin_zone_id | INTEGER | FK | Reference to zones |
destination_zone_id | INTEGER | FK | Reference to zones |
rate_calculation_method_id | INTEGER | FK | Reference to rate_calculation_methods |
base_rate | DECIMAL(10,4) | Base rate per unit | |
flat_rate | DECIMAL(10,2) | Flat rate per consignment | |
minimum_rate | DECIMAL(10,2) | Minimum charge | |
maximum_rate | DECIMAL(10,2) | Maximum charge | |
charging_type | VARCHAR(50) | e.g., flat_rate, per_unit | |
display_name | VARCHAR(255) | Human-readable name | |
transit_time_hours | INTEGER | Custom transit time override (NULL = use profile) | |
vehicle_type_id | INTEGER | FK | Reference to vehicle_types (for FTL/hourly) |
distance_km | DECIMAL(10,2) | Route distance | |
rate_per_km | DECIMAL(10,4) | Per-km rate | |
rate_per_tonne | DECIMAL(10,4) | Per-tonne rate | |
minimum_hours | DECIMAL(5,2) | Minimum hours for hourly rates | |
load_rate | DECIMAL(10,2) | Load component for bulk rates | |
drop_rate | DECIMAL(10,2) | Drop component for bulk rates | |
is_active | BOOLEAN | DEFAULT TRUE | |
created_at | TIMESTAMP | ||
updated_at | TIMESTAMP |
Unique Index: (rate_card_id, COALESCE(origin_zone_id, 0), COALESCE(destination_zone_id, 0), COALESCE(vehicle_type_id, 0))
rate_entry_tiers
Section titled “rate_entry_tiers”| Column | Type | Description |
|---|---|---|
id | SERIAL | PK |
rate_entry_id | INTEGER | FK to rate_entries (CASCADE) |
tier_name | VARCHAR(100) | e.g., “0-500kg”, “1-4 pallets” |
tier_range_start | DECIMAL(10,2) | Start of range |
tier_range_end | DECIMAL(10,2) | End of range |
base_charge | DECIMAL(10,4) | Rate per unit within tier |
minimum_charge | DECIMAL(10,2) | Minimum charge for this tier |
tier_unit | VARCHAR(50) | chargeable_weight, pallets, quantity |
tier_type | VARCHAR(30) | weight, pallet, space, volume |
created_at | TIMESTAMP |
rate_entry_conditions
Section titled “rate_entry_conditions”| Column | Type | Description |
|---|---|---|
id | SERIAL | PK |
rate_entry_id | INTEGER | FK to rate_entries |
applies_to | JSONB | Condition config: {"transport_config_ids": [...]}, {"packaging_types": [...]}, {"condition_type": "time_window_pickup"} |
min_length_cm | DECIMAL | |
max_length_cm | DECIMAL | |
min_width_cm | DECIMAL | |
max_width_cm | DECIMAL | |
min_height_cm | DECIMAL | |
max_height_cm | DECIMAL | |
min_weight_kg | DECIMAL | |
max_weight_kg | DECIMAL | |
min_volume_m3 | DECIMAL | |
max_volume_m3 | DECIMAL | |
time_window_start | TIME | For time-window conditions |
time_window_end | TIME | |
time_window_days | JSONB | e.g., ["Monday","Tuesday","Wednesday","Thursday","Friday"] |
rate_overrides
Section titled “rate_overrides”| Column | Type | Description |
|---|---|---|
id | SERIAL | PK |
rate_entry_id | INTEGER | FK to rate_entries |
service_level_id | INTEGER | FK to service_levels |
custom_base_charge | DECIMAL(10,4) | Custom base rate for this service level |
custom_min_charge | DECIMAL(10,2) | Custom minimum for this service level |
Unique Constraint: (rate_entry_id, service_level_id)
rate_entry_surcharges
Section titled “rate_entry_surcharges”| Column | Type | Description |
|---|---|---|
id | SERIAL | PK |
rate_entry_id | INTEGER | FK to rate_entries |
name | VARCHAR(255) | Surcharge name |
action | VARCHAR(20) | add, subtract |
rate_type | VARCHAR(20) | fixed, percentage |
charge_by | VARCHAR(50) | Category label |
amount | DECIMAL(10,2) | Amount or percentage |
addon_id | INTEGER | FK to addons (NULL for custom surcharges) |
is_excluded | BOOLEAN | TRUE = inherited addon excluded from this entry |
rate_card_variations
Section titled “rate_card_variations”| Column | Type | Description |
|---|---|---|
id | SERIAL | PK |
rate_card_id | INTEGER | FK to rate_cards |
rate_entry_id | INTEGER | FK to rate_entries |
variation_name | VARCHAR(255) | e.g., “Pantech 12-pallet” |
variation_type | VARCHAR(50) | custom |
rate_calculation_method_id | INTEGER | FK |
variation_data | JSONB | Contains: base_charge, flat_rate, minimum_charge, conditions[], tiers[], addons[] |
display_order | INTEGER | |
is_active | BOOLEAN |
| Column | Type | Description |
|---|---|---|
id | SERIAL | PK |
name | VARCHAR(255) | Full name (e.g., “Sydney Metro”) |
short_name | VARCHAR(10) | Code (e.g., “SYD”) |
is_metro | INTEGER | 0 or 1 |
is_no_service_flag | INTEGER | 0 or 1 |
is_limited_service | INTEGER | 0 or 1 |
country_id | INTEGER | FK to countries |
zone_listing_id | INTEGER | FK to zone_listings |
boundary_geometry | JSONB | GeoJSON boundary |
is_active | INTEGER | 0 or 1 |
zone_suburbs
Section titled “zone_suburbs”| Column | Type | Description |
|---|---|---|
id | SERIAL | PK |
zone_id | INTEGER | FK to zones |
suburb_name | VARCHAR(255) | |
postcode | VARCHAR(10) | |
state_code | VARCHAR(10) |
service_levels
Section titled “service_levels”| Column | Type | Description |
|---|---|---|
id | SERIAL | PK |
name | VARCHAR(100) | e.g., “Express”, “Standard”, “Economy” |
description | TEXT | |
priority | INTEGER | Display/sort order |
base_cost_multiplier | DECIMAL(5,2) | Multiplier applied to base rates |
transit_time_multiplier | DECIMAL(5,2) | Multiplier applied to transit times |
cubic_factor | DECIMAL(8,2) | For chargeable weight computation (default 250) |
is_active | BOOLEAN |
warehouse_rates
Section titled “warehouse_rates”| Column | Type | Description |
|---|---|---|
id | SERIAL | PK |
rate_card_id | INTEGER | FK to rate_cards (CASCADE) |
category | VARCHAR(30) | INBOUND, STORAGE, OUTBOUND, VALUE_ADDED |
activity_name | VARCHAR(255) | |
activity_code | VARCHAR(50) | |
rate | DECIMAL(10,2) | |
rate_unit | VARCHAR(50) | per_container, per_pallet, per_week, per_hour, etc. |
minimum_charge | DECIMAL(10,2) | |
notes | TEXT | |
sort_order | INTEGER | |
is_active | BOOLEAN |
rate_calculation_methods
Section titled “rate_calculation_methods”| Column | Type | Description |
|---|---|---|
id | SERIAL | PK |
name | VARCHAR(100) | Internal name: load, pallet, actual_weight, chargeable_weight, time, warehouse, etc. |
display_name | VARCHAR(255) | UI label |
symbol | VARCHAR(20) | Unit symbol: $, $/kg, $/plt, $/hr |
order_no | INTEGER | Sort order |
addons
Section titled “addons”| Column | Type | Description |
|---|---|---|
id | SERIAL | PK |
name | VARCHAR(255) | Unique name |
label | VARCHAR(255) | Display label |
alias | VARCHAR(100) | Short unique alias |
addon_type_id | INTEGER | FK to addon_types |
form_target_id | INTEGER | FK to form_targets (legacy) |
value_type_id | INTEGER | FK to value_types |
default_value | VARCHAR(100) | Default amount/percentage |
trigger_mode | VARCHAR(20) | mandatory, automatic, manual |
ui_binding | VARCHAR(100) | UI checkbox binding for automatic triggers |
calculation_order | INTEGER | Lower = earlier in waterfall |
applies_on | VARCHAR(20) | base_rate, subtotal, running_total |
application_scope | VARCHAR(20) | per_booking, per_unit |
unit_type | VARCHAR(20) | pallet, kg, km, cubic_meter, item |
minimum_charge | DECIMAL | |
maximum_charge | DECIMAL | |
tax_category | VARCHAR(20) | standard, gst_free, zero_rated, input_taxed |
region | VARCHAR(10) | AU, US, DXB, PH, GLOBAL |
resolver_url | VARCHAR(500) | External pricing API URL |
resolver_secret | VARCHAR(255) | Secret header for external API |
fallback_value | DECIMAL | Fallback when resolver fails |
apply_to_all_rate_cards | BOOLEAN | |
apply_to_all_customers | BOOLEAN | |
is_active | BOOLEAN |
transit_time_profiles
Section titled “transit_time_profiles”| Column | Type | Description |
|---|---|---|
id | SERIAL | PK |
name | VARCHAR(255) | Profile name |
is_default | BOOLEAN | Whether this is the system default profile |
is_active | BOOLEAN |
transit_time_entries
Section titled “transit_time_entries”| Column | Type | Description |
|---|---|---|
id | SERIAL | PK |
profile_id | INTEGER | FK to transit_time_profiles |
origin_zone_id | INTEGER | FK to zones |
destination_zone_id | INTEGER | FK to zones |
base_transit_hours | INTEGER | Transit time in hours |
vehicle_types
Section titled “vehicle_types”| Column | Type | Description |
|---|---|---|
id | SERIAL | PK |
name | VARCHAR(100) | e.g., “A Trailer”, “B Double”, “Rigid Truck” |
code | VARCHAR(20) | e.g., “A_TRAILER”, “B_DOUBLE” |
12. Rate Types Reference
Section titled “12. Rate Types Reference”load (FTL — Full Truck Load)
Section titled “load (FTL — Full Truck Load)”- Dimension:
vehicle_type_iddifferentiates rates per vehicle type - Storage: One
rate_entryper route + vehicle type combination - Pricing:
base_rate= flat per-load charge. No tiers. - Additional fields:
distance_km,rate_per_km,rate_per_tonne - Computation:
total = base_rate x service_level_multiplier
pallet (Per Pallet / Tier-Based)
Section titled “pallet (Per Pallet / Tier-Based)”- Storage: One
rate_entryper route withbase_rate = 0(pricing via tiers) - Tiers: Typically 3 tiers:
1-4 pallets,5-12 pallets,13+ pallets - Tier unit:
palletsorquantity - Computation: Match pallet count to tier, apply
tier.base_charge x quantity x service_level_multiplier
actual_weight (Per KG)
Section titled “actual_weight (Per KG)”- Storage: One
rate_entryper route withbase_rate= basic per-kg rate,minimum_rate= minimum charge - Tiers: Typically 3 weight tiers:
0-500kg,501-750kg,751+kg - Tier unit:
chargeable_weight - Computation:
- Compute chargeable weight per item:
max(volumetric_weight, dead_weight)wherevolumetric_weight = (L x W x H / 1,000,000) x cubic_factor - Match total chargeable weight to tier
total = tier_rate x chargeable_weight x service_level_multiplier- Enforce minimum charge
- Compute chargeable weight per item:
time (Hourly Hire)
Section titled “time (Hourly Hire)”- Dimension:
vehicle_type_iddifferentiates rates per vehicle - Storage: One
rate_entryper vehicle type (no origin/destination zones) - Fields:
base_rate= hourly rate,minimum_hours - Computation:
effective_hours = max(requested_hours, minimum_hours)total = hourly_rate x effective_hours- Enforce minimum charge
warehouse (Activity-Based)
Section titled “warehouse (Activity-Based)”- Storage: Uses separate
warehouse_ratestable (notrate_entries) - Structure: Grouped by category: INBOUND, STORAGE, OUTBOUND, VALUE_ADDED
- Each rate:
activity_name,rate,rate_unit,minimum_charge - Not computed via the standard rate engine — used for warehouse service pricing
Bulk/CSR Rates
Section titled “Bulk/CSR Rates”- Storage:
rate_entrieswithload_rate+drop_ratecomponents - Fields:
base_rate= total (load_rate + drop_rate) - Computation: Standard pipeline with load/drop breakdown
13. Integration Guide
Section titled “13. Integration Guide”Step-by-Step: Compute a Rate from an External System
Section titled “Step-by-Step: Compute a Rate from an External System”-
Authenticate — Obtain a session cookie by logging in via
/login/operationswith valid credentials. -
Resolve zones (if you have addresses, not zone IDs):
POST /api/rate-entries/check-zoneBody: { "suburb": "Parramatta", "postcode": "2150" }Repeat for both pickup and delivery locations. Extract
zone_idfrom responses. -
Identify customer — Look up the customer ID:
GET /api/customers/Find the customer by name. Note the
id. -
Compute the rate:
POST /api/rate-entries/compute-rateBody: {"customer_id": 42,"pickup_zone_id": 5,"delivery_zone_id": 8,"service_level_id": 2,"items": [{"quantity": 4,"packaging_type": "Pallet","length_cm": 120,"width_cm": 120,"height_cm": 150,"weight_kg": 350}]} -
Read the response:
computation.totals.final_total— The freight charge (before addons)computation.totals.initial_cost— Base rate (goes to BASE PRICE field)computation.addons.grand_total— Total including all addons (fuel, GST, etc.)computation.tier_matched— Which pricing tier was appliedcomputation.calculation_steps— Human-readable audit trail
-
Create a quote (optional):
POST /api/quotes/Body: {"origin_location": "Sydney","destination_location": "Melbourne","customer_id": 42,"weight_kg": 1400,"quantity": 4,"base_rate": 102.60,"fuel_surcharge": 23.09,"gst_amount": 12.57,"total_amount": 138.26,"source_portal": "API"}
Rate Card CSV Import
Section titled “Rate Card CSV Import”Zone and transit time data can be imported via CSV:
POST /api/rate-cards/:id/zones/importContent-Type: multipart/form-dataField: zones_file (CSV file)POST /api/rate-cards/:id/transit-times/importContent-Type: multipart/form-dataField: transit_times_file (CSV or XLSX)External Resolver Pattern (Dynamic Pricing)
Section titled “External Resolver Pattern (Dynamic Pricing)”For addons that need real-time pricing from external APIs:
- Create an addon with
value_type = "external_resolver" - Set
resolver_urlto your API endpoint - Optionally set
resolver_secret(sent asX-Resolver-Secretheader) - Set
fallback_valuefor timeout/error cases
Expected resolver API contract:
Request:
POST {resolver_url}{ "context": "pricing_calculation", "addon_id": "fuel_levy", "shipment_data": { "origin": { "lat": -33.86, "lng": 151.20 }, "destination": { "lat": -37.81, "lng": 144.96 }, "weight_kg": 500, "vehicle_type": "rigid_truck", "route_distance_km": 880 }, "customer_id": 42}Expected response:
{ "status": "success", "cost": 125.50, "currency": "AUD", "taxable": true, "meta_data": { "source": "fuel_index_2025_07" }}Results are cached for 60 seconds per unique route+addon combination. Timeout is 2 seconds.
14. Appendix
Section titled “14. Appendix”A. Rate Calculation Methods
Section titled “A. Rate Calculation Methods”| ID | Name | Display Name | Symbol |
|---|---|---|---|
| 1 | load | Per Load (FTL) | $ |
| 2 | pallet | Per Pallet | $/plt |
| 3 | actual_weight | Per KG (Actual Weight) | $/kg |
| 4 | chargeable_weight | Per KG (Chargeable) | $/kg |
| 5 | time | Per Hour | $/hr |
| 6 | distance | Per KM | $/km |
| 10 | warehouse | Warehouse | $ |
| 11 | per_tonne | Per Tonne | $/t |
B. Surcharge Types (Legacy System)
Section titled “B. Surcharge Types (Legacy System)”Enum values for surcharge_type:
FUEL— Fuel surchargeRESIDENTIAL— Residential deliveryREMOTE— Remote areaOVERSIZE— Oversize itemsTAILGATE— Tailgate serviceWEEKEND— Weekend deliveryDG— Dangerous goodsACCESS— Access difficultyHANDLING— Special handlingOTHER— Other
C. Apply Surcharge Per (Legacy System)
Section titled “C. Apply Surcharge Per (Legacy System)”Enum values for apply_per:
CONSIGNMENT— Per consignmentITEM— Per itemKG— Per kilogramCUBIC_METRE— Per cubic metrePALLET— Per palletHOUR— Per hourMANIFEST— Per manifest
D. Zone Types
Section titled “D. Zone Types”Derived from is_metro flag:
Metro—is_metro = 1Regional—is_metro = 0
Service status (derived):
Full Service—is_no_service_flag = 0ANDis_limited_service = 0Limited Service—is_limited_service = 1No Service—is_no_service_flag = 1
E. Service Level Multipliers (Typical Values)
Section titled “E. Service Level Multipliers (Typical Values)”| Service Level | base_cost_multiplier | transit_time_multiplier | cubic_factor |
|---|---|---|---|
| Express | 1.50 | 0.50 | 250 |
| Standard | 1.00 | 1.00 | 250 |
| Economy | 0.85 | 1.50 | 250 |
How multipliers work:
- Rates stored in
rate_entriesare Standard rates (multiplier = 1.0) - Express:
effective_rate = base_rate x 1.50 - Economy:
effective_rate = base_rate x 0.85 - Custom overrides in
rate_overridesbypass the multiplier entirely
F. Addon Trigger Modes
Section titled “F. Addon Trigger Modes”| Mode | Behavior |
|---|---|
mandatory | Always applied — cannot be removed |
automatic | Applied when the corresponding UI checkbox (ui_binding) is checked |
manual | Applied only when explicitly selected by the user |
G. Addon Tax Categories
Section titled “G. Addon Tax Categories”| Category | In Tax Base? | Description |
|---|---|---|
standard | Yes | Normal taxable item (e.g., Fuel Levy, Tailgate) |
gst_free | No | GST-free supply (e.g., International freight, medical) |
zero_rated | No | Zero-rated supply (e.g., Exports) |
input_taxed | No | Input-taxed (rare — mainly financial services) |
H. Addon Waterfall Calculation Order
Section titled “H. Addon Waterfall Calculation Order”The waterfall processes addons in two passes:
- Non-tax addons — Sorted by
calculation_order(ascending). Each addon can referencebase_rate,subtotal, orrunning_totalfor percentage calculations. - Tax addons — Always processed after all non-tax addons. Tax amount is calculated on the
taxable_running_total(subtotal + all taxable non-tax addons).
Typical ordering:
- 10: Fuel Levy (mandatory, percentage on subtotal)
- 50: Tailgate Pickup (automatic, fixed per booking)
- 50: Tailgate Delivery (automatic, fixed per booking)
- 100: DG Surcharge (manual, fixed per booking)
- 900: GST (mandatory, percentage on taxable running total)
I. Quote Statuses
Section titled “I. Quote Statuses”| Status | Description |
|---|---|
PENDING | Newly created, awaiting pricing |
QUOTED | Price calculated and presented |
APPROVED | Customer approved the quote |
REJECTED | Customer or system rejected |
EXPIRED | Past expiry date |
CONVERTED | Converted to a booking |
J. HTTP Status Code Summary
Section titled “J. HTTP Status Code Summary”| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad request (validation error) |
| 401 | Unauthorized (not logged in) |
| 404 | Resource not found |
| 409 | Conflict (duplicate name, constraint violation) |
| 500 | Internal server error |