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

# Location profile

> Everything the datasets your key can read record at a point, in one call: a longitude and latitude in, the profile of that place out, named by dataset.

This is the recorded data of `GET /v1/frames/data` addressed by coordinate instead of frame id, for when what you hold is a device's position, a geocoded address or a tapped point on a map. The grid stays behind the curtain: the point is resolved to the cell that contains it to read what is recorded there, but the answer names no frame — only the point echoed back and the datasets. A caller who wants the frame itself, its id, shape and box, asks `GET /v1/frames/at`. Naming frames directly remains `GET /v1/frames/data`, unchanged; this call is an addition, not a replacement.

A **baseline** dataset, one keyed by frame, contributes its row at the point as `values`: the administrative identity, population, buildings, physical and cultural attributes Milsat records, and any baseline your organisation has added or licensed. A **feature** dataset, one of points, lines or polygons, contributes the features bound there as `features`, at most 200 per dataset — their properties carry the dataset's attributes, without the `_frame` marker `/v1/frames/data` adds. Feature datasets appear only for a key holding `feature.read`; a narrower key sees baselines alone.

An organisation's **connected sources** answer here too: databases it runs elsewhere — postal codes, sensor readings, availability, anything keyed to a place — queried live when the profile is asked, never copied in. Their datasets arrive with `kind: "live"`, an `asOf` timestamp, `values` for a single row or `rows` for several, and `stale: true` when the source was unreachable and the last good answer stands in. A connected source that cannot answer in time is named in `leftOut`, and the rest of the answer still arrives.

A dataset that records nothing at the location is simply absent from the list. A point outside the grid is a `404`: unlike a frame id, which answers empty, a coordinate always claims a place, and off the grid there is none.

A dataset licensed per query is charged once per request in which it contributed something, and its id is listed in `charged`. One the organisation's credits cannot cover is left out and named in `leftOut` with the reason, so the rest of the answer still arrives.

One location per request. `lng` is accepted as a spelling of `lon`. Needs `frame.read`.



## OpenAPI

````yaml /api-reference/openapi.json get /v1/location/profile
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, the profile of a coordinate in one
    call, and a worded answer about them.
  description: >-
    A small set of read-only calls under `/v1`, 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. When all you
    have is a coordinate, the **location profile** does the resolving for you:
    one call takes a longitude and latitude and returns everything recorded
    there.


    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.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.
  - name: Location
    description: A coordinate resolved to its place, and everything recorded there.
paths:
  /v1/location/profile:
    get:
      tags:
        - Location
      summary: Location profile
      description: >-
        Everything the datasets your key can read record at a point, in one
        call: a longitude and latitude in, the profile of that place out, named
        by dataset.


        This is the recorded data of `GET /v1/frames/data` addressed by
        coordinate instead of frame id, for when what you hold is a device's
        position, a geocoded address or a tapped point on a map. The grid stays
        behind the curtain: the point is resolved to the cell that contains it
        to read what is recorded there, but the answer names no frame — only the
        point echoed back and the datasets. A caller who wants the frame itself,
        its id, shape and box, asks `GET /v1/frames/at`. Naming frames directly
        remains `GET /v1/frames/data`, unchanged; this call is an addition, not
        a replacement.


        A **baseline** dataset, one keyed by frame, contributes its row at the
        point as `values`: the administrative identity, population, buildings,
        physical and cultural attributes Milsat records, and any baseline your
        organisation has added or licensed. A **feature** dataset, one of
        points, lines or polygons, contributes the features bound there as
        `features`, at most 200 per dataset — their properties carry the
        dataset's attributes, without the `_frame` marker `/v1/frames/data`
        adds. Feature datasets appear only for a key holding `feature.read`; a
        narrower key sees baselines alone.


        An organisation's **connected sources** answer here too: databases it
        runs elsewhere — postal codes, sensor readings, availability, anything
        keyed to a place — queried live when the profile is asked, never copied
        in. Their datasets arrive with `kind: "live"`, an `asOf` timestamp,
        `values` for a single row or `rows` for several, and `stale: true` when
        the source was unreachable and the last good answer stands in. A
        connected source that cannot answer in time is named in `leftOut`, and
        the rest of the answer still arrives.


        A dataset that records nothing at the location is simply absent from the
        list. A point outside the grid is a `404`: unlike a frame id, which
        answers empty, a coordinate always claims a place, and off the grid
        there is none.


        A dataset licensed per query is charged once per request in which it
        contributed something, and its id is listed in `charged`. One the
        organisation's credits cannot cover is left out and named in `leftOut`
        with the reason, so the rest of the answer still arrives.


        One location per request. `lng` is accepted as a spelling of `lon`.
        Needs `frame.read`.
      operationId: locationProfile
      parameters:
        - name: lat
          in: query
          required: true
          description: Latitude in degrees, −90 to 90.
          schema:
            type: number
          example: 9.0579
        - name: lon
          in: query
          required: true
          description: Longitude in degrees, −180 to 180. `lng` is accepted too.
          schema:
            type: number
          example: 7.4951
      responses:
        '200':
          description: >-
            The profile of the point: every readable dataset's record there,
            with the point echoed back.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/LocationProfile'
                required:
                  - data
              example:
                data:
                  location:
                    lon: 7.4951
                    lat: 9.0579
                  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_96ed00bcae6ecbe5
                      name: Cultural baseline
                      category: Cultural
                      kind: frames
                      orgId: org-milsat
                      values:
                        cul_ctethnfrm: '1'
                        cul_ctethnsta: '5'
                        cul_ctlangfrm: '1'
                        cul_ctlangsta: '8'
                        cul_ethnfrm: Gwandara
                        cul_ethnsta: Gbari, Gwandara, Igbira, Koro, Nupe
                        cul_langfrm: Gwandara
                        cul_langsta: >-
                          Ashe, Begbere-Ejar, Dibo, Gade, Gbagyi, Gbari,
                          Gwandara, Ija-Zuba
                        cul_rururb: Urban
                    - layerId: lyr_ee58d5508da410ea
                      name: Physical baseline
                      category: Physical
                      kind: frames
                      orgId: org-milsat
                      values:
                        phy_elevmean: '522.0260260260264'
                        phy_landcov: '6'
                        phy_sbsiltxtcls: '6'
                        phy_slopemean: '6.229817091881695'
                        phy_tpsiltxtcls: '9'
                    - 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'
        '400':
          description: >-
            `lat` or `lon` is missing, not a number, or out of range. Code
            `invalid_point`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: invalid_point
                  message: lon and lat are required, in degrees
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/InsufficientScope'
        '404':
          description: The point lies outside the grid. Code `not_found`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: not_found
                  message: no frame contains that point
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/Internal'
components:
  schemas:
    LocationProfile:
      type: object
      description: Everything recorded at a coordinate, with the point echoed back.
      properties:
        location:
          type: object
          description: The point asked about, as the server read it.
          properties:
            lon:
              type: number
            lat:
              type: number
          required:
            - lon
            - lat
        datasets:
          type: array
          items:
            $ref: '#/components/schemas/FrameDataset'
          description: >-
            Each readable dataset's record at the point, named by dataset —
            stored datasets and the organisation's connected sources (`kind:
            "live"`) alike. One that records nothing there is absent.
        charged:
          type: array
          items:
            type: string
          description: Ids of per-query datasets this request was charged for.
        leftOut:
          type: array
          items:
            type: object
            properties:
              layerId:
                type: string
              name:
                type: string
              reason:
                type: string
            required:
              - layerId
              - name
              - reason
          description: >-
            Datasets left out, each with the reason: a per-query licence the
            organisation's credits could not cover, or a connected source that
            could not answer in time.
      required:
        - location
        - datasets
    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
    FrameDataset:
      type: object
      description: >-
        One dataset's contribution to one frame: `values` for a baseline keyed
        by frame, `features` for a dataset of shapes.
      properties:
        layerId:
          type: string
        name:
          type: string
        category:
          type: string
        kind:
          type: string
          enum:
            - frames
            - project
            - baseline
            - live
          description: >-
            `frames` is a baseline keyed by frame; `project` is a dataset of
            points, lines or polygons; `live` is a dataset answered at request
            time from a database the organisation connected — nothing copied in.
            `live` appears only on the location profile.
        orgId:
          type: string
          description: The organisation that owns the dataset.
        values:
          type: object
          additionalProperties: true
          description: The baseline's row on this frame. Values are strings as stored.
        features:
          type: array
          items:
            $ref: '#/components/schemas/Feature'
          description: >-
            The features bound to this frame, at most 200 per dataset per
            request.
        rows:
          type: array
          items:
            type: object
            additionalProperties: true
          description: >-
            A `live` dataset's rows at the point, when it answers with more than
            one; a single row arrives as `values`.
        asOf:
          type: string
          format: date-time
          description: >-
            When a `live` dataset's answer was read from its source. Answers are
            memoised briefly between reads, each dataset on its own freshness
            window.
        stale:
          type: boolean
          description: >-
            Present and `true` on a `live` dataset when its source could not be
            reached just now and this is the last good answer, served past its
            freshness.
      required:
        - layerId
        - name
        - kind
        - orgId
    Feature:
      type: object
      description: >-
        A GeoJSON feature from one of your datasets. `properties` carries the
        feature's attributes, plus `_frame`, the frame it is bound to.
      properties:
        type:
          type: string
          const: Feature
        id:
          type: string
        geometry:
          $ref: '#/components/schemas/Geometry'
        properties:
          type: object
          additionalProperties: true
      required:
        - type
        - id
        - geometry
        - properties
    Geometry:
      type: object
      description: A GeoJSON geometry.
      properties:
        type:
          type: string
          example: Polygon
        coordinates:
          description: Positions as `[lon, lat]`, nested as GeoJSON requires for the type.
      required:
        - type
        - coordinates
  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
    InsufficientScope:
      description: >-
        The key does not carry `frame.read`. Every preset grants it, so this
        only arises on a key minted with an explicit capability list.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: insufficient_scope
              message: this API key does not carry the frame.read scope
    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.