API Endpoints

Complete reference for all r4m.ai REST API endpoints. All requests require a Bearer token in the Authorization header.

Optimizations

POST/api/v1/optimizations
Try it →

Create a new route optimization. Returns 202 Accepted immediately; the optimization is processed asynchronously. A stop sent without lat/lng is geocoded as part of the request (charged at the geocode price) and must resolve to one precise place: pass geocoding.region or geocoding.bounds to rule out homonyms, or the request is refused with 422 GEOCODING_UNRESOLVED and the candidates.

Request body

{
  "addresses": [
    {
      "address": "Depot — 25 Kent Ave, Brooklyn, NY",
      "lat": 40.7218,
      "lng": -73.9615,
      "isDepot": true,
      "serviceTimeSeconds": 900
    },
    {
      "address": "1155 Manhattan Ave, Brooklyn, NY",
      "lat": 40.7304,
      "lng": -73.954,
      "serviceTimeSeconds": 600,
      "timeWindowStart": "09:00",
      "timeWindowEnd": "10:30",
      "priority": 1
    },
    {
      "address": "238 Bedford Ave, Brooklyn, NY",
      "lat": 40.7135,
      "lng": -73.967,
      "serviceTimeSeconds": 900
    },
    {
      "address": "1 Front St, Brooklyn, NY",
      "lat": 40.7033,
      "lng": -73.9881,
      "serviceTimeSeconds": 600,
      "priority": 2
    },
    {
      "address": "445 Albee Square W, Brooklyn, NY",
      "lat": 40.6926,
      "lng": -73.9873,
      "serviceTimeSeconds": 1200
    }
  ],
  "parameters": {
    "travelMode": "driving",
    "optimizeMode": "Time",
    "roundTrip": true,
    "routeStartAt": "2026-04-14T08:00",
    "timezone": "America/New_York"
  },
  "webhookUrl": "https://your-server.com/webhooks/r4m"
}

Parameters

ParameterTypeDefaultDescription
travelModestring"driving"Mode of transportation used when computing the route.("driving" | "walking" | "bicycling")
roundTripbooleanfalseWhen true, the route returns to the starting depot after the last stop.
routeStartAtstring—When the route starts, as local date and time without a zone, e.g. 2026-04-14T08:00. Read in `timezone`.
optimizeModestring"Time"Optimization objective: minimize total travel time or distance.("Time" | "Distance")
timezonestring—IANA timezone the local times in this request are read in, for example America/New_York. Required when any stop has a delivery window.
maxRoutesFree —, Pro 50, more by agreementinteger—Maximum number of vehicles (routes) for multi-route VRP optimization. Availability and ceiling depend on your plan.(≥ 1)
maxRouteDurationSecondsFree —, Pro 86400, more by agreementinteger—Maximum wall-clock duration of one route, in seconds. Availability and ceiling depend on your plan.(≥ 1)
maxRouteDistanceMetersFree —, Pro 2000000, more by agreementinteger—Maximum distance of one route, in meters. Applied in steps of 1609 meters, rounded down, so a cap under 1609 becomes 1609. Availability and ceiling depend on your plan.(≥ 1)

Address fields

FieldTypeRequiredDescription
addressstringYesThe stop's address. With `lat`/`lng` it is only a label; without them it is geocoded (charged at the geocode price) and must resolve to exactly one place.
latnumberNoLatitude in decimal degrees. Optional: omit both `lat` and `lng` to have the address geocoded.(-90–90)
lngnumberNoLongitude in decimal degrees. Goes together with `lat`.(-180–180)
isDepotbooleanNoWhen true, marks this stop as the route's depot (start and/or end).
serviceTimeSecondsintegerNoTime spent at this stop, in seconds (service dwell time).(≥ 0)
timeWindowStartstringNoEarliest local time this stop can be served, HH:MM. Taken as the first such time at or after the route starts, so 02:00 on a route leaving at 22:00 means the following morning.
timeWindowEndstringNoLatest local time this stop can be served, HH:MM. The stop's `serviceTimeSeconds` must finish by then, not merely begin.
priorityintegerNoStop priority (1 = highest). Higher-priority stops are scheduled earlier.(1–9999)

Response (202 Accepted)

{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "pending",
    "priceMicros": 1,
    "addressCount": 4,
    "webhookUrl": "https://your-server.com/webhook",
    "createdAt": "2026-04-14T10:00:00Z"
  }
}
GET/api/v1/optimizations
Try it →

List optimizations with cursor-based pagination.

Query parameters

?cursor=<last-id>&limit=20

Response

{
  "success": true,
  "data": {
    "items": [
      { "id": "uuid", "status": "completed", "priceMicros": 1, "createdAt": "..." },
      { "id": "uuid", "status": "pending", "priceMicros": 2, "createdAt": "..." }
    ],
    "nextCursor": "550e8400-e29b-41d4-a716-446655440001"
  }
}
GET/api/v1/optimizations/:id
Try it →

Get a single optimization by ID, including the result when completed.

Response (completed)

{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "completed",
    "priceMicros": 10000,
    "webhookUrl": "https://your-server.com/webhooks/r4m",
    "webhookStatus": "delivered",
    "result": {
      "routes": [
        {
          "routeStartAt": "2026-04-14T08:00:00-04:00",
          "totalDistanceMeters": 19473,
          "totalTimeSeconds": 8285,
          "polyline": "ulpwFpplbMl@qA@CLOYYIIIK…",
          "waypoints": [
            {
              "index": 0,
              "arrivalAt": "2026-04-14T08:00:00-04:00",
              "departureAt": "2026-04-14T08:15:00-04:00",
              "waitTimeSeconds": 0,
              "cumulativeDistanceMeters": 0,
              "cumulativeTimeSeconds": 0,
              "polylineToNext": "ulpwFpplbMl@qA@CLOYYIIIK…"
            },
            {
              "index": 1,
              "arrivalAt": "2026-04-14T09:00:00-04:00",
              "departureAt": "2026-04-14T09:10:00-04:00",
              "waitTimeSeconds": 2543,
              "cumulativeDistanceMeters": 1416,
              "cumulativeTimeSeconds": 3600,
              "polylineToNext": "}brwFhckbMI{A?IGkAIqBMkC…"
            }
          ]
        }
      ],
      "unroutedStops": []
    },
    "createdAt": "2026-04-14T11:58:12Z",
    "updatedAt": "2026-04-14T11:58:41Z"
  }
}
DELETE/api/v1/optimizations/:id
Try it →

Permanently delete an optimization and its result.

Response

{
  "success": true,
  "data": { "deleted": true }
}

Geocoding

POST/api/v1/geocoding/forward
Try it →

Forward geocode a single address string to lat/lng coordinates.

Request body

{
  "address": "1600 Amphitheatre Parkway, Mountain View, CA"
}

Response

{
  "success": true,
  "data": {
    "lat": 37.4220,
    "lng": -122.0841,
    "formattedAddress": "1600 Amphitheatre Pkwy, Mountain View, CA 94043"
  }
}
POST/api/v1/geocoding/bulk
Try it →

Bulk geocode many addresses in a single request (up to your plan limit). One result per distinct address, in the order first sent — match by original, never by position; duplicates are charged once.

Request body

{
  "addresses": [
    "1600 Amphitheatre Parkway, Mountain View, CA",
    "Springfield",
    "asdkjh qwe 123"
  ]
}

Response — status is ok, ambiguous or not_found; only precision "address" is exact enough to drive to

{
  "success": true,
  "data": {
    "results": [
      { "original": "1600 Amphitheatre Parkway, Mountain View, CA", "status": "ok",
        "address": "1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA",
        "lat": 37.4220, "lng": -122.0841, "precision": "address", "confidence": "high" },
      { "original": "Springfield", "status": "ambiguous", "candidates": [
        { "address": "Springfield, IL, USA", "lat": 39.8007, "lng": -89.6449, "precision": "city", "confidence": "medium" },
        { "address": "Springfield, MA, USA", "lat": 42.1013, "lng": -72.5893, "precision": "city", "confidence": "medium" },
        { "address": "Springfield, MO, USA", "lat": 37.2090, "lng": -93.2923, "precision": "city", "confidence": "medium" }
      ] },
      { "original": "asdkjh qwe 123", "status": "not_found" }
    ],
    "priceMicros": 15000
  }
}

API Keys

POST/api/v1/keys
Try it →

Create a new API key. The raw key (prefixed with acc_) is returned only once in this response -- store it securely.

Request body

{
  "name": "Production Server"
}

Response

{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Production Server",
    "prefix": "acc_a1b2c3",
    "key": "acc_a1b2c3d4e5f6g7h8i9j0...",
    "createdAt": "2026-04-14T10:00:00Z"
  }
}
GET/api/v1/keys
Try it →

List all API keys. Raw keys are never returned -- only the prefix.

Response

{
  "success": true,
  "data": [
    {
      "id": "uuid",
      "name": "Production Server",
      "prefix": "acc_a1b2c3",
      "lastUsedAt": "2026-04-14T09:30:00Z",
      "createdAt": "2026-04-10T10:00:00Z"
    }
  ]
}
DELETE/api/v1/keys/:id
Try it →

Revoke an API key (soft delete). The key will stop working immediately.

Response

{
  "success": true,
  "data": { "revoked": true }
}

Usage & Account

GET/api/v1/usage
Try it →

Get the current prepaid balance (micro-dollars) and recent activity.

Response

{
  "success": true,
  "data": {
    "balance": 4958,
    "ledger": [
      {
        "id": "8f3a2b1c-...",
        "delta": -42,
        "reason": "usage",
        "kind": "optimization",
        "quantity": 250,
        "refId": "opt_7f3a...",
        "createdAt": "2026-07-09T10:00:00Z"
      }
    ]
  }
}
GET/api/v1/account
Try it →

Get current user account and plan information.

Response

{
  "success": true,
  "data": {
    "email": "dev@example.com",
    "planTier": "pro",
    "balanceMicros": 4820,
    "plan": {
      "name": "Pro",
      "maxAddressesPerRequest": 100,
      "maxApiKeys": 5
    }
  }
}
GET/api/v1/webhook-secret
Try it →

The secrets webhook deliveries are signed with. primary signs every delivery; previous is set while a rotation is in progress — verify against both. Any API key can read this: if a key leaks, revoke it and rotate the secret in the console. Never cached.

Response

{
  "success": true,
  "data": {
    "primary": { "secret": "whsec_…", "createdAt": "2026-11-02T23:44:20.313Z" },
    "previous": null
  }
}
POST/api/v1/credits/topup
Try it →

Preview — machine-payment (MPP) top-up. Without a credential, returns a 402 with an MPP challenge; on payment, adds to the balance and returns a receipt. Enabled per deployment; returns 501 NOT_CONFIGURED until then. See the Machine Payments concept.

402 challenge response

{
  "success": false,
  "error": { "code": "INSUFFICIENT_FUNDS", "message": "Payment required to top up the balance." },
  "challenge": {
    "scheme": "mpp",
    "status": "payment_required",
    "amountCents": 500,
    "currency": "usd",
    "grantMicros": 5000000,
    "endpoint": "/api/v1/credits/topup",
    "minCents": 500
  }
}

Try it live

Send requests directly from your browser with the interactive API reference powered by Scalar.

Open Interactive API Reference