Everything the dashboard does, your code can do too. Create the day’s plan, push delivery points from your ERP or shop, assign couriers with their shifts and capacity, run the optimization and read back routes with per-stop arrival times.
The EfiRoute REST API is included on the Pro ($15/mo) and Business ($49/mo) plans — no enterprise contract or sales call. Authenticate with a company API key you create yourself in the dashboard, call 24 endpoints under https://api.efiroute.com/api/v1, and subscribe to HMAC-signed webhooks instead of polling. The OpenAPI specification is public.
Keys belong to the company, not to a person, so an integration keeps working when staff change. The raw key is shown once — EfiRoute stores only a hash of it.
Five calls from empty project to optimized routes. Only the last one costs anything — reading is always free.
curl -H "Authorization: Bearer efr_live_…" \
https://api.efiroute.com/api/v1/accountcurl -X POST https://api.efiroute.com/api/v1/projects \
-H "Authorization: Bearer efr_live_…" \
-H "Content-Type: application/json" \
-d '{"name":"Monday deliveries","defaultStartTime":"08:00"}'curl -X POST https://api.efiroute.com/api/v1/projects/$PID/stops \
-H "Authorization: Bearer efr_live_…" \
-H "Content-Type: application/json" \
-d '{"stops":[
{"name":"Acme Ltd","address":"Bahnhofstrasse 1, 8001 Zurich","serviceMinutes":"15 min"},
{"name":"Beta AG","address":"Langstrasse 20, 8004 Zurich","serviceMinutes":"1:30"}
]}'
# Required: the optimizer skips stops without coordinates
curl -X POST https://api.efiroute.com/api/v1/projects/$PID/stops/geocode \
-H "Authorization: Bearer efr_live_…"curl -X POST https://api.efiroute.com/api/v1/projects/$PID/couriers \
-H "Authorization: Bearer efr_live_…" \
-H "Content-Type: application/json" \
-d '{"courierId":"…","startAddress":"Depot, 8005 Zurich",
"endAddress":"Depot, 8005 Zurich",
"workStartTime":"08:00","workEndTime":"17:00",
"stopDurationMinutes":10,
"loadLimits":{"weight":800,"count":40}}'curl -X POST https://api.efiroute.com/api/v1/projects/$PID/optimize \
-H "Authorization: Bearer efr_live_…" \
-H "Content-Type: application/json" -d '{}'
# 200 -> routes in the response
# 202 -> { "jobId": "…" }, poll /optimization-jobs/{jobId}
# or subscribe to the optimization.completed webhookFull request and response schemas, including every field and error code, live in the OpenAPI document. Import it into Postman or generate a typed client with openapi-generator.
https://api.efiroute.com/api/v1
| Method | Endpoint | What it does | Scope |
|---|---|---|---|
| GET | /account | Plan, credit balance, plan limits and this month’s usage | read |
| GET | /projects | List projects, filter by status or name | read |
| POST | /projects | Create a project (one day’s plan) | write |
| PATCH | /projects/{id} | Rename, change defaults, or move the lifecycle status | write |
| DELETE | /projects/{id} | Delete a project — only while it is still a draft | write |
| GET | /projects/{id}/stops | List the delivery points of a project | read |
| POST | /projects/{id}/stops | Add one delivery point, or up to 1000 in a single call | write |
| PATCH | /projects/{id}/stops/{stopId} | Update address, time window, service time, priority… | write |
| DELETE | /projects/{id}/stops/{stopId} | Remove a delivery point | write |
| POST | /projects/{id}/stops/geocode | Resolve addresses to coordinates — required before optimizing | write |
| GET | /couriers | The company driver pool | read |
| POST | /couriers | Add a driver to the company | write |
| GET | /projects/{id}/couriers | Courier assignments on a project, with shift and capacity | read |
| POST | /projects/{id}/couriers | Assign a courier: depots, shift hours, breaks, load limits | write |
| PATCH | /projects/{id}/couriers/{assignmentId} | Change a courier’s shift, depots or capacity on this project | write |
| DELETE | /projects/{id}/couriers/{assignmentId} | Remove a courier from the project | write |
| POST | /projects/{id}/optimize | Run the optimization — billed per courier | optimize |
| GET | /projects/{id}/routes | Read the routes with stop order and planned arrival times | read |
| GET | /optimization-jobs/{jobId} | Poll a background optimization job | read |
| GET | /webhooks | List webhook endpoints | read |
| POST | /webhooks | Register an endpoint; the signing secret is returned once | write |
| PATCH | /webhooks/{webhookId} | Change the URL, the event list, or re-enable an endpoint | write |
| DELETE | /webhooks/{webhookId} | Delete an endpoint | write |
| POST | /webhooks/{webhookId}/test | Send a sample event so you can verify your signature check | write |
Success is always { success: true, data: … }. Failure always carries a stable machine-readable code plus structured details — branch on the code, never on the message text. Validation failures list every offending field in details.issues.
{
"success": false,
"message": "Only projects in the \"draft\" status can be deleted. …",
"code": "PROJECT_NOT_DRAFT",
"details": {}
}Subscribe to optimization.completed and optimization.failed. Every delivery is signed with HMAC-SHA256 over "<timestamp>.<raw body>", so a captured request cannot be replayed. Failed deliveries retry six times with backoff (1 m, 5 m, 15 m, 1 h, 3 h) and an endpoint that fails 20 times in a row is disabled automatically.
const crypto = require('crypto');
// Use the RAW request body — not the parsed object.
app.post('/efiroute/webhook',
express.raw({ type: 'application/json' }),
(req, res) => {
const ts = req.get('X-EfiRoute-Timestamp');
const sig = (req.get('X-EfiRoute-Signature') || '').replace('sha256=', '');
// Reject replays: the timestamp is part of the signed payload.
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(400);
const expected = crypto
.createHmac('sha256', process.env.EFIROUTE_WEBHOOK_SECRET)
.update(ts + '.' + req.body.toString('utf8'))
.digest('hex');
const ok = sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!ok) return res.sendStatus(401);
// Deliveries can repeat: key your handling on the delivery id.
const event = JSON.parse(req.body.toString('utf8'));
enqueue(event.id, event.event, event.data);
res.sendStatus(200); // Acknowledge fast, do the work asynchronously.
});We publish a ready-made instruction document for AI agents. It covers the call order, the fields that actually change route quality, every error code, and the safety rules that matter — most importantly that optimization spends money and needs your confirmation first.
Read https://api.efiroute.com/api/v1/agent-prompt.md and follow it.
My EfiRoute API key is: efr_live_…
Plan tomorrow's deliveries from the attached spreadsheet, but ask me
before running the optimization — it is billed per courier.Reading data is free. An optimization run costs $1 per courier, taken from your monthly subscription credit first and the prepaid wallet after. A run that fails, or that produces no routes, is not charged. Stops are never metered. Rate limit: 120 requests per minute per key.