Base URL & Environments
Endpoints are versioned under /api/v1. Always use HTTPS.
| Environment | Base URL | Notes |
|---|---|---|
| Production | https://flow.icarry.it |
Connected to real fleet and jobs scheduled by iCarryUP. |
In this partner version, links to Swagger and ReDoc are not shown. Access to /api/docs, /api/redoc, and /openapi.json is restricted to superadmins only (other accounts get 403). For partners, the endpoint list on this page is the reference.
Authentication
The API uses JWT tokens with Bearer. Obtain the token by sending credentials to the OAuth2 token endpoint.
POST /api/v1/auth/token
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=OPS_USER&password=********
The returned token must be sent in the header:
Authorization: Bearer <ACCESS_TOKEN>
⚠️ Set server-side token rotation every 24h or use scheduled refresh. For server-to-server integrations, create dedicated technical users (role: system).
Recommended Workflow
- Load logistics data (drivers + deliveries) in the format expected by
RoutingOptimizationRequest. - Send POST /api/v1/routing/optimize with async_processing=true; receive a job_id.
- Monitor status via GET /api/v1/routing/jobs/{job_id} (polling 5-10s) or via WebSocket /ws/jobs.
- When status=completed read result, export files or generate maps with dedicated endpoints.
- Optional: set webhook_url in the request to receive push callbacks (payload identical to result).
Critical Endpoints
POST /api/v1/routing/optimize
Submits a new optimization. Supports both modern and legacy payloads (automatic conversion of lat/lng to start_lat, etc.).
{
"async_processing": true,
"session_id": "tms-session-example-001",
"webhook_url": "https://your-tms.example.com/webhooks/routing",
"delivery_points": [
{
"tracking_number": "SHIP-EXAMPLE-0001",
"delivery_company": "Example SpA",
"address": "Via Esempio 1, Milano",
"lat": 45.4642,
"lng": 9.1900,
"tot_items": 4,
"priority": 1
}
],
"drivers": [
{
"name": "Driver example 01",
"start_lat": 45.5342,
"start_lng": 9.3265,
"end_lat": 45.5342,
"end_lng": 9.3265,
"capacity": 90,
"vehicle_type": "van_standard",
"max_distance_km": 220
}
],
"algorithms": ["standard"],
"optimization_objective": "time",
"optimization_params": {
"max_iterations": 120,
"time_limit_seconds": 420,
"distance_weight": 0.5,
"time_weight": 0.3,
"balance_weight": 0.2
},
"constraints": {
"max_route_distance": 250,
"max_route_duration": 480,
"max_stops_per_route": 45
}
}
Immediate response:
{
"job_id": "6b758bee-65e5-473e-add8-8cecc36f6fda",
"status": "queued",
"estimated_completion": "2025-11-26T09:11:37.123Z",
"result_url": "/api/v1/routing/jobs/6b758bee-65e5-473e-add8-8cecc36f6fda"
}
GEL (locker pickup): if the user does not have the gel_proximity plan feature (tenant with GEL on map + flag on tier/profile), the backend silently strips pickup_point_id from deliveries and gel_cost_to_time_equivalent_minutes_per_eur from optimization_params before optimization.
Which objective to send
The same four choices as the console wizard. The field is optimization_objective on POST /api/v1/routing/optimize. Default is time. An unknown value is treated as time. They exist because one number cannot be the shortest tour, the most punctual tour, and the cheapest tour at once.
Less time time
WhyWithout this, a short-kilometre tour can leave recipients waiting. It penalises waiting and fills time windows.
WhenUse it for SLAs, tight windows, or promised arrival times.
Fewer km distance
WhyUse it when kilometres dominate, not the clock. Waiting is not penalised.
WhenUse it when fuel and tolls dominate, stops are sparse, or time windows are few.
Balanced balanced
WhyA compromise between time and kilometres, without pushing either extreme.
WhenUse it for a mixed fleet or an ordinary day.
Lower cost cost
WhyTurns kilometres and hours into euros using the driver fields hourly_cost, fuel_consumption, prezzo_litro, and costo_pedaggio_km.
WhenUse it for a fleet budget or a cost comparison. budget_max_eur in optimization_params is the cap, not this objective.
This is not balance_options: that splits workload across drivers (similar stop counts, km, or minutes). The weights distance_weight, time_weight, and balance_weight in optimization_params are fine-tuning; for an integration, optimization_objective is enough.
{
"optimization_objective": "time"
}
Replace time with distance, balanced, or cost. The rest of the optimize request stays as in the example above.
GET /api/v1/routing/jobs/{job_id}
Returns status, progress and, if completed, the full RoutingResultResponse (routes, geometries, global metrics). Results are persisted in shared cache and deleted during scheduled maintenance.
A completed result may include savings_report: estimated savings (km/time/cost) versus a nearest-neighbor baseline on the same job.
GET /api/v1/routing/jobs
Lists the user's latest jobs. Accepts ?limit=50 and ?status=processing/completed... for operational dashboards.
DELETE /api/v1/routing/jobs/{job_id}
Cancels queued or running jobs. Returns 400 if job is already completed.
GET /api/v1/routing/eta
Estimates wall-clock optimization duration (seconds) from similar historical runs (P50/OLS) or the TSP budget. Typical query params: deliveries, drivers, algorithm, routing_provider, has_time_windows, pickup_count, optimization_objective.
GET /api/v1/routing/history
Persistent optimization history for the user/tenant (optionally excludes simulations). Supports q= to search by name or job_id. Useful for dashboards and audit.
GET /api/v1/routing/calendar
Calendar events (planned runs and drafts) for a date range. Powers the console calendar view.
GET /api/v1/routing/status
Enriched health-check (active jobs, version, uptime). Can be used for monitoring systems or readiness probes.
The algorithms_available field is reserved for administrative accounts; for others it is always an empty array.
POST /api/v1/routing/simulate
Starts a what-if simulation (Control Tower) from a baseline job. Returns a simulation_id; the result is an optimization job with is_simulation=true.
{
"baseline_job_id": "6b758bee-65e5-473e-add8-8cecc36f6fda",
"baseline_scope": "PLANNED",
"simulation_mode": "reoptimize",
"patches": [
{
"type": "add_delivery",
"point_type": "delivery",
"lat": 41.9028,
"lng": 12.4964,
"tracking_number": "NEW-001",
"pickup_point_id": "HUB-1"
},
{ "type": "remove_delivery", "tracking_number": "OLD-042" }
]
}
Supported patches: add_delivery (also pickups with point_type=pickup and pickup_point_id), remove_delivery, add_driver, remove_driver, delivery_time_window, balance_options, invalidate_matrices. baseline_scope=PLANNED (LIVE not supported in v1).
GET /api/v1/routing/simulations/{simulation_id}
Returns simulation metadata and comparison (baseline vs what-if result).
POST /api/v1/routing/estimate-workload
Estimates workload (minutes/drivers) before choosing the fleet. mode=quick (fast heuristic) or precise (solver with virtual fleet).
GET /api/v1/routing/delivery-execution/summary
Delivery execution KPI (e.g. on-time %) aggregated across the user's recent jobs.
GET /api/v1/routing/jobs/{job_id}/delivery-execution
Per-job delivery execution detail and proof of delivery (POD).
POST /api/v1/routing/manual-assignments
Allows fixing deliveries → driver before launching optimization. Other GET/DELETE /manual-assignments endpoints allow reading/resetting preferences.
POST /api/v1/routing/jobs/{job_id}/generate-real-map
Creates persistent HTML maps (web/static/maps folder) ready for distribution to drivers. GET .../download-map and GET .../export generate maps and Excel respectively.
POST /api/v1/routing/jobs/{job_id}/generate-geometries
Generates road geometries for a completed job (JWT required). Optional If-Match header with routes_version_hash; mismatch returns 409 routes_version_mismatch — retry without the header (geometries come from the stored job).
GET /api/v1/routing/driver-maps
Lists driver maps for the current tenant (JWT required; 401 without token). Superuser without impersonation may see all tenants.
Geocoding (authenticated)
JWT required. POST /optimize does not geocode: numeric lat/lng in the JSON stay mandatory. Call these endpoints first. source is a diagnostic resolver label, not a business contract.
POST /api/v1/geocoding/lookup
Cache only, up to 500 addresses. hit false means not cached (use forward). Does not start an external resolve.
{
"addresses": ["Via Roma 1, Milano", "Piazza Duomo, Firenze"]
}
{
"results": [
{ "index": 0, "hit": true, "lat": 45.46, "lng": 9.19, "source": "…", "low_confidence": false, "cache_id": 12 },
{ "index": 1, "hit": false, "lat": null, "lng": null, "source": null, "low_confidence": null, "cache_id": null }
]
}
POST /api/v1/geocoding/forward
Resolves up to 10 addresses (raw + query). origin: api (default), wizard, mobile, one_driver, simulator, rubrica. Budget about 25 seconds. status: ok, not_found, pending (retry the same call), error.
{
"origin": "api",
"items": [{ "raw": "Via Roma 1, Milano", "query": "Via Roma 1, Milano" }]
}
{
"results": [
{
"status": "ok",
"lat": 45.46,
"lng": 9.19,
"confidence": 90,
"matched": true,
"low_confidence": false,
"source": "…",
"cache_id": 12
}
]
}
GET /api/geocode_address?address=
Single address, same resolver as forward. 200 with lat/lng; 400 if address is missing; 404 if nothing matched; 503 if the resolve is still pending.
GET /api/reverse_geocode?lat=&lng=
Coordinates to address. 200 { address, source }; 400 if lat or lng is missing; 404 if nothing matched.
Coordinate pins Enterprise
Enterprise plan: store addresses and coordinates. Tenant pins take precedence in lookup/forward. Optional auto-capture from POST /optimize (settings).
POST /api/v1/geocoding/pins
Upsert up to 500 pins. Same role as DELETE and PUT settings: owner, admin, or superuser, plus the geocode_pins feature. A member gets 403 owner_or_admin_required. Each results item has index, status, pin_id, address_key, error.
status: created (new), updated (same address, coordinates moved by at least 5 m), unchanged (move under 5 m), rejected (error: invalid_address, invalid_coordinates, zero_coordinates, swapped_lat_lng, out_of_area), limit_reached (tenant pin cap). summary uses the same counts.
address_key uses the same normalization as lookup and forward: Unicode NFKC, lowercase, collapsed spaces, commas rendered as ", ", leading and trailing spaces and commas removed, 500 characters max. The same string on POST and lookup finds the pin: use the returned value. Civic number, postal code, or extra words change the key; case and double spaces do not.
{
"items": [
{ "address": "Via Roma 1, 20121 Milano MI", "lat": 45.46, "lng": 9.19, "external_id": "CLI-1" }
]
}
{
"results": [
{ "index": 0, "status": "created", "pin_id": 12, "address_key": "via roma 1, 20121 milano mi", "error": null }
],
"summary": { "created": 1, "updated": 0, "unchanged": 0, "rejected": 0, "limit_reached": 0 }
}
GET /api/v1/geocoding/pins
List pins for anyone with the feature (q from 3 characters, external_id, limit, after_id).
DELETE /api/v1/geocoding/pins/{pin_id}
Delete a pin in your tenant. Owner, admin, or superuser only. Another tenant’s pin returns 404.
GET / PUT /api/v1/geocoding/pins/settings
GET (feature): enabled, auto_capture, count, max_pins. PUT (owner/admin): required body { "auto_capture": true|false }. PUT response: enabled, auto_capture, max_pins.
{ "auto_capture": true }
Excel export (authenticated)
POST /api/v1/routing/export-geocoded-deliveries
Requires Authorization Bearer. Returns an Excel file for the posted payload.
POST /api/v1/routing/export-geocoded-drivers
Requires Authorization Bearer. Returns an Excel file for the posted payload.
POST /api/v1/routing/export-manual-drivers
Requires Authorization Bearer. Returns an Excel file for the posted payload.
POST /api/v1/routing/export-manual-deliveries
Requires Authorization Bearer. Returns an Excel file for the posted payload.
POST /api/v1/routing/export-manual-pickups
Requires Authorization Bearer. Returns an Excel file for the posted payload.
Public / bootstrap endpoints
GET /api/v1/public/features
Returns frontend flags: gel_proximity_enabled, rlp_bryntum_timeline_enabled, and PostHog bootstrap (posthog_enabled, posthog_host, posthog_project_api_key, posthog_session_replay, posthog_unmask_replay). Authorization Bearer is optional: without a token the GEL flag is false; with a valid token, tenant and tier features are evaluated. If PostHog is disabled or incomplete, posthog_enabled is false and the project key is omitted.
GET /api/v1/public/fuel-prices
Fuel and energy averages, no authentication. This is not an iCarry time series: a current snapshot from MIMIT (daily Italy and regional averages) plus plant-level LNG/HVO and monthly public AC/DC charging. For UI and cost estimates, not billing, and with no SLA on third-party sources. It is not the route field prezzo_litro.
Cache-Control public, max-age=1800: honour that TTL when polling. national and regions: diesel, petrol and LPG in €/L, methane in €/kg (regions[].name is the MIMIT region name, e.g. Lombardia). extended.gnl (eur_kg), hvo (eur_l), electric_ac/electric_dc (eur_kwh) with value, unit, updated, source. If every source fails: {"available": false}. The GME PUN is not included. The JSON sample shows shape, not live values.
{
"available": true,
"source": "MIMIT",
"updated": "21/09/2026",
"national": { "gasolio": 1.623, "benzina": 1.712, "gpl": 0.741, "metano": 1.401 },
"regions": [{ "name": "Lombardia", "gasolio": 1.631, "benzina": 1.720, "gpl": 0.738, "metano": 1.390 }],
"extended": {
"gnl": { "value": 1.452, "unit": "eur_kg", "source": "mimit_impianti" },
"hvo": { "value": 1.890, "unit": "eur_l", "source": "mimit_impianti" },
"electric_ac": { "value": 0.62, "unit": "eur_kwh", "source": "adiconsum_mensile" }
}
}
The GEL pickup-points map endpoint is not part of this public partner documentation. It is covered in internal (superadmin) docs and provided on request when your contract includes locker integration. Contact support for details.
Input data dictionary (Excel/CSV ↔ JSON)
Official template columns map to POST /api/v1/routing/optimize fields. Download blank templates, then send geocoded JSON. Column order is defined in the delivery and driver templates.
Templates: GET /api/v1/templates/delivery-template and GET /api/v1/templates/driver-template (XLSX/CSV). There is no separate pickup download — use the delivery sheet with point_type=pickup.
Excel may omit lat/lng when an address can be geocoded. POST /optimize always requires numeric lat/lng (and start_lat/start_lng for drivers).
POST /api/v1/files/upload is storage only: it does not parse columns or run optimization. Partner API integration is JSON to /optimize.
Deliveries / stops (delivery_points[])
| Excel | JSON /optimize | Required | Type | Use |
|---|---|---|---|---|
tracking_number | tracking_number | yes | string ≤64 | Stop ID |
delivery_address | address | yes (or lat+lng) | string | Geocoding / display |
delivery_company | delivery_company | yes in JSON (import default “Cliente”) | string | Metadata |
lat / lng | lat / lng | yes in JSON; optional in Excel if address is set | degrees | Matrix / geometry |
tot_items | tot_items | no (default 1) | int ≥1 | Capacity (parcels) |
priority | priority | no (default 1) | 1–10 (1 = highest) | Priority / drop protection |
estimated_duration | estimated_duration | no | minutes | Service time at stop |
notes | notes | no | string ≤500 | Driver app only |
phone | phone | no | E.164 (+ and digits, e.g. +393331234567) | Recipient phone (tel link in app and Control Tower) |
preassigned_driver | preassigned_driver | no | drivers[].name | Lock → mandatory_assignments |
point_type | point_type | no (default delivery) | pickup | delivery | Pickup before delivery; an unlinked pickup is a standalone stop |
pickup_point_id | pickup_point_id | no | string | Links a delivery to its pickup; omitted pickup = return to the driver's end |
time_window_start / end | same | no | HH:MM | Time windows |
volume_m3 / weight_kg | same | no | float ≥0 | Volume cap (weight → 0.001 m³ if volume missing) |
JSON-only (stop): optional id (otherwise tracking_number or a generated UUID).
A point_type=pickup with no linked delivery is not dropped: it is a required stop and the goods return to the driver's end point. The response sets pickup_mode=return_to_depot and adds a warnings row. If it cannot be planned it appears in unassigned_deliveries.
Drivers / vehicles (drivers[])
| Excel | JSON /optimize | Required | Notes |
|---|---|---|---|
name | name | yes | Identity |
phone | phone | no | Optional driver phone in E.164 |
start_address / end_address | — | start required on import | Geocoding → start_* / end_* |
lat / lng | start_lat / start_lng | yes in JSON | Start depot |
end_lat / end_lng | end_lat / end_lng | yes in JSON (default = start) | Return depot |
capacity | capacity | no (default 50) | Max parcels (tot_items) |
max_volume_m3 | max_volume_m3 | no | Volume capacity |
vehicle_type | vehicle_type | no (default car) | Full list below: car, van, van_standard, van_maxi, truck, truck_7_5t, truck_12t, truck_18t, truck_32t, artic_44t, bike, scooter, icarry_cargo_bike |
routing_profile | routing_profile | no | Override: car, van_standard, van_maxi, truck_7_5t, truck_12t, truck_18t, truck_32t, artic_44t, icarry_cargo_bike |
max_distance_km | max_distance_km | no | Max route km |
hourly_cost, fuel_consumption, prezzo_litro, costo_pedaggio_km | same | no | Cost / reporting |
start_time / end_time | same | no (08:00–18:00 if pair set) | Shift |
break_start / break_end | same | as a pair | Break |
max_minutes | max_minutes | no | Max duration (derivable from shift−break) |
JSON-only (driver): optional id, active (default true), max_overtime_minutes (default 0).
Allowed vehicle types (full list)
Send a slug, not a commercial model. Default vehicle_type=car. 4x4 or body variants have no dedicated slug.
| API slug | Category | When to use it |
|---|---|---|
car | Car (M1) | Standard car. |
van Alias | Vans (N1) | Same as van_standard. |
van_standard | Vans (N1) | Compact van up to 2.5 t, height about 1.9 m. |
van_maxi | Vans (N1) | High-roof van up to 3.5 t, height about 2.7 m. |
truck Alias | Trucks | Same as truck_7_5t. |
truck_7_5t | Trucks | Light urban truck, GVW 7.5 t. |
truck_12t | Trucks | Medium truck, GVW 12 t. Example: Iveco TGM 13.250 4x4 → truck_12t. |
truck_18t | Trucks | Heavy 2-axle truck, GVW 18 t. |
truck_32t | Trucks | 4-axle truck, GVW 32 t. |
artic_44t | Articulated | Articulated 44 t, about 16.5 m. |
bike Alias | Cycle logistics | Same as icarry_cargo_bike. |
scooter Alias | Car (M1) | Same as car. |
icarry_cargo_bike | Cycle logistics | Last-mile cargo bike. |
If set, routing_profile must be a road slug from the table (not a van/truck/bike/scooter alias) and overrides vehicle_type.
Common header aliases (IT/EN)
File import also accepts alternate headers (normalized, accents stripped). Frequent examples:
- Deliveries: address/delivery_address ← indirizzo, destinazione, location · delivery_company ← destinatario, cliente, company · tot_items ← colli, quantity, pezzi · phone ← telefono, tel, cellulare, mobile · time_window_start/end ← fascia_dalle/alle, dalle/alle, window_start/end.
- Drivers: name ← nome, autista, fattorino · start_address/end_address ← partenza/rientro, departure/arrival · capacity ← capacita, max_consegne · start_time/end_time ← turno_inizio/fine, shift_start/end.
Main Data Schema
DeliveryPointRequest
Required in JSON: lat/lng, tracking_number (≤64), address, delivery_company. Optional: tot_items (default 1), priority (1–10), estimated_duration (minutes), notes, phone (E.164, e.g. +393331234567), preassigned_driver, point_type (pickup|delivery), pickup_point_id, time_window_start/end (HH:MM), volume_m3, weight_kg, id.
Full Excel↔JSON column map: see “Input data dictionary” above.
DriverRequest
Required in JSON: name, start_lat/lng, end_lat/lng (end defaults to start). Optional: phone (E.164), capacity (default 50), max_volume_m3, vehicle_type (default car; full list in vehicle types table), routing_profile, max_distance_km, costs (hourly_cost, fuel_consumption, prezzo_litro, costo_pedaggio_km), start_time/end_time, break_start/end, max_minutes, active, max_overtime_minutes, id. Excel only: start_address/end_address (geocoded to coordinates).
vehicle_type — full list: car, van, van_standard, van_maxi, truck, truck_7_5t, truck_12t, truck_18t, truck_32t, artic_44t, bike, scooter, icarry_cargo_bike. Default car. Do not send a make/model: an Iveco TGM 13.250 4x4 is truck_12t. Aliases: van → van_standard, truck → truck_7_5t, bike → icarry_cargo_bike, scooter → car.
routing_profile — optional override. Allowed values: car, van_standard, van_maxi, truck_7_5t, truck_12t, truck_18t, truck_32t, artic_44t, icarry_cargo_bike. If omitted it is derived from vehicle_type.
Full Excel↔JSON column map: see “Input data dictionary” above.
OptimizationParamsRequest
Balances distance, time and load via configurable weights. Values depend on your operational priorities.
RoutingConstraintsRequest
Enables hard constraints (max duration, max distance, stops per route). If omitted the engine applies dynamic limits from the drivers.
Completion Webhook
Set webhook_url in the request to receive a signed POST (header X-iCarry-Signature) with the same payload as RoutingResultResponse. If the webhook returns status code < 300 it is considered delivered; otherwise 3 exponential retries (15s, 60s, 180s) are performed.
Realtime via WebSocket
Connect to wss://flow.icarry.it/ws/jobs and join room job_{job_id} via Socket.IO join_room event. You will receive job_progress, job_completed, job_failed events.
Support Tools
- CSV/XLSX templates — GET /api/v1/templates/driver-template and /api/v1/templates/delivery-template. Column dictionary: see “Input data dictionary” on this page.
- Driver maps — GET /api/v1/routing/driver-maps lists tenant-scoped maps (JWT). Single-map HTML URLs remain capability links.
- Driver App HTML — GET /api/v1/routing/jobs/{job_id}/download-driver-app?driver_name={driver_name} returns the offline version of the single-driver map.
Errors & Best Practices
| Code | Scenario | How to react |
|---|---|---|
| 400 | Validation error (Pydantic schema) | Read detail for invalid field; apply sanitization on caller side. |
| 401 | Token missing or expired (also driver-maps / Excel export without Bearer) | Regenerate token and retry the call. |
| 403 | algorithms value not allowed (use auto or standard) | Use auto or standard, or contact support. |
| 403 | Admin/superuser endpoint without privileges | Tenant-admin endpoints need is_admin; billing/algorithm mutations and settings need is_superuser. |
| 409 | Duplicate job in queue (same session_id) | Use unique sessions for parallel jobs. |
| 409 | routes_version_mismatch (If-Match / concurrent route edit) | Reload the job hash, or retry generate-geometries without If-Match. |
| 503 | Dependency not configured (e.g. GEL webhook token missing) | Configure the required token/secret in admin settings or env, then retry. |
| 500 | Internal error / Routing service unreachable | Retry with backoff. Verify status with /api/v1/routing/status. |
To reduce response times, set batches between 80 and 120 deliveries per job; beyond 150 stops consider automatic splitting (flag allow_partial_assignments true) and subsequent recomposition.
Support
For advanced features (high-performance engine, priority SLA, dedicated environments) or to enable IP whitelisting on production endpoints, contact [email protected] with your client_id and the public IP to authorize.
🚀 TMS integration guide (Transport Management System)
This guide summarizes how to integrate iCarryUP routing and optimization APIs with your TMS using server-to-server calls (no web UI). For the full Italian walkthrough with extended code samples, switch locale to Italiano.
Quick index
1. Introduction
Use the versioned API under /api/v1. Typical flow: your TMS sends drivers and stops → iCarryUP runs optimization → you poll GET /routing/jobs/{id} or receive a webhook_url callback → import routes back into the TMS.
- Automation — batch daily or on-demand re-optimization.
- Reliability — async jobs, retries on webhooks, health via
/routing/status.
2. Setup
2.1 Credentials
Request a technical user from [email protected] (roles, IP allowlisting if needed).
2.2 Base URL
| Environment | Base URL | When to use |
|---|---|---|
| Production | https://flow.icarry.it |
Live traffic |
2.3 Connectivity check
curl -s "https://flow.icarry.it/api/v1/routing/status"
Example fields: status, version (e.g. 2.9.23-1), active_jobs, routing_service_status. No auth required for reachability. The field algorithms_available is reserved for administrative accounts; for others it is always [].
2.4 Public API & GEL map layer
Use GET /api/v1/public/features to see whether GEL proximity features apply to your integration (same as the Public endpoints section above). Bearer is optional; with a valid token, tenant map setting and gel_proximity on profile/tier are applied.
On POST /api/v1/routing/optimize, you may set per-stop pickup_point_id and gel_cost_to_time_equivalent_minutes_per_eur in optimization_params. If the token user lacks effective gel_proximity, the API strips these fields silently before solving — validate flags/tier before relying on GEL in optimization.
3. Authentication
OAuth2-style password grant to obtain a JWT, then Authorization: Bearer <token> on API calls.
curl -X POST "https://flow.icarry.it/api/v1/auth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&username=USER&password=SECRET"
Refresh before expiry (often ~24h). Prefer dedicated technical users for server-to-server flows.
4. End-to-end workflow
- Map TMS stops/drivers to
RoutingOptimizationRequest(see main schema section on this page). - If lat/lng are missing, resolve addresses with
POST /api/v1/geocoding/forward(orlookupfor cache hits only).POST /optimizestill requires numeric coordinates. See Geocoding. - Optional (Enterprise): sync your pins with
POST /api/v1/geocoding/pinsso later lookups reuse your coordinates. You can also enable auto-capture fromPOST /optimizevia/api/v1/geocoding/pins/settings. POST /api/v1/routing/optimizewithasync_processing: true→ receivejob_id.- Poll
GET /api/v1/routing/jobs/{job_id}every 5–10s or subscribe to websocket / webhook. - When
statusis completed, readresult; export maps or Excel via dedicated endpoints if needed.
5. Minimal optimize example
curl -X POST "https://flow.icarry.it/api/v1/routing/optimize" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"async_processing": true,
"session_id": "tms-2026-04-02-001",
"delivery_points": [{"tracking_number":"1","delivery_company":"ACME","address":"Via Roma 1, Milano","lat":45.46,"lng":9.19,"tot_items":1,"priority":1}],
"drivers": [{"name":"Driver 1","start_lat":45.46,"start_lng":9.19,"end_lat":45.46,"end_lng":9.19,"capacity":50,"vehicle_type":"van_standard","max_distance_km":200}],
"algorithms": ["standard"],
"optimization_objective": "time"
}'
Why time, distance, balanced, and cost exist: choice guide.
6. Webhooks
Set webhook_url on the optimize request to receive a POST with the same shape as the completed job result. Optional X-iCarry-Signature verification. Failed deliveries retry with backoff.
7. Best practices
- Batch roughly 80–120 stops per job when possible; split very large instances.
- Use unique
session_idvalues to avoid 409 duplicate-job conflicts. - Handle 401 by refreshing tokens; 403 may mean blocked algorithm or missing admin rights.
- Monitor
/routing/statusand job error payloads for integration health.
8. Troubleshooting & support
Include job id, HTTP status, redacted payload, and timestamps when writing to [email protected].