> ## Documentation Index
> Fetch the complete documentation index at: https://docs.loci.ng/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Mint a key, then make all four calls. Every response on this page is one the API really returned.

This takes you from nothing to a worded answer about a place. The responses
were captured against a development stack carrying the national frame index
and Milsat's baselines, with long coordinate lists shortened where it says so.

You need a LOCI account with the **Admin** or **Owner** role in an
organisation. Minting a key is something a person does in the app; there is
no call for it, so a key can never mint another.

<Steps>
  <Step title="Mint a key">
    In the app, open **Developers** and choose **New key**. Four things to
    decide:

    * **Name.** What will hold this key, such as `field-app-prod`. It is how
      you will recognise the key later, in the list and in the audit trail.
    * **Scope.** Pick **Analyst** for this walkthrough. It grants the frame
      reads and the question call. **Locality** would be refused at step 5.
    * **Datasets.** Leave it at every dataset the organisation can read, or
      narrow the key to named ones.
    * **Expiry.** From seven days to a year. There is no never-expires option.

    <Warning>
      The secret is shown once, on the screen that mints it. The server keeps
      only a hash, so it cannot be shown again. Lose it and you revoke the key
      and mint another.
    </Warning>
  </Step>

  <Step title="Export the secret">
    ```bash theme={null}
    export LOCI_KEY="loci_sk_..."
    export LOCI_API="https://api.staging.loci.ng"
    ```

    The key travels in the `Authorization` header and nowhere else:

    ```
    Authorization: Bearer loci_sk_...
    ```
  </Step>

  <Step title="The frame at a location">
    A point in Garki, Abuja:

    ```bash theme={null}
    curl -s "$LOCI_API/v1/frames/at?lat=9.0579&lon=7.4951" \
      -H "Authorization: Bearer $LOCI_KEY"
    ```

    ```json Response (200) — coordinates abridged theme={null}
    {
      "data": {
        "id": "2163551",
        "geometry": {
          "type": "Polygon",
          "coordinates": [[[7.5003, 9.05143], [7.49131, 9.05143], "…", [7.5003, 9.05143]]]
        },
        "bbox": [7.48751, 9.05143, 7.5003, 9.06201]
      }
    }
    ```

    The real ring has 64 vertices, because this frame is clipped where the
    grid meets a boundary. `id` is what the next three calls take. A point
    outside the grid is `404 not_found`; a missing or malformed coordinate is
    `400 invalid_point`.
  </Step>

  <Step title="The frames around it">
    There is no radius parameter. Build a box around the point instead. Frames
    are about 0.01° across, so ±0.01° returns the frame and its neighbours.
    This call is limited to three so the truncation flag shows:

    ```bash theme={null}
    curl -s "$LOCI_API/v1/frames?bbox=7.485,9.048,7.505,9.068&limit=3" \
      -H "Authorization: Bearer $LOCI_KEY"
    ```

    ```json Response (200) — coordinates abridged theme={null}
    {
      "data": {
        "type": "FeatureCollection",
        "features": [
          { "type": "Feature", "id": "2163725", "geometry": { "type": "Polygon", "coordinates": [[[7.4904, 9.05143], [7.4805, 9.05143], "…"]] } },
          { "type": "Feature", "id": "2163731", "geometry": { "type": "Polygon", "coordinates": [[[7.4904, 9.04434], [7.48158, 9.04434], "…"]] } },
          { "type": "Feature", "id": "2163732", "geometry": { "type": "Polygon", "coordinates": [[[7.49509, 9.04434], [7.4904, 9.04434], "…"]] } }
        ],
        "truncated": true
      }
    }
    ```

    Without `limit` the same box returns all thirteen frames it touches and
    no `truncated` flag. The cap is 5,000 frames per response; past it the
    flag is set rather than the call refused, and the fix is a smaller box.
  </Step>

  <Step title="What is recorded on a frame">
    ```bash theme={null}
    curl -s "$LOCI_API/v1/frames/data?ids=2163551" \
      -H "Authorization: Bearer $LOCI_KEY"
    ```

    ```json Response (200) — four of six datasets shown theme={null}
    {
      "data": {
        "frames": [
          {
            "id": "2163551",
            "bbox": [7.48751263500003, 9.051434265000069, 7.500301437000076, 9.06200771500005],
            "datasets": [
              {
                "layerId": "lyr_8142687a47796c6e",
                "name": "Administrative baseline",
                "category": "Administrative",
                "kind": "frames",
                "orgId": "org-milsat",
                "values": {
                  "adm_lga": "ABUJA MUNICIPAL AREA COUNCIL",
                  "adm_lga_code": "436",
                  "adm_state": "FCT",
                  "adm_state_code": "14",
                  "adm_ward": "GARKI",
                  "adm_ward_code": "9470",
                  "adm_zone": "NORTH CENTRAL",
                  "adm_zone_code": "3"
                }
              },
              {
                "layerId": "lyr_07311eb9fddfe966",
                "name": "Built environment baseline",
                "category": "Built environment",
                "kind": "frames",
                "orgId": "org-milsat",
                "values": { "bld_ct": "608", "bld_dens": "1237.337222089773" }
              },
              {
                "layerId": "lyr_1b81b870d62fe092",
                "name": "Population baseline",
                "category": "Population",
                "kind": "frames",
                "orgId": "org-milsat",
                "values": { "pop_demest25": "5699", "pop_demest26": "5805" }
              },
              {
                "layerId": "lyr_1c0ea62e30f89f79",
                "name": "Urbanisation baseline",
                "category": "Urbanisation",
                "kind": "frames",
                "orgId": "org-milsat",
                "values": { "urb_class": "Peri-urban", "urb_index": "1" }
              }
            ]
          }
        ]
      }
    }
    ```

    The real response also carried the Cultural and Physical baselines. Each
    dataset the key can read contributes its row on the frame as `values`,
    with the values as strings exactly as stored. A dataset of points, lines
    or polygons contributes `features` instead, and only to a key holding
    `feature.read`. Up to 100 ids per call, comma-separated.
  </Step>

  <Step title="Ask about it">
    ```bash theme={null}
    curl -s -X POST "$LOCI_API/v1/frames/insight" \
      -H "Authorization: Bearer $LOCI_KEY" \
      -H "Content-Type: application/json" \
      -d '{"frameIds":["2163551"],"question":"What is here?"}'
    ```

    ```json Response (200) — sources and table omitted theme={null}
    {
      "data": {
        "intent": "frames",
        "headline": "1 area selected in GARKI",
        "narrative": "You're looking at Garki, a ward in Abuja Municipal Area Council, FCT. It's classed as peri-urban, with around 608 buildings and an estimated population of 5,699 people in 2025, rising slightly to 5,805 in 2026. There are also 38 specific building points mapped within the area.",
        "facets": [
          { "label": "State", "value": "FCT" },
          { "label": "LGA", "value": "ABUJA MUNICIPAL AREA COUNCIL" },
          { "label": "Ward", "value": "GARKI" },
          { "label": "Building count", "value": "608" },
          { "label": "Estimated population (2025)", "value": "5,699" },
          { "label": "Estimated population (2026)", "value": "5,805" },
          { "label": "Settlement class", "value": "Peri-urban (1)" }
        ],
        "followUps": ["How many people live here?", "How many buildings are there?", "Which languages are spoken here?"],
        "confidence": "exact",
        "engine": "claude",
        "model": "eu.anthropic.claude-sonnet-4-6",
        "register": "plain",
        "frameIds": ["2163551"]
      }
    }
    ```

    `narrative` is the reply to show. `headline` and `facets` are the figures
    it was checked against, and `sources` in the full response names the
    dataset and attribute behind each one. Every number in the narrative was
    found among those figures before the answer was returned; had one not
    been, the narrative would be absent and `engine` would read
    `deterministic`.

    Up to 50 frames and 1,000 characters per question. Leave `question` out
    for a profile of the frames; send `"format": "report"` for a sectioned
    write-up.
  </Step>
</Steps>

## Two failures worth seeing now

**A typo in the body is refused, not ignored.** Unknown fields are a `400`:

```bash theme={null}
curl -s -X POST "$LOCI_API/v1/frames/insight" \
  -H "Authorization: Bearer $LOCI_KEY" -H "Content-Type: application/json" \
  -d '{"frameIds":["2163551"],"questoin":"typo"}'
```

```json Response (400) theme={null}
{"error":{"code":"invalid_body","message":"json: unknown field \"questoin\""}}
```

**The wrong scope is a `403` that names the missing capability.** A Locality
key on the question call:

```json Response (403) theme={null}
{"error":{"code":"insufficient_scope","message":"this API key does not carry the ask.query scope"}}
```

## Next

* [Authentication](/authentication): how a key's authority is re-derived on every request, and the one change that kills a key instantly.
* [Scopes](/scopes): the three presets, and which calls each one reaches.
* [Errors](/errors): every code these four calls return.
* [Limits](/limits): the rate budgets, the caps that truncate, and what a question costs.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.