Tournevia

API reference

Sessions, reliable writes, asynchronous calculations and operation contracts.

Choose the right entry point

Application operations use /api/v1 on the same origin as the interface. The OpenAPI JSON contract provides field types, limits and responses for the documented version. A shared (HTTPS) installation does not serve /api/docs or /api/openapi.json itself, so use this copy. JSON names and enum values remain English regardless of the interface language.

This API uses named accounts and sessions. It does not offer developer keys or a multi-tenant SaaS platform. The server determines organization ownership and permissions; clients cannot choose another organization.

Sessions and write protection

  1. Sign in with POST /api/v1/session from the configured origin, including X-VRP-Request: 1. This first request has no session CSRF token yet.
  2. The browser retains the HttpOnly session cookie. The response contains a csrf_token.
  3. Writes require X-VRP-Request: 1 and X-CSRF-Token, alongside the cookie and expected origin.
  4. Sign out with DELETE /api/v1/session.

Shared mode requires HTTPS and the __Host-vrp_session cookie. Authenticated local mode uses a distinct cookie. Expired sessions return 401; incorrect origin or CSRF tokens return 403.

Save personal language with PUT /api/v1/preferences and {"language":"en"} or {"language":"fr"}. This does not alter roles or allow selecting another user.

Change the signed-in user's password with PUT /api/v1/session/password and current_password and new_password (15 to 128 characters); the user's other sessions end and wrong current passwords count toward the sign-in limits. An administrator sets another user's password with POST /api/v1/users/{key}/password and new_password, which ends all of that user's sessions.

Sessions last at most eight hours and end after 30 minutes without requests; requests sent with X-VRP-Background: 1 do not count as activity. Failed sign-ins are limited to 5 per username and client, and 50 per username, in 15 minutes (429 LOGIN_THROTTLED). Behind the bundled HTTPS proxy, sign-in requests are also limited per address (429 SIGNIN_RATE_LIMITED).

Read and update records

Filter deliveries by service_date. Fleet records belong to the workspace and are independent of the date. Lists are in identifier order; use limit (1 to 250, default 50) and offset for pagination. status filters across the whole day or fleet — active, inactive, review, missing or ready for deliveries; active, inactive or off_day with a service_date for vehicles — and q searches identifiers (and delivery addresses) without case. GET /api/v1/orders/summary and GET /api/v1/vehicles/summary return the matching counts, also for a search.

A saved vehicle has weekly working hours in hours: for each day, monday to sunday, a start and an end in local HH:MM time, or null for a day it does not work. Up to 100 dated exceptions replace those hours on their date: a day off (hours: null) or other hours. Overnight work is unsupported. A calculation copies each vehicle as it works on the planning day.

GET /api/v1/geocode?q=… (3 to 200 characters) returns candidate points for an address, best first and ranked around the main depot, each with label, latitude, longitude, score (0 to 1), kind (housenumber, street, locality or municipality), postcode and city. They come from the installation's own geocoder (GEOCODER_URL) or, when the installation has none and an administrator turned it on, IGN's public service. They are proposals: save one as a location, then review the entrance. Without address lookup the answer is 404 GEOCODER_DISABLED; when the service does not respond, 503 GEOCODER_UNAVAILABLE. GET /api/v1/configuration returns the data attribution in address_lookup and where addresses go in address_lookup_source (installation or public) when lookup is available, null otherwise. In a delivery CSV import preview, address_match marks rows whose coordinates were proposed from their address, location_source (FILE, ADDRESS, CUSTOMER, SAVED or NONE) says where each point comes from, warnings lists points to check without blocking the import, and address_lookup (UNAVAILABLE or INCOMPLETE) says why some rows were not looked up.

An import refuses a row whose identifier already exists with other values, unless it is sent with update_existing=true: the preview then marks the row UPDATE and names the changed fields in changes, and the commit applies it to the record's current version. For an existing delivery, a row without coordinates keeps the saved point and its review. A file whose expected header row is separated by semicolons, as spreadsheets set to French conventions save CSV, is answered 422 with one error on row 1, field headers, saying so instead of listing the expected headers.

POST /api/v1/orders/activation cancels ("active": false) or restores (true) several deliveries; POST /api/v1/orders/delete deletes them for good and returns their number in deleted. Both take orders, a list of {"id", "version"} at the version the user saw (412 otherwise). A cancelled delivery stays in the list and is not planned. Deletion is refused while a waiting or running calculation uses any of the deliveries (409 JOB_ACTIVE); saved plans keep their copy and then ask for a fresh calculation.

Records have an integer version. Send the expected version in If-Match, for example "3", when updating; a missing header returns 428 VERSION_REQUIRED. On conflict, read the record again and let the user reconcile changes rather than silently overwriting a colleague's work.

API quantities use the units named in their fields: kilograms, dm³, millimetres, seconds and euro cents. The interface converts some fields to metres, m³, minutes or euros; do not copy displayed values without conversion. Coordinates are latitude/longitude, with no altitude. Equipment is a list of codes: skills for a vehicle, required_skills for a delivery, a customer and a route sheet stop. The interface names TAIL_LIFT (Tail lift) and REFRIGERATED (Refrigerated body) and shows any other code as written; records, files, the API and the CSV export keep the codes.

Depots and places

GET /api/v1/depot and PUT /api/v1/depot read and update the main depot. GET /api/v1/depots lists every depot and place of the company, the main depot first. A place (kind PLACE), such as a driver's home, is only where a vehicle starts or ends its day. An administrator adds a depot or place with POST /api/v1/depots (its point arrives unreviewed), changes it with PUT /api/v1/depots/{key}, makes it the main depot with POST /api/v1/depots/{key}/main and deletes it with DELETE /api/v1/depots/{key}, each time with If-Match. A depot's code cannot change while a vehicle or delivery names it; the main depot, and a depot or place still named, cannot be deleted.

A vehicle names where it starts its day (start, a depot or place code; null for the main depot) and where it ends it (end; null for where it started). A delivery may name the depot it ships from (ships_from, a depot's code, never a place's): only a vehicle starting at that depot may carry it, a rule the independent validation also checks.

Customers

GET /api/v1/customers lists saved customers in code order; status (active, inactive, review or missing) and q (code, name or address) filter the whole list, and GET /api/v1/customers/summary returns the counts. POST /api/v1/customers creates a customer and PUT /api/v1/customers/{key} changes it with If-Match; deliveries already saved keep their own copy of its entrance. A delivery naming a customer (customer, its code) without a point of its own takes the customer's entrance with its review. POST /api/v1/orders/{key}/customer, with code, name and the delivery's version in If-Match, saves a delivery as a new customer, keeping its reviewed entrance, and links it.

Company settings and examples

First-use setup. GET /api/v1/setup says whether setup is required and enabled, and names the installation's own geocoder in address_lookup when there is one. While the installation is empty, GET /api/v1/setup/geocode?q=… looks up the depot's address with the private code in the X-Setup-Token header; public=true uses IGN's service when the installation has no geocoder. POST /api/v1/setup creates the company, its main depot (a reviewed point is required) and the first administrator in one transaction, then opens their session. The body holds setup_token, company_name, depot, display_name, username and password, and optionally public_address_lookup and language (fr or en, default en), the first administrator's interface language. The workspace_kind field no longer exists.

Address search. An administrator turns IGN's public service on or off for the company with PUT /api/v1/workspace/address-lookup and {"public_service": true} or false. Every looked-up address is then sent to IGN. An installation with its own geocoder always uses that one.

Safety margin. An administrator sets it with PUT /api/v1/workspace/safety-margin and {"minutes": 15}, a whole number from 0 to 120; it is 15 by default, and GET /api/v1/configuration returns it in safety_margin_minutes. New calculations plan every delivery to start at least that long before its window ends, never before it opens; validation still checks the real windows. A delivery that can only be reached in the last minutes of its window may stay unassigned. Each plan keeps the margin it was calculated with in input_data.safety_margin_seconds: 0 for a plan calculated before the setting existed.

Delivery numbers. GET /api/v1/workspace/numbering returns the delivery number format (format, start), the number a delivery saved today would receive (next) and a format that would continue the existing identifiers (suggestion). An administrator sets or clears it with PUT /api/v1/workspace/numbering. A format combines text, the date parts {YYYY}, {YY}, {MM} and {DD} of the service date and exactly one number {N} or {N:4} (four digits), for example LIV-{YYYY}-{N:4}. GET /api/v1/workspace/numbering/preview?format=…&start=… shows the number it would give before it is saved. GET /api/v1/orders/next-identifier?service_date=… proposes the next number, and POST /api/v1/orders?assign_identifier=true gives the final one when saving, under the company lock: two users never receive the same number. Without a format, that option returns 409 NUMBERING_OFF. With a format, a delivery CSV row with an empty external_id receives the next number; the preview marks it numbered.

Examples. POST /api/v1/examples with {"service_date": "YYYY-MM-DD"} (administrator) adds ten fictional Paris deliveries for that day, with an example depot and two example vehicles starting there if they do not exist yet. The server marks these records example: true; a client cannot set that field. A calculation takes only examples or only the company's own records (422 EXAMPLES_MIXED), and only examples name the example depot (EXAMPLE_PLACE). A plan of examples can be marked Ready: its route sheets carry example: true and every planning_notice of its CSV begins "Example data, not for driving." ("Données d’exemple, pas pour la route." in French). POST /api/v1/examples/remove (administrator) deletes every example, with the plans and calculations of example vehicles, and returns their number in removed; it is refused while a calculation is waiting or running (409 JOB_ACTIVE). GET /api/v1/configuration says in examples whether the company has any. First-use setup no longer creates a separate sample workspace.

Follow a calculation

Submitting optimization creates an asynchronous job. Retain its ID and read its status until SUCCEEDED or FAILED. Success means a result was saved, not approved. POST /api/v1/jobs/{key}/cancel cancels a job that is waiting or running. A company can have at most five jobs waiting or running; another submission returns 429 QUEUE_FULL with Retry-After. Selected vehicles that do not work on the planning day (a day off, or no hours on that weekday) are refused before a job is created (422 VEHICLE_OFF_DAY, with their fleet numbers in identifiers). Also refused: a selection mixing examples and the company's own records (422 EXAMPLES_MIXED); a start or end depot or place whose entrance is not reviewed (422 PLACE_NOT_REVIEWED, with their codes); a selection whose places, depots included, are farther apart than MATRIX_MAX_KM (422 AREA_TOO_WIDE, with the two farthest places — depot, another depot or place code, or delivery identifiers — in identifiers).

Operations requiring an Idempotency-Key must reuse the same key and body when retrying the same submission. Use a new key for a new request. Do not automatically resubmit optimization whenever a status refresh fails.

After changing stop sequences, wait for recalculation and independent validation. Approval and export check the current version and result eligibility again.

Handle errors

ResponseClient action
400Check request format and quoted version syntax.
428Supply the required If-Match or Idempotency-Key header.
401Request a new sign-in.
403Check origin, CSRF token and permissions.
404Check the identifier within the current company.
409 or 412Refresh state and reconcile the conflict.
413Reduce the request: CSV imports and request bodies are limited to 1 MiB (UPLOAD_TOO_LARGE, or REQUEST_TOO_LARGE from the proxy).
415Send a CSV import as text/csv (CSV_REQUIRED).
422Show invalid fields and retain entered values.
422 AREA_TOO_WIDESelect deliveries closer together; identifiers names the two farthest places.
429 LOGIN_THROTTLED or SIGNIN_RATE_LIMITEDWait before signing in again.
429 QUEUE_FULLCancel a calculation or wait for one to finish.
503Check the routing service (ACCESS_CHECK_FAILED) or the geocoder (GEOCODER_UNAVAILABLE), or wait for the application to respond (SERVICE_UNAVAILABLE from the proxy).
other 5xx or unreadable responsePreserve inputs and check whether the submission was recorded before repeating it.

Do not include passwords, cookies or private setup codes in diagnostic logs.

Domain errors contain code and detail, and identifiers when the message concerns named records. Field-validation errors contain a detail array with loc, msg, type, and a fixed English template with its values, so a client can translate the message. Middleware errors can contain only detail. Branch on status and code, not message text. Messages and what to do lists the codes the interface explains.

Example: save the signed-in user's language

This code runs in the application's same-origin browser context after sign-in. It reads the current session instead of embedding a token. Integrations must handle an expired session and keep credentials out of logs.

const sessionResponse = await fetch('/api/v1/session');
if (!sessionResponse.ok) throw new Error('Sign in again');
const session = await sessionResponse.json();
const response = await fetch('/api/v1/preferences', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'X-VRP-Request': '1',
    'X-CSRF-Token': session.csrf_token,
  },
  body: JSON.stringify({language: 'fr'}),
});
if (!response.ok) throw new Error('Language was not saved');
const identity = await response.json(); // identity.language === "fr"

Calculation request sequence

  1. Read GET /api/v1/orders?service_date=YYYY-MM-DD, GET /api/v1/vehicles and GET /api/v1/depot.
  2. Submit POST /api/v1/optimization-jobs with the exact request model below and an idempotency key.
  3. Poll GET /api/v1/jobs/{key}. Keep the same job after a transient connection failure; cancel it with POST /api/v1/jobs/{key}/cancel if it is no longer needed.
  4. When successful, retrieve the returned plan with GET /api/v1/plans/{key}.
  5. Review validation and unassigned work before POST /api/v1/plans/{key}/finalize. Partial acceptance must be explicit. If another Ready plan for the day dispatches some of the same vehicles or deliveries, the request returns 409 READY_CONFLICT unless it sets replace_conflicting_ready.
  6. Download GET /api/v1/plans/{key}/export.csv only for the current eligible Ready result, with its quoted version in If-Match. layout=standard (default) gives comma-separated UTF-8; layout=spreadsheet adds a byte-order mark, semicolons and a decimal comma in coordinates. The columns are listed in CSV formats; start_place and end_place give the codes of where each vehicle starts and ends.
  7. Read the drivers' route sheets with GET /api/v1/plans/{key}/sheets, under the same conditions as the export and with the same If-Match. The response holds one sheet per vehicle with deliveries (?vehicle= for one vehicle): where and when it leaves and ends, distance, driving time and load, then each delivery in driving order with its number, customer, address, reviewed entrance, window, arrival, service start and departure, load, equipment and instructions. example: true marks a plan of examples.

GET /api/v1/system-status reports the worker, routing service and queue for a signed-in user. GET /api/v1/metrics is for monitoring only: it needs Authorization: Bearer with the installation's MONITOR_TOKEN and exists only when that setting is configured.

Find an operation

Choose an area, then an operation. Only that operation’s parameters and models are displayed. Open a response to inspect its fields; the full contract remains downloadable.

Sign in, store personal language and manage authorized accounts. Creating and managing other accounts requires an administrator.

GET/api/v1/session

Responses

200Successful Response
SessionRecord · application/json
FieldType and rule
identityRequiredIdentity
Inspect fields
FieldType and rule
organization_idRequiredstring
user_idRequiredstring | null
usernameRequiredstring
display_nameRequiredstring
roleRequiredadmin | planner
languageOptionalen | fr
csrf_tokenRequiredstring | null
expires_atRequiredstring | null
401Unauthorized
SourceProblem · application/json
FieldType and rule
codeRequiredstring
detailRequiredstring
identifiersOptionalstring[] | null
403Forbidden
SourceProblem · application/json
FieldType and rule
codeRequiredstring
detailRequiredstring
identifiersOptionalstring[] | null
404Not Found
SourceProblem · application/json
FieldType and rule
codeRequiredstring
detailRequiredstring
identifiersOptionalstring[] | null
409Conflict
SourceProblem · application/json
FieldType and rule
codeRequiredstring
detailRequiredstring
identifiersOptionalstring[] | null
429Too Many Requests
SourceProblem · application/json
FieldType and rule
codeRequiredstring
detailRequiredstring
identifiersOptionalstring[] | null

This view reads the documented version’s contract. JSON keys remain identical in both languages. Download OpenAPI

On this page