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.
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_…addresses:searchcurl "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).
addresses:searchcurl "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:
addresses:resolvecurl -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:
- 400
VALIDATION_FAILEDA parameter is missing or invalid.details.errorslists each failing field with its messages, andmessagejoins them. - 401
API_KEY_REQUIREDThe request carries no key. - 401
API_KEY_INVALIDThe key is unknown or revoked. - 401
API_KEY_EXPIREDThe key has passed its expiry date. Create or rotate a key in the console. - 403
API_KEY_SCOPE_DENIEDThe key lacks the scope the endpoint needs.messagenames it. - 403
API_KEY_RESTRICTEDThe request does not come from the website, address or app the key is restricted to. - 403
SUBSCRIPTION_FEATURE_NOT_INCLUDEDYour plan does not include this product, for example routing. - 403
ORGANIZATION_SUSPENDEDThe organization is suspended, so its keys are refused until it is reactivated. Contact support. - 402
SUBSCRIPTION_REQUIREDThe billing account the key's project is linked to has no active plan. A lapsed plan answersSUBSCRIPTION_INACTIVEorSUBSCRIPTION_EXPIRED. - 402
BALANCE_LIMIT_REACHEDThe balance cannot cover usage beyond the included units. Top up in Billing. - 429
RATE_LIMIT_EXCEEDEDThe key made more requests this minute than the plan allows. WaitRetry-Afterseconds. - 429
SUBSCRIPTION_LIMIT_EXCEEDEDThe plan's included units are used and the plan does not allow more.details.resetsAtsays when they renew. - 503
SEARCH_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.
- 429
RATE_LIMIT_EXCEEDEDThe key made more requests this minute than the plan allows. WaitRetry-Afterseconds. - 429
IP_RATE_LIMIT_EXCEEDEDToo many requests from one network address this minute. - 429
QUOTA_EXCEEDEDA cap you set in the console (a quota on a project or a key) is used up.details.product,details.scope,details.windowanddetails.limitsay which one. It resets at 00:00 UTC for a daily cap and on the 1st of the month for a monthly one: waitRetry-Afterseconds, or seedetails.resetAt. - 429
SPEND_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.limitMinoranddetails.currencyCodesay which one, and theX-Spend-Cap-LimitandX-Spend-Cap-Currencyheaders repeat it. Requests the plan covers cost nothing and keep working. WaitRetry-Afterseconds, or seedetails.resetAt. - 429
SUBSCRIPTION_LIMIT_EXCEEDEDThe plan's included units are used and the plan does not allow more.details.resetsAtsays when they renew. - 429
DAILY_LIMIT_EXCEEDEDThe plan's requests for the day are used. The day ends at 00:00 UTC: waitRetry-Afterseconds, or seedetails.resetsAt. - 402
BUDGET_CAP_REACHEDA monthly budget set to stop usage is spent. Raise it in Billing. - 402
BALANCE_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-Keyheader 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…
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…
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>.tgzWithout 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-navigationThe other Dart samples use the http package: dart pub add http.