Skip to content
Developers

Quickstart

From a new account to a working address search in a few minutes, then what to know about keys, errors, limits and billing.

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

Try it before you write any codeSend real requests from your browser, pick points on a map and copy the code for the same call.

Open the playground

Create an account and a key

Create a company account, then open API keys in the console and create a key. Choose the project, the environment (test or live) and the scopes the key needs. For this page, addresses:search is enough.

The secret is shown once, when the key is created. Copy it then: only a hash is stored, so nobody can show it to you again. A lost key is rotated, not recovered.

Make your first call

Put the secret in an environment variable, then search for an address. Every sample reads the key from MAKANI_API_KEY:

export MAKANI_API_KEY=addr_test_…
GET/v1/addresses/{countryCode}/searchScopeaddresses:search
curl "https://makani-vol.lamah.com/v1/addresses/QA/search?q=Street%20984&locale=en&limit=5" \  -H "x-api-key: $MAKANI_API_KEY"

The answer is JSON: results holds the matching addresses, each with its addressId, its publicCode and a formattedAddress in the requested locale. nextCursor is the next page, or null.

Send the key

Send the key in the x-api-key header, as above, or as Authorization: Bearer. Both work on every endpoint that needs a key.

Never put a key in a URL, and never ship a key without restrictions in a web page or a mobile app: restrict it to your website, your servers' addresses or your app first (see API keys).

GET/v1/addresses/{countryCode}/searchScopeaddresses:search
curl "https://makani-vol.lamah.com/v1/addresses/QA/search?q=Street%20984&locale=en&limit=5" \  -H "Authorization: Bearer $MAKANI_API_KEY"

Test and live keys

Every project has a test and a live environment, and a key belongs to one of them. A test secret starts with addr_test_ and a live one with addr_live_.

  • Both call the same API and the same data, with the same limits.
  • Requests made with a test key count against the plan's included units and are never charged.
  • Requests made with a live key are charged once the included units are used.
  • Usage, quotas and keys are reported per environment in the console.

Errors

Every error has the same shape. code is stable and meant for your code; message is for people. Some errors add details, such as a limit and when it resets. Keep requestId: support can find the request with it. It is also in the x-request-id response header.

{  "error": {    "code": "API_KEY_SCOPE_DENIED",    "message": "This API key does not include the routing:route scope",    "path": "/v1/routing/route",    "requestId": "65ef43e2-b08e-4890-82ed-5aa405710f77",    "traceId": "dcef681610ec5b41bd36cf2551c8ef79",    "timestamp": "2026-10-03T18:09:06.390Z"  }}

Errors with status 429 and 503 carry a Retry-After header in seconds. Wait that long, then retry:

GET/v1/addresses/{countryCode}/resolveScopeaddresses:resolve
curl -i "https://makani-vol.lamah.com/v1/addresses/QA/resolve?latitude=25.2854&longitude=51.531&radiusMeters=1000&locale=en" \  -H "x-api-key: $MAKANI_API_KEY"

The codes you are most likely to meet:

  • 400VALIDATION_FAILEDA parameter is missing or invalid. details.errors lists each failing field with its messages, and message joins them.
  • 401API_KEY_REQUIREDThe request carries no key.
  • 401API_KEY_INVALIDThe key is unknown or revoked.
  • 401API_KEY_EXPIREDThe key has passed its expiry date. Create or rotate a key in the console.
  • 403API_KEY_SCOPE_DENIEDThe key lacks the scope the endpoint needs. message names it.
  • 403API_KEY_RESTRICTEDThe request does not come from the website, address or app the key is restricted to.
  • 403SUBSCRIPTION_FEATURE_NOT_INCLUDEDYour plan does not include this product, for example routing.
  • 403ORGANIZATION_SUSPENDEDThe organization is suspended, so its keys are refused until it is reactivated. Contact support.
  • 402SUBSCRIPTION_REQUIREDThe billing account the key's project is linked to has no active plan. A lapsed plan answers SUBSCRIPTION_INACTIVE or SUBSCRIPTION_EXPIRED.
  • 402BALANCE_LIMIT_REACHEDThe balance cannot cover usage beyond the included units. Top up in Billing.
  • 429RATE_LIMIT_EXCEEDEDThe key made more requests this minute than the plan allows. Wait Retry-After seconds.
  • 429SUBSCRIPTION_LIMIT_EXCEEDEDThe plan's included units are used and the plan does not allow more. details.resetsAt says when they renew.
  • 503SEARCH_TIMEOUTThe search took too long and was cancelled. Retry, or narrow the text.

Rate limits and quotas

Each key may make a number of requests a minute, set by your plan (see the table below). Every answer to a keyed request says where you stand:

  • X-RateLimit-Limit: requests allowed in the window.
  • X-RateLimit-Remaining: requests left in it.
  • X-RateLimit-Reset: when the window ends, as Unix time in seconds.
  • X-RateLimit-Window: the window's length in milliseconds.

Beside the key's limit, each network address may make 300 requests a minute across the API. You can also set your own quotas per project, environment or key in the console, and a monthly budget that alerts you or stops usage.

  • 429RATE_LIMIT_EXCEEDEDThe key made more requests this minute than the plan allows. Wait Retry-After seconds.
  • 429IP_RATE_LIMIT_EXCEEDEDToo many requests from one network address this minute.
  • 429QUOTA_EXCEEDEDA cap you set in the console (a quota on a project or a key) is used up. details.product, details.scope, details.window and details.limit say which one. It resets at 00:00 UTC for a daily cap and on the 1st of the month for a monthly one: wait Retry-After seconds, or see details.resetAt.
  • 429SPEND_CAP_EXCEEDEDA cap in money you set in the console (on a project or a key, per day or month) is reached: the request would be charged beyond it. details.scope, details.window, details.limitMinor and details.currencyCode say which one, and the X-Spend-Cap-Limit and X-Spend-Cap-Currency headers repeat it. Requests the plan covers cost nothing and keep working. Wait Retry-After seconds, or see details.resetAt.
  • 429SUBSCRIPTION_LIMIT_EXCEEDEDThe plan's included units are used and the plan does not allow more. details.resetsAt says when they renew.
  • 429DAILY_LIMIT_EXCEEDEDThe plan's requests for the day are used. The day ends at 00:00 UTC: wait Retry-After seconds, or see details.resetsAt.
  • 402BUDGET_CAP_REACHEDA monthly budget set to stop usage is spent. Raise it in Billing.
  • 402BALANCE_LIMIT_REACHEDThe balance cannot cover usage beyond the included units. Top up in Billing.

A quota is a cap you choose: so many billed items a minute, a day or a month, for one product, for matrix pairs alone or for every product. A daily cap counts the UTC day and a monthly cap the UTC calendar month. The lowest of your caps and the plan's limits applies. A request over a cap answers 429 QUOTA_EXCEEDED with Retry-After, details.resetAt and the three X-RateLimit headers of the cap; the organization's owners and admins get a notice and an email at 80% and at 100%.

How requests are counted and billed

A plan includes a number of units per period. They are used first, whichever product the usage belongs to. Only usage beyond them is charged.

  • An ordinary request is counted once.
  • A batch lookup is counted once per item.
  • A routing matrix is counted once per answered source-target pair.
  • A best stop order is counted once per stop.
  • A reachable area is counted once per contour.
  • Each of these uses its product's units of the plan, as the table says.
  • Only answers with a status below 400 count. A request refused for its key, a rate limit or a quota is not counted.
  • Road closures for your map and straight-line routing estimates are never charged.
  • Send an Idempotency-Key header when you retry: the same request is then counted once.

Products do not all count the same: each one uses the number of units the price list gives it (unitWeight, the "Uses of a plan" column below), because a route costs more to serve than an address lookup. A plan therefore covers fewer routing requests than address requests. A plan's daily cap counts the same units; the quotas you set yourself count each request, item, pair, stop or contour once.

Loading plans…

Compare plans

Beyond the included units, each request is charged on its own, whatever it counted: address and map requests at the plan's price per 1,000 in the table, routing requests and matrix pairs at the price list per 1,000 of their product. On a plan that does not allow more, requests stop with 429 SUBSCRIPTION_LIMIT_EXCEEDED until the period renews or the plan is upgraded; details.resetsAt says when.

The price list below is in the platform's default currency and is read live from GET /v1/public/prices, which needs no key:

Loading prices…

GET/v1/public/pricesNo key needed
curl "https://makani-vol.lamah.com/v1/public/prices"

SDKs

The JavaScript samples use the TypeScript SDK, and fetch only where a sample shows a request header. The Dart samples use the Flutter navigation package for turn-by-turn directions and the http package for everything else.

TypeScript SDK

The SDK is the @workspace/sdk package of the platform repository. It has no dependencies and is not on the public npm registry yet, so build its tarball from the repository and install that file:

pnpm --filter @workspace/sdk buildpnpm --filter @workspace/sdk pack --pack-destination /tmpnpm install /tmp/workspace-sdk-<version>.tgz

Without the repository, use fetch: each SDK call in these pages is the HTTP request shown in its curl tab.

Flutter navigation package

Turn-by-turn navigation for Flutter is the global_address_nav package, installed from the platform repository with a Git dependency. It needs read access to that repository:

dependencies:
  global_address_nav:
    git:
      url: git@github.com:lamah-co/makani-global.git
      path: packages/flutter-navigation

The other Dart samples use the http package: dart pub add http.