Tolls API
ES EN FR

Tolls API Guides

Everything you need to integrate toll calculation via API or MCP: authentication, credit billing, errors and limits.

Updated

⚡ Quickstart

  1. Create an account on the dashboard (dash.tolls.cargoffer.com).
  2. Top up credits (or redeem a coupon) in the Wallet section.
  3. Create an API key (starts with tk_live_; shown only once).
  4. Call POST /api/tolls/calculate (1 credit per calculation).
# Set TOLLS_API_KEY to your API key; persist a unique key for each calculation.
export TOLLS_API_KEY='YOUR_TOLLS_API_KEY'
curl -X POST https://api.tolls.cargoffer.com/api/tolls/calculate \
  -H "Authorization: Bearer $TOLLS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: route-estimate-001" \
  -d '{
    "origin": {"lat": 40.4168, "lng": -3.7038},
    "destination": {"lat": 41.3874, "lng": 2.1686},
    "vehicleType": "pesado_1"
  }'

Credits are only charged when a calculation is served. If the engine fails, the credit is refunded automatically. A processed key returns 409 and is not charged again, but the API does not replay the original response. Store the response with the key and use a new one for each new calculation.

🔑 Authentication

Authenticate each request with Authorization: Bearer <token>. Use an API key or the dashboard session JWT.

MethodUse
tk_live_… (API key)Agents, MCP, server-to-server. Up to 10 keys per account, revocable from the dashboard.
JWT de sesiónThe dashboard. POST /api/auth/login → accessToken (2 h) + refreshToken (30 d).

Verify any token against GET /api/auth/validate: it returns subject, type and scopes. MCP: clients can use the official server at https://mcp.tolls.cargoffer.com/mcp (streamable HTTP, Bearer required on every request).

🧭 Errors

HTTPMeaningWhat to do
400Invalid request (coordinates, vehicleType)Fix the body: origin/destination are {lat,lng} objects; vehicleType ligero|pesado_1|pesado_2
401Missing or invalid tokenRegenerate the API key or refresh the JWT
402Insufficient balanceTop up credits or redeem a coupon; the engine was not called
404Resource or coupon not foundCheck the code/path
409Idempotency-Key or coupon already usedGenerate a new key for a new calculation
429Rate limit exceeded (demo)Wait and retry with backoff
502Toll engine unavailableRetry; the credit was already refunded
503Wallet temporarily unavailableRetry in a few seconds (fail-closed)

⏱️ Limits & demo

  • Separate limits per authentication method, so an anonymous flood cannot block paying clients: API key 200 req/min per key, dashboard session 300 req/min per IP, unauthenticated 50 req/min per IP. When exceeded: 429 with a Retry-After header.
  • Demo POST /api/demo/calculate: 5 req/min per IP and a shared 500 calculations/day pool (429 when exhausted). No auth, no cost.
  • Up to 10 active API keys per account; revoking one frees a slot. GET /api/keys/limits publishes the current cap.
  • Each served calculation consumes 1 credit and is logged in GET /api/history.

🎟️ Coupons

A coupon credits the wallet directly, with no payment: POST /api/wallet/redeem with {"code":"…"}. Each account can redeem each coupon once (409 on repeat); coupons may be limited to one email or N total redemptions.

🧩 Odoo

For Odoo, make the request from a custom module or Python worker authorized for HTTP calls. Keep one Idempotency-Key per logical calculation and reuse it only when retrying that operation:

Server Action context and variables depend on the Odoo version and configuration. This is a generic Python integration pattern; check the official documentation before running code inside Odoo: official Server Actions documentation (Odoo 19)

import os
import requests

# Load a persisted UUID for this calculation; reuse it only for retries.
calculation_id = "persisted-uuid-for-this-calculation"
response = requests.post(
    "https://api.tolls.cargoffer.com/api/tolls/calculate",
    headers={
        "Authorization": f"Bearer {os.environ['TOLLS_API_KEY']}",
        "Content-Type": "application/json",
        "Idempotency-Key": f"odoo-toll-{calculation_id}",
    },
    json={
        "origin": {"lat": 40.4168, "lng": -3.7038},
        "destination": {"lat": 41.3874, "lng": 2.1686},
        "vehicleType": "pesado_1",
    },
    timeout=30,
)
response.raise_for_status()
tolls = response.json()

Node.js example (18+):

const apiKey = process.env.TOLLS_API_KEY;
const idempotencyKey = process.env.TOLLS_IDEMPOTENCY_KEY; // Persist per logical calculation.
const response = await fetch('https://api.tolls.cargoffer.com/api/tolls/calculate', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': idempotencyKey,
  },
  body: JSON.stringify({
    origin: { lat: 40.4168, lng: -3.7038 },
    destination: { lat: 41.3874, lng: 2.1686 },
    vehicleType: 'pesado_1',
  }),
});
if (!response.ok) throw new Error(`Tolls API HTTP ${response.status}`);
const tolls = await response.json();

A processed key returns 409: it is not charged again, but the original response is not replayed. Store the response with the key and use a new key for a new calculation.

📜 Changelog

DateChange
2026-10-29Wallet coupons (/api/wallet/coupons, /api/wallet/redeem), GET /api/auth/validate, idempotent ledger and Idempotency-Key header on /api/tolls/calculate (409 on reuse).
2026-09-04v1: auth, API keys, wallet + Stripe, calculation proxy, history, public demo.

Frequently asked questions

How many credits does a calculation use?

Each served calculation consumes 1 credit. If the engine fails or rejects the request, the API refunds the credit.

What happens when an Idempotency-Key is reused?

The API returns 409 without a second charge or engine call. It does not replay the original response; store the response with its key.

What coordinate format should I send?

Send origin and destination as objects with decimal latitude and longitude: {lat, lng}. Send the vehicle class in vehicleType.