Skip to content
Developers

Delivery fee quotes

Price a delivery by road distance without paying for requests nobody needs: when to quote, one request for many stores, caching, and moving from Google's Distance Matrix.

Base URLhttps://makani-k8s.lamah.com

Quote where the fee matters

A delivery fee depends on the road distance from the store to the customer's door. That distance matters for one store only: the one the customer is ordering from. An app that prices every store of every list makes ten to twenty requests for each order, and all of them are counted.

  • Quote in the cart and at order creation. That is one or two requests for each order.
  • In lists (stores near me, search results, a category), sort and label with the straight-line distance between the two points. Your server computes it from the coordinates: no request, no cost.
  • If a list must show a fee, estimate it from the straight-line distance times a factor you measure on your own past orders, and price exactly in the cart.
  • Quote when the address or the store changes, never on a timer or on every position update.

One request for many stores

When several stores do need a road distance at once, for example a cart with items from three stores, send one matrix request: the stores as sources and the customer as the only target. One request answers every store, up to 2,500 pairs.

The answer has one row for each store, in the order you sent them, each with one cell: distanceMeters in meters and durationSeconds in seconds. A store with no road route to the customer has distanceMeters: null and a reason, and is not counted. Three stores are three units whether you send one request or three; one request is one round trip and counts once against your requests a minute.

POST/v1/routing/matrixScoperouting:matrix
curl -X POST "https://makani-k8s.lamah.com/v1/routing/matrix" \  -H "x-api-key: $MAKANI_API_KEY" \  -H "content-type: application/json" \  -d '{    "countryCode": "LY",    "sources": [      {        "latitude": 32.86,        "longitude": 13.23      },      {        "latitude": 32.895,        "longitude": 13.15      },      {        "latitude": 32.87,        "longitude": 13.21      }    ],    "targets": [      {        "latitude": 32.8872,        "longitude": 13.1913      }    ]  }'

Cache on your side

The road between a store and a door does not change from one order to the next. Keep each answer and ask only when it is missing:

  • Key the cache on the store and on the customer's coordinates rounded to four decimal places, about 11 meters. Rounding is what lets a position that is not a saved address hit the cache: two GPS fixes at the same door become the same key.
  • Send the customer's exact coordinates in the request; round only in your key.
  • Keep an entry for 30 days, and drop a store's entries when the store moves.
  • Keep the pairs with no route too, for a shorter time, so a misplaced pin is not asked again on every screen.
  • The platform also answers a repeated pair from its own cache, which makes it fast. It is counted like any other request, so your cache is what lowers the bill.

What a quote costs

A quote is counted for each answered pair of the matrix. Your plan's included units are used first, and a pair uses the number of units the price list gives the routing matrix (unitWeight). Beyond the plan, each pair is charged at the price list's price per 1,000.

  • One store and one customer: one pair.
  • Five stores and one customer in one request: five pairs.
  • A pair with no route, a refused request and an answer with a status of 400 or above are not counted.
  • At one or two quotes for each order, 100 orders a day are 3,000 to 6,000 pairs a month.
  • A bug that quotes in a loop stops at a number you choose: in the console's Quotas, set a daily cap on the routing matrix for the key your quotes use. The pair that would pass it answers 429 QUOTA_EXCEEDED until 00:00 UTC, your other keys keep working, and you are told at 80% and at 100%.

Prices and weights can change, so none is written on this page. Read them from GET /v1/public/prices, which needs no key, or from this table, which is read live:

Loading prices…

Moving from Google's Distance Matrix

Code that already calls Google's Distance Matrix API moves by changing the URL and the key. This endpoint takes the same query parameters and answers in the same JSON shape, so the code that reads rows[0].elements[0].distance.value and duration.value stays as it is:

GET/v1/routing/compat/google/distancematrix/jsonScoperouting:matrix
curl "https://makani-k8s.lamah.com/v1/routing/compat/google/distancematrix/json?origins=32.86,13.23&destinations=32.8872,13.1913&mode=driving&units=metric&region=ly" \  -H "x-api-key: $MAKANI_API_KEY"

Send the key in the x-api-key header. A key= query parameter is accepted too, as Google's clients send it, but a key in a URL can end up in the logs of whatever the request passes through. The key needs the routing:matrix scope. What differs from Google:

  • The country. Send the two-letter country of the points as region (country and countryCode are read too). A key limited to one country needs no parameter: that country is used. Otherwise a request without a country answers INVALID_REQUEST.
  • Coordinates only. origins and destinations are lat,lng points separated by |, up to 25 each and 100 pairs in a request. Addresses, place ids and encoded polylines answer INVALID_REQUEST.
  • Travel modes. driving (the default), walking and bicycling. transit and its parameters answer INVALID_REQUEST.
  • No live traffic. departure_time, traffic_model and avoid are accepted and ignored, like any parameter the endpoint does not know. duration is the usual travel time, and duration_in_traffic is never returned.
  • Addresses. origin_addresses and destination_addresses repeat the coordinates you sent. Nothing is geocoded.
  • Text. units and language change distance.text and duration.text only. value is always meters and seconds.
  • Statuses. A refusal is HTTP 200 with a status, as Google answers, and error_message starts with this platform's error code: INVALID_REQUEST for a bad request, MAX_DIMENSIONS_EXCEEDED and MAX_ELEMENTS_EXCEEDED for too many points, REQUEST_DENIED for the key, its scope, its country or the plan, OVER_QUERY_LIMIT for the requests a minute, and OVER_DAILY_LIMIT for a daily cap, a quota, the plan's units or the balance. A pair with no road route has the element status ZERO_RESULTS. When routing cannot answer, the status is UNKNOWN_ERROR with HTTP 503 and a Retry-After header.

Road closures, the platform's cache and billing apply as on the matrix: one unit for each answered pair. Changing the URL does not change how often your app asks, so apply the sections above as well: they are what lowers the bill.

The driver's route line

An app that draws the driver's route with Google's Routes API (computeRoutes) replaces that one call with the route request. There is no compatible endpoint for it: client libraries for that API are tied to Google's address, so the call is replaced in code either way. The answer carries the same three things: distanceMeters, durationSeconds (a number, where Google writes a text such as 303s) and shape, the encoded line.

One difference needs care: shape is encoded with six decimal places, not five. A decoder written for five draws the line in the wrong place, so decode with a precision of six, as the Dart sample does. Ask for the route again when the driver leaves it or the destination changes, not on every position update. closures.avoided says why a line goes around a closed road.

POST/v1/routing/routeScoperouting:route
curl -X POST "https://makani-k8s.lamah.com/v1/routing/route" \  -H "x-api-key: $MAKANI_API_KEY" \  -H "content-type: application/json" \  -d '{    "countryCode": "LY",    "from": {      "latitude": 32.86,      "longitude": 13.23    },    "to": {      "latitude": 32.8872,      "longitude": 13.1913    }  }'