# Routing24 route optimizer > Optimize vehicle/delivery routes on Routing24 (routing24.com) from a list of addresses and vehicles. Use when the user wants to plan or optimize a route, build a delivery run, or assign stops to vehicles and get a shareable plan link. Drives the Routing24 tab in the user's own browser via the window.R24Agent JS API (geocode → optimize → render → save → plan link). Requires the Claude Cowork browser extension (Chrome/Edge) and that the user is signed in to routing24.com. Turn a natural-language routing request ("optimize these 8 addresses with 2 vans from this depot") into an optimized route plan on **Routing24**, shown on the map, with a link the user can open. Routing24 has **no server API** — everything runs in the browser. You drive it by executing JavaScript in the page against a purpose-built global, **`window.R24Agent`**, using Cowork's `javascript_tool`. The optimizer itself runs client-side in WASM, so `optimize()` is asynchronous: you start it, then **poll `status()`**. This document is self-contained (procedure + API contract + snippets). The live copy is served at `https://routing24.com/llms.txt` and is generated from the app's types, so it always matches the deployed API. ## Prerequisites (check first) - Cowork browser tools available (`navigate`, `javascript_tool`). This is **Chrome/Edge only** (beta). If `javascript_tool` is unavailable, tell the user the Routing24 automation needs the Claude for Chrome / Cowork browser integration on Chrome or Edge, and stop. - The user is signed in to `https://routing24.com`. Cowork reuses the browser's login session, so no credentials are needed here. ## Procedure 1. **Open the app.** `navigate` to `https://routing24.com/app/plan/new/optimize`. 2. **Verify the API is present.** Run `await window.R24Agent?.version` via `javascript_tool`. If it's `undefined`, the page may still be loading or the façade isn't deployed — reload once and retry; if still missing, tell the user the Routing24 agent API isn't available on this page and stop. 3. **Confirm auth.** Run `await window.R24Agent.ensureAuth()`. - If `loggedIn` is `false`, ask the user to sign in to routing24.com in the tab, then re-run. (Anonymous works for a same-browser view, but a durable / cross-device plan link needs login.) 4. **Parse the request** into the `optimize()` input shape (see **API reference** below for the full contract). Times are seconds-since-midnight; all fields except the addresses are optional. Use **≥2 stops** and **≥1 vehicle**. `optimize()` validates the input and, on bad data, throws a message naming the offending fields — relay it to the user. 5. **Geocode + confirm.** Run `await window.R24Agent.geocode([])`. - Show the user any row with `ok:false` (not found) and ask them to fix the address. Also surface `matched` values that look wrong and confirm. - Once confirmed, use the returned `lat`/`lng` for each entity (pass them in the `optimize()` input so you don't re-geocode). `optimize()` will geocode any address you leave without coordinates, but doing it here lets the user confirm ambiguous matches first. 6. **Start optimization.** Run `await window.R24Agent.optimize()`. It returns quickly with `{ started: true, planUuid }`. 7. **Poll progress.** Every ~3 seconds run `window.R24Agent.status()` until `phase === 'done'` or `phase === 'error'`. - Relay `progress` (0–1) and, once available, `routes` / `distance` / `feasible`. - Small jobs finish in seconds; large ones can take minutes (keep the tab active). If `phase === 'error'`, report `error` and stop. - To abort on the user's request: `window.R24Agent.cancel()`. 8. **Show + save.** Run `window.R24Agent.render()` (brings the routes onto the map), then `await window.R24Agent.save()` to persist and get `{ saved, planUrl }`. - To *show the user the map*, take a screenshot of the tab after `render()` — the API returns JSON, not images. 9. **Report** to the user: - Number of routes, total distance (with unit), total duration, and any `unservedCount` (stops that couldn't be served — mention them). - Whether the solution is `feasible`. - The **plan link** (`planUrl`) they can open, and note the map on screen shows the routes. ## JavaScript snippets Ready-to-eval expressions for `javascript_tool` — each resolves to a value that comes back to you. Replace the ADDRESS/STOP/VEHICLE placeholders. ```js // 0) Confirm the API is present (undefined => reload or façade not deployed). await window.R24Agent?.version; // 1) Auth status. If loggedIn is false, ask the user to sign in, then re-run. await window.R24Agent.ensureAuth(); // 2) Geocode depot + all stop addresses. Inspect for ok:false and sanity-check // the `matched` strings with the user before optimizing. await window.R24Agent.geocode(["DEPOT ADDRESS", "STOP 1 ADDRESS", "STOP 2 ADDRESS"]); // 3) Start optimization. Prefer passing lat/lng from step 2 so nothing is // re-geocoded (addresses alone also work; they'll be geocoded). await window.R24Agent.optimize({ depot: { address: "DEPOT ADDRESS" /*, lat, lng */ }, stops: [ { id: "S1", address: "STOP 1 ADDRESS", delivery: 1, service_duration_s: 300 }, { id: "S2", address: "STOP 2 ADDRESS", delivery: 1, service_duration_s: 300 }, ], vehicles: [ { available_count: 2, capacity: 20, tw_early_s: 8 * 3600, tw_late_s: 18 * 3600 }, ], // options: { time_limit_s: 30 }, }); // 4) Poll (~every 3s) until phase is "done" or "error". Relay progress meanwhile. window.R24Agent.status(); // 5) Show routes on the map, then persist and get the plan link. window.R24Agent.render(); await window.R24Agent.save(); // -> { saved, planUrl } // 6) Current plan URL (also returned by save()). window.R24Agent.planUrl(); // Optional: cancel a long-running solve (keeps the best solution so far). window.R24Agent.cancel(); // Optional single blocking wait (prefer separate status() calls to stream progress): await (async () => { for (let i = 0; i < 400; i++) { const s = window.R24Agent.status(); if (s.phase === "done" || s.phase === "error") return s; await new Promise((r) => setTimeout(r, 3000)); } return window.R24Agent.status(); })(); ``` ## API reference Installed on every `https://routing24.com/app/*` page as `window.R24Agent`. Async methods return Promises. `optimize()` is **fire-and-poll**: it returns immediately after starting the background WASM solve; observe it with `status()`. Shapes reuse the solver's own `Site`/`VehicleType`/`Location` fields and are validated at runtime — the JSON Schema at the end is authoritative. ### `version: string` Read it to confirm the façade is present: `await window.R24Agent?.version`. ### `ensureAuth(): Promise` Refreshes/validates the session's services token and reports status. Cannot log a user in — if `loggedIn` is false, ask the user to sign in on the page. ```ts type AuthStatus = { loggedIn: boolean; email?: string; region?: string; uid?: string; }; ``` ### `geocode(addresses: string[]): Promise` Batch-geocodes address strings (order preserved; every input gets a row back). `ok:false` (`quality:"failed"`) = not found → ask the user to correct it. ```ts type GeocodeResult = { input: string; // The address string as passed in. ok: boolean; matched?: string; // Canonical/expanded address the geocoder matched. lat?: number; lng?: number; quality: "rooftop" | "failed"; }; ``` ### `optimize(input: OptimizeInput): Promise<{ started: true; planUuid: string }>` Creates a fresh plan, fetches the O/D matrix, and starts the solve. Returns at once. ```ts type OptimizeInput = { depot: Place; // Single depot, used as both start and end for every vehicle. stops: OptimizeStop[]; // min 1 vehicles: OptimizeVehicle[]; // min 1 options?: { time_limit_s?: integer }; }; ``` ```ts // A geographic point the caller supplies either as coordinates or as an address // string to geocode. `lat`/`lng` are the solver's own location fields. type Place = { lat?: number; lng?: number; address?: string; }; ``` ```ts // A delivery/pickup stop: a place plus solver site constraints. type OptimizeStop = { lat?: number; lng?: number; address?: string; pickup?: number; delivery?: number; service_duration_s?: number; tw_early_s?: number; tw_late_s?: number; prize?: number; required?: boolean; id?: string; }; ``` ```ts // A vehicle (type): solver vehicle constraints; `available_count` clones this type. type OptimizeVehicle = { tw_early_s?: number; tw_late_s?: number; capacity?: number; available_count?: number; cost?: { fixed?: number; distance?: number; duration?: number; stop?: number }; id?: string; }; ``` - Each depot/stop needs **either** `lat`+`lng` **or** an `address` (geocoded automatically; if any fail, `optimize()` throws listing them). - Times are **seconds since midnight**. `delivery`/`pickup` are single-dimension loads. `available_count` = identical vehicles of that type. The single depot is both start and end. ### `status(): OptimizeStatus` Synchronous snapshot — poll (~every 3s) while solving. `phase` walks `idle → geocoding → matrix → solving → done` (or `error`). ```ts type OptimizeStatus = { phase: OptimizePhase; running: boolean; progress?: number; // 0..1 while solving, when available. feasible?: boolean; routes?: number; stops?: number; unservedCount?: number; distance?: number; distanceUnit?: "km" | "mi"; durationHours?: number; error?: string; planUuid?: string; }; ``` ### `render(): void` Navigates to the plan's optimize page so the routes draw on the map. ### `save(): Promise<{ saved: boolean; planUrl: string }>` Persists the plan and returns `{ saved, planUrl }`. The link reopens only for the same signed-in user (cross-device only after `save()` while logged in). ### `planUrl(): string` Absolute URL of the current plan's optimize page. ### `cancel(): void` Aborts an in-flight solve (mirrors the UI's cancel button). ### Machine-readable JSON Schema Generated by typia from the app types (OpenAPI 3.1 / JSON Schema 2020-12): ```json { "version": "3.1", "components": { "schemas": { "OptimizeInput": { "type": "object", "properties": { "depot": { "$ref": "#/components/schemas/Place", "description": "Single depot, used as both start and end for every vehicle." }, "stops": { "type": "array", "items": { "$ref": "#/components/schemas/OptimizeStop" }, "minItems": 1 }, "vehicles": { "type": "array", "items": { "$ref": "#/components/schemas/OptimizeVehicle" }, "minItems": 1 }, "options": { "type": "object", "properties": { "time_limit_s": { "type": "integer", "minimum": 1, "description": "Solve budget in seconds; omit to auto-scale with problem size." } }, "required": [] } }, "required": [ "depot", "stops", "vehicles" ] }, "Place": { "type": "object", "properties": { "lat": { "type": "number" }, "lng": { "type": "number" }, "address": { "type": "string" } }, "required": [], "description": "A geographic point the caller supplies either as coordinates or as an address\nstring to geocode. `lat`/`lng` are the solver's own location fields." }, "OptimizeStop": { "type": "object", "properties": { "lat": { "type": "number" }, "lng": { "type": "number" }, "address": { "type": "string" }, "pickup": { "type": "number" }, "delivery": { "type": "number" }, "service_duration_s": { "type": "number" }, "tw_early_s": { "type": "number" }, "tw_late_s": { "type": "number" }, "prize": { "type": "number" }, "required": { "type": "boolean" }, "id": { "type": "string" } }, "required": [], "description": "A delivery/pickup stop: a place plus solver site constraints." }, "OptimizeVehicle": { "type": "object", "properties": { "tw_early_s": { "type": "number" }, "tw_late_s": { "type": "number" }, "capacity": { "type": "number" }, "available_count": { "type": "number" }, "cost": { "$ref": "#/components/schemas/solver.VehicleCost" }, "id": { "type": "string" } }, "required": [], "description": "A vehicle (type): solver vehicle constraints; `available_count` clones this type." }, "solver.VehicleCost": { "type": "object", "properties": { "fixed": { "type": "number" }, "distance": { "type": "number" }, "duration": { "type": "number" }, "stop": { "type": "number" } }, "required": [] }, "GeocodeResult": { "type": "object", "properties": { "input": { "type": "string", "description": "The address string as passed in." }, "ok": { "type": "boolean" }, "matched": { "type": "string", "description": "Canonical/expanded address the geocoder matched." }, "lat": { "type": "number" }, "lng": { "type": "number" }, "quality": { "oneOf": [ { "const": "rooftop" }, { "const": "failed" } ] } }, "required": [ "input", "ok", "quality" ] }, "AuthStatus": { "type": "object", "properties": { "loggedIn": { "type": "boolean" }, "email": { "type": "string" }, "region": { "type": "string" }, "uid": { "type": "string" } }, "required": [ "loggedIn" ] }, "OptimizeStatus": { "type": "object", "properties": { "phase": { "$ref": "#/components/schemas/OptimizePhase" }, "running": { "type": "boolean" }, "progress": { "type": "number", "description": "0..1 while solving, when available." }, "feasible": { "type": "boolean" }, "routes": { "type": "number" }, "stops": { "type": "number" }, "unservedCount": { "type": "number" }, "distance": { "type": "number" }, "distanceUnit": { "oneOf": [ { "const": "km" }, { "const": "mi" } ] }, "durationHours": { "type": "number" }, "error": { "type": "string" }, "planUuid": { "type": "string" } }, "required": [ "phase", "running" ] }, "OptimizePhase": { "oneOf": [ { "const": "idle" }, { "const": "geocoding" }, { "const": "matrix" }, { "const": "solving" }, { "const": "saving" }, { "const": "done" }, { "const": "error" } ] } } }, "schemas": [ { "$ref": "#/components/schemas/OptimizeInput" }, { "$ref": "#/components/schemas/GeocodeResult" }, { "$ref": "#/components/schemas/AuthStatus" }, { "$ref": "#/components/schemas/OptimizeStatus" } ] } ``` ## Notes & pitfalls - Everything happens in the user's authenticated tab; you are not calling any server API directly. - The API returns **serialized JSON/text only** — you cannot receive an image from it. To show the user the map, `render()` then screenshot the tab. - `optimize()` starts a **new plan** each time. The plan link only reopens for the same logged-in user (and cross-device only after `save()` while signed in). - If `status()` never leaves `matrix`/`solving`, the network (matrix service) or the solve may be slow — keep polling; only treat it as failed on `phase:'error'`.