Skip to main content
The map renders boundaries you already hold without any credential. To fill it from LOCI instead — the frame under a point, the data recorded on a frame — you give the SDK an API key and preload frames before the map appears. The map never touches the network itself: panning, drawing, showing and hiding work areas never wait on a connection and never spend the key’s budget. Everything on this page is the SDK making the same calls this site documents. The request and response contract is the API reference; this page is which of those calls the SDK makes, and when.

Which API calls the SDK makes

The SDK makes exactly three kinds of request, all on the frames service. It does not call /v1/frames/admin, /v1/states, /v1/lgas, /v1/wards or /v1/frames/insight, and it never looks frames up by box.

Get your key

The SDK needs one credential, an API key. Create it in your LOCI dashboard under Developers → New key: pick a scope and an expiry, and copy the secret, which is shown once. See Authentication for the full story, and Scopes for what each preset reaches.
A key is read-only, acts for one organisation, and expires. A key narrowed to named datasets only ever preloads those datasets. Every device that uses the same key shares one rate-limit budget — see cost.

Initialise the SDK

Call MilsatMaps.initialize once, for example in main, with the key you created. After that, MilsatMaps.frames is the object every example here uses. Call MilsatMaps.dispose when your app is done with it.
initialise the SDK

Preload your frames

You hand the SDK a list of frame ids. It fetches each frame’s outline and the data recorded on it, groups the data by layer, and gives back ready-made work areas. Call these methods in a prepare step before the map appears — when the user opens a project, say — not from a tap or a camera callback.
load, then show
Each frame is a MapWorkArea with the frame id as its id; the dataset rows sit under properties[MilsatFramePack.propertyKey], grouped by layer id. Pass the list to controller.setWorkAreas, or to the prebuilt widget as workAreas:. Frames arrive switched off, like any work area with initiallyVisible false; the user turns them on, or you call controller.showWorkArea(id). A frame is a work area, so every work-area rule, including geofencing, applies.

What you get back

read a result

Finding ids to start from

A frame id is a string of digits. You can hold ids from your dashboard or your own records, or resolve them.
Ids go straight to loadFrames. An id the service does not know comes back in missingIds, not as an error.

Offline packs

A pack is a saved set of frames and their data, kept on the device under an id you choose (1–64 of letters, digits, _ or -). Nothing is cached unless you call these; nothing refreshes a pack for you.
save, load, refresh, list, delete
Expiry. maxAge on savePack/refreshPack marks a pack expired (not deleted) after that long; loadPack then throws. With no maxAge a pack never expires. Hidden layers passed to any call are left out when data is stored and again when it is returned, so a hidden layer is not on the map, not in the result, and not in the pack file.

Source: cloud or pack

loadFrames takes one source, so the two stores look like a single choice.
the three sources

Errors and events

Failures are typed and the same on Android and iOS. None carries the key. Branch on the code, never on the message.
handle each family

Service errors — MilsatFramesException

These mirror the API’s own error codes. The SDK adds three transport codes the service never sends: network, untrusted_connection and unknown. The three key codes are final: the SDK does not retry them and emits a MilsatFramesTokenRejected event. On rate_limited the SDK waits the Retry-After time and tries up to three more times before surfacing the error; do not loop on it. See Limits.

Pack errors

A MilsatFramesPackStoreException reports missing, expired, wrongOrganisation or storageFailed. A MilsatFramesPackException reports a damaged or foreign pack file (authenticationFailed, truncated, badMagic, unsupportedVersion, corruptPayload). Every pack failure closes: you get an error and no data, and nothing is deleted — a failed read never destroys a pack a fix could recover. Call deletePack to get rid of a bad one.

Events

listen for events
Billing is not an event: read charged on each result.

Rate limits and cost

The limits and caps are the API’s own — the full detail is in Limits. Read retryAfterSeconds on a rate_limited error rather than any number here, because the numbers can change.
  • Shared budget. Every device using the same key shares one bucket. A whole team loading frames at 9am will see the first few succeed and the rest get rate_limited. Stagger syncs, or give large teams more than one key.
  • Load once, then work from a pack. Save a pack when the user opens a project and use packFirst or packOnly after that. Do not refresh on startup, on a timer, or in a retry loop — each refresh spends requests again.
  • The cost of a load. The SDK sends at most 100 ids per request, one request at a time, and a load makes two kinds of call (outlines and data), so N ids cost about 2 × ceil(N / 100) requests. Resolving a point or a work area is one request.
  • Watch charged. Some datasets are licensed per query and billed per request they contribute to; each refresh bills them again.
  • Watch truncatedLayerIds. A layer at 200 features per request may be incomplete, and nothing else says so.
Keys expire, and a key stops working when the person who created it leaves the organisation. A phone offline for weeks may find its key dead — but pack data already on the device stays readable.