REST API · v1

Routella API for developers

Connect your own website or store to Routella for a full two-way integration. Push orders straight into dispatch, read their live status, and receive a webhook on every step of the delivery — created, picked, dispatched, delivered, and more.

Server-to-server Bearer key auth Delivery webhooks
Base URLhttps://routella.appAll endpoints are relative to this host and return JSON.
QuickstartWays to connectGet your API keyAuthenticationCreate an orderList ordersGet an orderOrder objectWebhooksErrors

Quickstart

New here? These five steps take you from zero to a live order with delivery updates flowing back to your own server.

  1. 1Create a Routella accountSign up and open the dashboard. The API and webhooks are free on every plan.
  2. 2Generate an API keyGo to Settings → Integrations → API access and select Generate API key. Copy it — the full key is shown only once.
  3. 3Send your first orderFrom your server, POST an order to /api/v1/orders with the key in the Authorization header. Only customerName and address are required.
  4. 4See it in your dashboardThe order appears in Routella right away, ready to drop into a delivery round.
  5. 5Receive events backAdd your server URL under Settings → Integrations → Webhooks and tick the events you care about. Routella POSTs you as the delivery moves.
Your first order — cURL
curl -X POST https://routella.app/api/v1/orders \
  -H "Authorization: Bearer rtl_3f9c1a7b2e8d4056af1c9b3e7d2a6f08" \
  -H "Content-Type: application/json" \
  -d '{ "customerName": "Jane Doe", "address": "221B Baker Street", "city": "London" }'

Every endpoint and every webhook is free on all plans — building a full integration costs nothing.

Three ways to connect

Routella connects to your systems in three directions. Most stores combine the first two — push orders in, receive webhooks back — for a complete two-way integration.

You → Routella

Push orders in

Your website or backend calls Routella whenever a customer places an order. You decide exactly when an order enters Routella. Uses your API key and POST /api/v1/orders.

Routella → You

Receive webhooks back

Routella calls your server on every delivery event — created, picked, dispatched, delivered, and more — so your own system stays in sync without polling. Set it up under Settings → Integrations → Webhooks.

Routella ← You

Let Routella pull (Custom API)

Already expose an orders endpoint? Add a Custom API integration in the dashboard. Routella reads orders from your own server on a schedule, with field mapping for any JSON shape — no pushing required.

Get your API key

Every API request is authenticated with a secret key tied to your Routella account. You generate one inside the dashboard.

  1. Open the dashboard and go to Settings → API.
  2. Select Generate API key. The key looks like rtl_3f9c1a7b2e8d4056af1c9b3e7d2a6f08 the prefix rtl_ followed by a hex string.
  3. Copy the key right away. The full key is shown only once. If you lose it, generate a new one and update your integration.
Keep the key on your server. Treat it like a password. Never put it in browser code, mobile apps, or any place a customer could see it. Anyone with the key can create and read your orders.

Authentication

Send your API key on every request in the Authorization header as a Bearer token:

Header
Authorization: Bearer rtl_3f9c1a7b2e8d4056af1c9b3e7d2a6f08

A request with a missing, malformed, or invalid key is rejected with 401 unauthorized. Because the key is a secret, all calls must be made from your server — never from a browser.

Create an order

Push a new order into Routella. The order shows up in your dashboard ready to be added to a delivery round. Routella geocodes the address automatically unless you pass coordinates yourself.

POST/api/v1/orders
Request body fields
FieldTypeRequirementDescription
customerNamestringRequiredName of the person receiving the order.
addressstringRequiredStreet address. Routella geocodes this if no coordinates are supplied.
citystringOptionalCity or town.
phonestringOptionalCustomer phone number. Recommended so the driver and notifications can reach them.
emailstringOptionalCustomer email address.
itemsarrayOptionalLine items. Each item is an object with title, quantity, price, and sku.
totalnumberOptionalOrder total.
currencystringOptionalISO currency code, for example GBP, USD, or EUR.
notestringOptionalFree-text delivery note shown to the driver.
paymentMethodstringOptionalOne of online, cash, card, check, transfer. Use cash for cash-on-delivery.
codAmountnumberOptionalAmount the driver must collect on delivery (cash-on-delivery).
countrystringOptionalCountry name.
countryCodestringOptionalTwo-letter country code.
stopTypestringOptionaldelivery or pickup. Defaults to delivery.
latitudenumberOptionalOptional. Skip geocoding by supplying coordinates directly.
longitudenumberOptionalOptional. Supply together with latitude.
packageCountnumberOptionalNumber of packages in the order.
deliveryMethodstringOptionalDelivery method label for the order.
discountAmountnumberOptionalDiscount applied to the order.
Example request — cURL
curl -X POST https://routella.app/api/v1/orders \
  -H "Authorization: Bearer rtl_3f9c1a7b2e8d4056af1c9b3e7d2a6f08" \
  -H "Content-Type: application/json" \
  -d '{
    "customerName": "Jane Doe",
    "address": "221B Baker Street",
    "city": "London",
    "phone": "+447700900123",
    "email": "jane@example.com",
    "items": [
      { "title": "Wireless Mouse", "quantity": 1, "price": 24.99, "sku": "WM-001" }
    ],
    "total": 24.99,
    "currency": "GBP",
    "paymentMethod": "cash",
    "codAmount": 24.99,
    "stopType": "delivery",
    "note": "Leave with the concierge"
  }'
Example request — Node.js fetch
const res = await fetch("https://routella.app/api/v1/orders", {
  method: "POST",
  headers: {
    "Authorization": "Bearer rtl_3f9c1a7b2e8d4056af1c9b3e7d2a6f08",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    customerName: "Jane Doe",
    address: "221B Baker Street",
    city: "London",
    phone: "+447700900123",
    items: [
      { title: "Wireless Mouse", quantity: 1, price: 24.99, sku: "WM-001" },
    ],
    total: 24.99,
    currency: "GBP",
    paymentMethod: "cash",
    codAmount: 24.99,
    stopType: "delivery",
  }),
});

if (!res.ok) {
  const err = await res.json();
  throw new Error(`Routella API error: ${err.error}`);
}

const { order } = await res.json();
console.log("Created order", order.id, order.orderNumber);
Example response
HTTP/1.1 201 Created
Content-Type: application/json

{
  "order": {
    "id": "6711e2a4c9d3f80012ab34cd",
    "orderNumber": "M-0042",
    "status": "unfulfilled",
    "customerName": "Jane Doe",
    "phone": "+447700900123",
    "email": "jane@example.com",
    "address": {
      "line1": "221B Baker Street",
      "city": "London",
      "full": "221B Baker Street, London",
      "country": "United Kingdom",
      "latitude": 51.523767,
      "longitude": -0.1585557
    },
    "items": [
      {
        "title": "Wireless Mouse",
        "quantity": 1,
        "price": 24.99,
        "sku": "WM-001",
        "variantTitle": ""
      }
    ],
    "total": 24.99,
    "currency": "GBP",
    "note": "Leave with the concierge",
    "paymentMethod": "cash",
    "codAmount": 24.99,
    "stopType": "delivery",
    "delivery": {
      "status": "unfulfilled",
      "driverName": null,
      "roundId": null
    },
    "createdAt": "2026-05-19T09:14:22.000Z",
    "updatedAt": "2026-05-19T09:14:22.000Z"
  }
}
On success the API returns HTTP 201 with the created order. A free plan that has hit its monthly order cap returns 402 quota_exceeded — see the Errors section below.

List orders

Return your orders, newest first. Use the query parameters to page through results and filter by fulfillment status.

GET/api/v1/orders
Query parameters
ParameterTypeDescription
limitnumberHow many orders to return. 1 to 100. Defaults to 50.
statusstringFilter by order status: unfulfilled, fulfilled, or partial.
Example request — cURL
curl -X GET "https://routella.app/api/v1/orders?status=unfulfilled&limit=25" \
  -H "Authorization: Bearer rtl_3f9c1a7b2e8d4056af1c9b3e7d2a6f08"
Example response
HTTP/1.1 200 OK
Content-Type: application/json

{
  "orders": [
    {
      "id": "6711e2a4c9d3f80012ab34cd",
      "orderNumber": "M-0042",
      "status": "unfulfilled",
      "customerName": "Jane Doe",
      "phone": "+447700900123",
      "address": {
        "line1": "221B Baker Street",
        "city": "London",
        "full": "221B Baker Street, London",
        "country": "United Kingdom",
        "latitude": 51.523767,
        "longitude": -0.1585557
      },
      "total": 24.99,
      "currency": "GBP",
      "paymentMethod": "cash",
      "codAmount": 24.99,
      "stopType": "delivery",
      "delivery": { "status": "unfulfilled", "driverName": null, "roundId": null },
      "createdAt": "2026-05-19T09:14:22.000Z",
      "updatedAt": "2026-05-19T09:14:22.000Z"
    }
  ],
  "count": 1
}

Get an order

Fetch a single order by its id, including its current delivery status — the assigned driver and round, if any.

GET/api/v1/orders/{id}
Example request — cURL
curl -X GET https://routella.app/api/v1/orders/6711e2a4c9d3f80012ab34cd \
  -H "Authorization: Bearer rtl_3f9c1a7b2e8d4056af1c9b3e7d2a6f08"
Example response
HTTP/1.1 200 OK
Content-Type: application/json

{
  "order": {
    "id": "6711e2a4c9d3f80012ab34cd",
    "orderNumber": "M-0042",
    "status": "partial",
    "customerName": "Jane Doe",
    "phone": "+447700900123",
    "email": "jane@example.com",
    "address": {
      "line1": "221B Baker Street",
      "city": "London",
      "full": "221B Baker Street, London",
      "country": "United Kingdom",
      "latitude": 51.523767,
      "longitude": -0.1585557
    },
    "items": [
      { "title": "Wireless Mouse", "quantity": 1, "price": 24.99, "sku": "WM-001", "variantTitle": "" }
    ],
    "total": 24.99,
    "currency": "GBP",
    "note": "Leave with the concierge",
    "paymentMethod": "cash",
    "codAmount": 24.99,
    "stopType": "delivery",
    "delivery": {
      "status": "partial",
      "driverName": "Marcus Lane",
      "roundId": "6711f0b8c9d3f80012ab35ef"
    },
    "createdAt": "2026-05-19T09:14:22.000Z",
    "updatedAt": "2026-05-19T11:02:47.000Z"
  }
}
If no order with that id belongs to your account, the API returns 404 not_found.

The order object

All three order endpoints return the same order shape. The status field is one of unfulfilled, fulfilled, or partial. The delivery object reflects the current dispatch state — driver and round.

{
  "id": "string",
  "orderNumber": "string",
  "status": "unfulfilled | fulfilled | partial",
  "customerName": "string",
  "phone": "string",
  "email": "string",
  "address": {
    "line1": "string",
    "city": "string",
    "full": "string",
    "country": "string",
    "latitude": "number",
    "longitude": "number"
  },
  "items": [
    {
      "title": "string",
      "quantity": "number",
      "price": "number",
      "sku": "string",
      "variantTitle": "string"
    }
  ],
  "total": "number",
  "currency": "string",
  "note": "string",
  "paymentMethod": "online | cash | card | check | transfer",
  "codAmount": "number",
  "stopType": "delivery | pickup",
  "delivery": {
    "status": "unfulfilled | fulfilled | partial",
    "driverName": "string",
    "roundId": "string"
  },
  "createdAt": "ISO 8601 string",
  "updatedAt": "ISO 8601 string"
}

Webhooks

Instead of polling, let Routella tell you when something happens. Open Settings → Integrations → Webhooks, add your server URL, and tick the events you want. Routella then sends an HTTP POST to that URL every time one of those events occurs. The picker in Settings lists exactly the events below.

The envelope

Every webhook POST has the same three top-level fields: event, timestamp (ISO 8601), and a data object whose contents depend on the event.

POST https://your-server.com/webhooks/routella
Content-Type: application/json
X-Webhook-Signature: 9a1f3c0b7e2d...   (HMAC-SHA256 hex — only when a secret is set)

{
  "event": "order.delivered",
  "timestamp": "2026-05-19T13:48:10.000Z",
  "data": { ... }
}
Events

The Payload column says which data shape the event ships — see the two reference tables below.

EventPayloadWhen it fires
order.createdorderA new order was created — for example through POST /api/v1/orders.
order.assignedorderThe order was added to a delivery round.
order.collectedorderThe order’s items were collected from the warehouse.
order.pickedorderThe order was picked up — by the driver, or handed to the delivery company.
order.dispatchedorderThe order is out for delivery and on its way to the customer.
order.arrivingorder + etaThe driver is approaching the customer. The payload carries an estimated arrival time.
order.deliveredorderThe order was delivered to the customer.
order.missedorderA delivery attempt failed — no answer, wrong address, refused, and so on.
order.feedback_requestedorderA review request was sent to the customer after delivery.
round.createdroundA delivery round was created.
round.completedroundEvery stop in a delivery round reached a final state — the round is finished.
Order event payload

Every order.* event carries this exact data object. The eta field is present only on order.arriving.

FieldTypeDescription
idstringRoutella order id. Set for orders created through the API; an empty string for events raised from a delivery round, where orderNumber identifies the order.
orderNumberstringThe order number, for example M-0042.
customerNamestringName of the person receiving the order.
phonestringCustomer phone number, or an empty string.
emailstringCustomer email address, or an empty string.
addressobjectDelivery address, or null. Holds line1, city, full, country, latitude, and longitude. Some fields may be empty depending on where the order came from.
driverstringName of the assigned driver, or an empty string if no driver is assigned yet.
roundstringId of the delivery round, or an empty string if the order is not in a round yet.
etastringHuman-readable estimated arrival, for example "16:47 (12 minutes)". Included only on order.arriving.
data — order events
{
  "id": "6711e2a4c9d3f80012ab34cd",
  "orderNumber": "M-0042",
  "customerName": "Jane Doe",
  "phone": "+447700900123",
  "email": "jane@example.com",
  "address": {
    "line1": "221B Baker Street",
    "city": "London",
    "full": "221B Baker Street, London",
    "country": "United Kingdom",
    "latitude": 51.523767,
    "longitude": -0.1585557
  },
  "driver": "Marcus Lane",
  "round": "1747645200000"
}
Round event payload

The two round.* events carry a short round summary instead of an order.

FieldTypeDescription
roundstringId of the delivery round.
driverstringName of the round’s driver, or an empty string.
stopsnumberNumber of stops (orders) in the round.
data — round events
{
  "round": "1747645200000",
  "driver": "Marcus Lane",
  "stops": 5
}
Example — order.created
{
  "event": "order.created",
  "timestamp": "2026-05-19T09:14:22.000Z",
  "data": {
    "id": "6711e2a4c9d3f80012ab34cd",
    "orderNumber": "M-0042",
    "customerName": "Jane Doe",
    "phone": "+447700900123",
    "email": "jane@example.com",
    "address": {
      "line1": "221B Baker Street",
      "city": "London",
      "full": "221B Baker Street, London",
      "country": "United Kingdom",
      "latitude": 51.523767,
      "longitude": -0.1585557
    },
    "driver": "",
    "round": ""
  }
}
Example — order.arriving (carries an ETA)
{
  "event": "order.arriving",
  "timestamp": "2026-05-19T13:31:50.000Z",
  "data": {
    "id": "",
    "orderNumber": "M-0042",
    "customerName": "Jane Doe",
    "phone": "+447700900123",
    "email": "jane@example.com",
    "address": {
      "line1": "",
      "city": "London",
      "full": "221B Baker Street, London",
      "country": "",
      "latitude": null,
      "longitude": null
    },
    "driver": "Marcus Lane",
    "round": "1747645200000",
    "eta": "16:47 (12 minutes)"
  }
}
Example — round.completed
{
  "event": "round.completed",
  "timestamp": "2026-05-19T15:02:00.000Z",
  "data": {
    "round": "1747645200000",
    "driver": "Marcus Lane",
    "stops": 5
  }
}
Routella delivers each webhook as soon as the event happens, and retries transient failures — timeouts, 429, and 5xx responses. A brief blip is retried within seconds; if your endpoint stays unreachable, Routella keeps retrying with backoff for up to 24 hours before dropping the event. Treat your handler as idempotent anyway: return a 2xx quickly and key off orderNumber or round so a repeated delivery is harmless.
Verifying the signature

If you set a webhook secret in Settings, Routella signs each request. The X-Webhook-Signature header holds an HMAC-SHA256 hex digest of the raw request body, keyed with your secret. Recompute it and compare to confirm the request really came from Routella.

// Node.js — verify the X-Webhook-Signature header.
// Use the RAW request body, not a re-serialized JSON object.
const crypto = require("crypto");

function verifyRoutellaWebhook(rawBody, signatureHeader, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody, "utf8")
    .digest("hex");

  // Constant-time compare to avoid timing attacks.
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(signatureHeader || "", "hex");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Express example (raw body required):
// app.post("/webhooks/routella",
//   express.raw({ type: "application/json" }),
//   (req, res) => {
//     const ok = verifyRoutellaWebhook(
//       req.body,                       // Buffer — the raw body
//       req.get("X-Webhook-Signature"),
//       process.env.ROUTELLA_WEBHOOK_SECRET
//     );
//     if (!ok) return res.status(401).send("bad signature");
//     const event = JSON.parse(req.body.toString("utf8"));
//     // ... handle event.event / event.data ...
//     res.sendStatus(200);
//   });

Errors

Errors come back with the matching HTTP status code and a JSON body containing an error string. Validation errors also include a fields object naming each problem.

401unauthorized

The API key is missing, malformed, or invalid. Check the Authorization header.

HTTP/1.1 401 Unauthorized

{ "error": "unauthorized" }
400validation_failed

One or more body fields are missing or invalid. The fields object names each problem.

HTTP/1.1 400 Bad Request

{
  "error": "validation_failed",
  "fields": {
    "customerName": "required",
    "address": "required"
  }
}
402quota_exceeded

The free-plan monthly order cap was reached. Upgrade the plan to keep creating orders.

HTTP/1.1 402 Payment Required

{ "error": "quota_exceeded" }
404not_found

No order with that id exists for this merchant.

HTTP/1.1 404 Not Found

{ "error": "not_found" }
Built for your backend

The Routella API is server-to-server

Call it from your own backend, never from a browser or mobile app. Your API key is a secret — keep it server-side and rotate it from Settings → API if it is ever exposed.

View pricing