> ## 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.

# Ask a question about frames

> A worded answer about some frames, computed from what the datasets your key can read record on them.

The figures are computed by the server from the data, never by the model. A model words them, and every figure in its wording is checked against the computed results before the answer is returned; wording that fails that check is dropped and the computed `headline` stands alone. `engine` says which happened: `claude` when a model worded the answer, `deterministic` when only the computed sentence is there.

Leave `question` empty for a profile of the frames. `format: "report"` returns a sectioned write-up instead of a reply: section titles are lines beginning `## ` and list items lines beginning `- `. `register` chooses the vocabulary, `plain` or `gis`; left out, it is inferred from the question.

At most 50 frames and 1,000 characters per question. Every answer worded by the model counts against the organisation's monthly AI allowance and, past it, its credits; when neither covers a question, the figures still come back and `notice` says why they are unworded.

Needs `ask.query`, which the Locality preset does not grant. Metered at 20 questions a minute per key on top of the key's general budget.



## OpenAPI

````yaml /api-reference/openapi.json post /v1/frames/insight
openapi: 3.1.0
info:
  title: LOCI API
  version: 1.0.0
  summary: >-
    The Milsat spatial index over HTTP: find the frame at a location, the frames
    around it, what is recorded on them, and a worded answer about them.
  description: >-
    Four calls, all under `/v1/frames`, all read-only, all with an API key.


    Nigeria is covered by a grid of **frames**: cells roughly a kilometre
    across, each with a stable numeric id. Every dataset on LOCI falls within
    frames, so a frame id is the key to everything known about a place. The API
    lets your code resolve a location to its frame, list the frames around a
    point, read what the datasets your organisation owns or licenses record on
    those frames, and ask a question about them in plain language.


    Every response is wrapped in an envelope: a success carries `data`, a
    failure carries `error` with a machine-readable `code` and a human
    `message`. Branch on the code; the message may be reworded.


    Send the key as `Authorization: Bearer loci_sk_…`. A key acts for one
    organisation, is read-only, and expires.
servers:
  - url: https://api.staging.loci.ng
    description: The LOCI API, which the app at loci.ng also uses.
  - url: http://localhost:8088
    description: A local development stack, which the examples here were captured against.
security:
  - apiKey: []
tags:
  - name: Frames
    description: The spatial index and what is recorded on it.
paths:
  /v1/frames/insight:
    post:
      tags:
        - Frames
      summary: Ask a question about frames
      description: >-
        A worded answer about some frames, computed from what the datasets your
        key can read record on them.


        The figures are computed by the server from the data, never by the
        model. A model words them, and every figure in its wording is checked
        against the computed results before the answer is returned; wording that
        fails that check is dropped and the computed `headline` stands alone.
        `engine` says which happened: `claude` when a model worded the answer,
        `deterministic` when only the computed sentence is there.


        Leave `question` empty for a profile of the frames. `format: "report"`
        returns a sectioned write-up instead of a reply: section titles are
        lines beginning `## ` and list items lines beginning `- `. `register`
        chooses the vocabulary, `plain` or `gis`; left out, it is inferred from
        the question.


        At most 50 frames and 1,000 characters per question. Every answer worded
        by the model counts against the organisation's monthly AI allowance and,
        past it, its credits; when neither covers a question, the figures still
        come back and `notice` says why they are unworded.


        Needs `ask.query`, which the Locality preset does not grant. Metered at
        20 questions a minute per key on top of the key's general budget.
      operationId: frameInsight
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                frameIds:
                  type: array
                  items:
                    type: string
                  minItems: 1
                  maxItems: 50
                  description: The frames the question is about. At most 50.
                question:
                  type: string
                  maxLength: 1000
                  description: What to find out. Empty asks for the frames' profile.
                format:
                  type: string
                  enum:
                    - answer
                    - report
                  description: A reply (the default) or a sectioned report.
                register:
                  type: string
                  enum:
                    - plain
                    - gis
                  description: >-
                    Everyday words, or precise GIS terms. Inferred from the
                    question when left out.
              required:
                - frameIds
              additionalProperties: false
            example:
              frameIds:
                - '2163551'
              question: What is here?
      responses:
        '200':
          description: >-
            The answer. `narrative` is the model's wording when there is one;
            `headline`, `facets` and `table` are the computed figures it was
            checked against.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Insight'
                required:
                  - data
              examples:
                profile:
                  summary: What is here?
                  value:
                    data:
                      intent: frames
                      headline: 1 area selected in GARKI
                      facets:
                        - label: Area
                          value: '1'
                        - 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)
                      table:
                        columns:
                          - Dataset
                          - Inside the selection
                          - In the dataset
                        rows:
                          - - 'Nigeria: local government areas'
                            - '0'
                            - '879'
                          - - 'Nigeria: states'
                            - '0'
                            - '37'
                          - - PCU
                            - '38'
                            - 772,333
                          - - Sample clinics
                            - '0'
                            - '6'
                      sources:
                        - layerId: lyr_07311eb9fddfe966
                          layerName: Built environment baseline
                          attribute: bld_ct
                          label: Building count
                        - layerId: lyr_1b81b870d62fe092
                          layerName: Population baseline
                          attribute: pop_demest25
                          label: Estimated population (2025)
                        - layerId: lyr_1b81b870d62fe092
                          layerName: Population baseline
                          attribute: pop_demest26
                          label: Estimated population (2026)
                        - layerId: lyr_ng_lgas
                          layerName: 'Nigeria: local government areas'
                        - layerId: lyr_ng_states
                          layerName: 'Nigeria: states'
                        - layerId: lyr_08c454af94bea2e6
                          layerName: PCU
                        - layerId: lyr_3c15e323c573795f
                          layerName: Sample clinics
                      followUps:
                        - How many people live here?
                        - How many buildings are there?
                        - Which languages are spoken here?
                      confidence: exact
                      engine: claude
                      retrieval: none
                      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.
                      register: plain
                      model: eu.anthropic.claude-sonnet-4-6
                      frameIds:
                        - '2163551'
                attribute:
                  summary: A question about one thing
                  value:
                    data:
                      intent: frames_attribute
                      headline: >-
                        Estimated population (2025) is 5,699 in this area in
                        GARKI
                      facets:
                        - label: Estimated population (2025)
                          value: 5,699
                      sources:
                        - layerId: lyr_1b81b870d62fe092
                          layerName: Population baseline
                          attribute: pop_demest25
                          label: Estimated population (2025)
                      confidence: partial
                      engine: claude
                      retrieval: none
                      narrative: >-
                        The estimated population here in Garki is 5,699, and
                        it's classed as a peri-urban settlement — meaning it
                        sits on the fringes of the urban area, somewhere between
                        a fully built-up city neighbourhood and the open
                        countryside.
                      register: plain
                      model: eu.anthropic.claude-sonnet-4-6
                      frameIds:
                        - '2163551'
        '400':
          description: >-
            `frameIds` is missing or names more than 50 frames (`invalid`); the
            question is over 1,000 characters (`too_long`); `format` is neither
            `answer` nor `report` (`invalid`); or the body has an unknown field
            or is not JSON (`invalid_body`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                invalid:
                  value:
                    error:
                      code: invalid
                      message: frameIds is required
                too_long:
                  value:
                    error:
                      code: too_long
                      message: questions are limited to 1000 characters
                invalid_body:
                  value:
                    error:
                      code: invalid_body
                      message: 'json: unknown field "questoin"'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: >-
            The key does not carry `ask.query`. The Locality preset does not
            grant it; Analyst and Full read do.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: insufficient_scope
                  message: this API key does not carry the ask.query scope
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/Internal'
      security:
        - apiKey:
            - ask.query
components:
  schemas:
    Insight:
      type: object
      properties:
        intent:
          type: string
          description: >-
            What the question was taken to ask, for example `frames` for a
            profile or `frames_attribute` for one measure.
        headline:
          type: string
          description: The computed answer as one sentence.
        detail:
          type: string
          description: The engine's elaboration, when it has one.
        narrative:
          type: string
          description: >-
            The model's wording, plain text with paragraphs separated by blank
            lines, or sections for a report. Absent when no model worded the
            answer.
        facets:
          type: array
          items:
            $ref: '#/components/schemas/Facet'
        table:
          $ref: '#/components/schemas/Table'
        sources:
          type: array
          items:
            $ref: '#/components/schemas/Source'
        followUps:
          type: array
          items:
            type: string
          description: Questions worth asking next.
        confidence:
          type: string
          enum:
            - exact
            - partial
            - unknown
          description: >-
            `partial` when a measure was matched from the wording rather than
            named; `unknown` when nothing was computed.
        engine:
          type: string
          enum:
            - deterministic
            - claude
          description: >-
            `claude` when a model worded the figures; `deterministic` when only
            the computed sentence is present.
        model:
          type: string
          description: The model that worded the answer, when one did.
        register:
          type: string
          enum:
            - plain
            - gis
        retrieval:
          type: string
          enum:
            - hybrid
            - lexical
            - none
          description: >-
            Which search informed the answer. Frame questions are answered from
            the frames directly, so this is usually `none`.
        format:
          type: string
          enum:
            - report
          description: Present when the narrative is a sectioned report.
        frameIds:
          type: array
          items:
            type: string
        notice:
          type: string
          description: >-
            Why the answer is unworded, when it is: the organisation's AI
            allowance and credits are spent. The figures still stand.
      required:
        - intent
        - headline
        - confidence
        - engine
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              description: Stable and machine-readable. Branch on it.
            message:
              type: string
              description: For people. May be reworded.
          required:
            - code
            - message
      required:
        - error
    Facet:
      type: object
      properties:
        label:
          type: string
        value:
          type: string
          description: Formatted for display, units and separators included.
      required:
        - label
        - value
    Table:
      type: object
      properties:
        columns:
          type: array
          items:
            type: string
        rows:
          type: array
          items:
            type: array
            items:
              type: string
      required:
        - columns
        - rows
    Source:
      type: object
      description: Which dataset, and which attribute of it, a figure came from.
      properties:
        layerId:
          type: string
        layerName:
          type: string
        attribute:
          type: string
        label:
          type: string
      required:
        - layerId
        - layerName
  responses:
    Unauthorized:
      description: >-
        No credential (`unauthorized`), or a key that is unknown, expired or
        revoked (`invalid_key`). A bad key is not treated as anonymous: the
        `loci_sk_` prefix proves a key was meant.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            none:
              value:
                error:
                  code: unauthorized
                  message: sign in, or send an API key
            invalid:
              value:
                error:
                  code: invalid_key
                  message: that API key is not valid, has expired, or was revoked
    RateLimited:
      description: >-
        A budget is spent. `Retry-After` says how many seconds until the next
        request will pass.
      headers:
        Retry-After:
          schema:
            type: integer
          description: Seconds to wait.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: rate_limited
              message: too many requests — retry in 5s
    Internal:
      description: >-
        Something failed on the server. The message is deliberately vague; retry
        once.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: internal
              message: could not look up the frame
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: loci_sk_…
      description: >-
        An API key minted in the Developers tab of the LOCI app, sent as
        `Authorization: Bearer loci_sk_…`. Acts for one organisation, is
        read-only, and expires.

````

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