EfiRoute

EfiRoute Developer API

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.

Included on Pro and Business. On the Free plan the API returns 402 API_REQUIRES_PAID_PLAN.

API keys

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.

  1. Sign in and open Settings → API.
  2. Create a key, pick the scopes it needs: read, write, optimize.
  3. Copy it immediately and store it as a secret in your own system.
  4. Send it on every request: Authorization: Bearer efr_live_…
  5. Revoke it any time; revoking does not delete the audit trail.

Quick start

Five calls from empty project to optimized routes. Only the last one costs anything — reading is always free.

1 · Check the balance
curl -H "Authorization: Bearer efr_live_…" \
  https://api.efiroute.com/api/v1/account
2 · Create the day’s plan
curl -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"}'
3 · Upload stops, then geocode them
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_…"
4 · Assign a courier — shift and capacity live here
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}}'
5 · Optimize — this is the billed call
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 webhook

Endpoint reference

Full 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

MethodEndpointWhat it doesScope
GET/accountPlan, credit balance, plan limits and this month’s usageread
GET/projectsList projects, filter by status or nameread
POST/projectsCreate a project (one day’s plan)write
PATCH/projects/{id}Rename, change defaults, or move the lifecycle statuswrite
DELETE/projects/{id}Delete a project — only while it is still a draftwrite
GET/projects/{id}/stopsList the delivery points of a projectread
POST/projects/{id}/stopsAdd one delivery point, or up to 1000 in a single callwrite
PATCH/projects/{id}/stops/{stopId}Update address, time window, service time, priority…write
DELETE/projects/{id}/stops/{stopId}Remove a delivery pointwrite
POST/projects/{id}/stops/geocodeResolve addresses to coordinates — required before optimizingwrite
GET/couriersThe company driver poolread
POST/couriersAdd a driver to the companywrite
GET/projects/{id}/couriersCourier assignments on a project, with shift and capacityread
POST/projects/{id}/couriersAssign a courier: depots, shift hours, breaks, load limitswrite
PATCH/projects/{id}/couriers/{assignmentId}Change a courier’s shift, depots or capacity on this projectwrite
DELETE/projects/{id}/couriers/{assignmentId}Remove a courier from the projectwrite
POST/projects/{id}/optimizeRun the optimization — billed per courieroptimize
GET/projects/{id}/routesRead the routes with stop order and planned arrival timesread
GET/optimization-jobs/{jobId}Poll a background optimization jobread
GET/webhooksList webhook endpointsread
POST/webhooksRegister an endpoint; the signing secret is returned oncewrite
PATCH/webhooks/{webhookId}Change the URL, the event list, or re-enable an endpointwrite
DELETE/webhooks/{webhookId}Delete an endpointwrite
POST/webhooks/{webhookId}/testSend a sample event so you can verify your signature checkwrite

Errors are predictable

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": {}
}

Webhooks instead of polling

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.
  });

Let an AI agent do the integration

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.

  • Works with Claude, ChatGPT, Custom GPTs, MCP servers, LangChain — anything that can read a URL.
  • Paste the snippet below into your agent together with your API key.
  • The agent reads the prompt, then the OpenAPI spec if it needs exact field names.
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.

What it costs

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.

Ready to build?

Start free, upgrade to Pro when you want API access. No per-driver seat fees, ever.

Create a free account