curl --request POST \
--url https://api.staging.loci.ng/v1/frames/insight \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"frameIds": [
"2163551"
],
"question": "What is here?"
}
'import requests
url = "https://api.staging.loci.ng/v1/frames/insight"
payload = {
"frameIds": ["2163551"],
"question": "What is here?"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({frameIds: ['2163551'], question: 'What is here?'})
};
fetch('https://api.staging.loci.ng/v1/frames/insight', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.staging.loci.ng/v1/frames/insight",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'frameIds' => [
'2163551'
],
'question' => 'What is here?'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.staging.loci.ng/v1/frames/insight"
payload := strings.NewReader("{\n \"frameIds\": [\n \"2163551\"\n ],\n \"question\": \"What is here?\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.staging.loci.ng/v1/frames/insight")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"frameIds\": [\n \"2163551\"\n ],\n \"question\": \"What is here?\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.staging.loci.ng/v1/frames/insight")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"frameIds\": [\n \"2163551\"\n ],\n \"question\": \"What is here?\"\n}"
response = http.request(request)
puts response.read_body{
"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"
]
}
}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.
curl --request POST \
--url https://api.staging.loci.ng/v1/frames/insight \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"frameIds": [
"2163551"
],
"question": "What is here?"
}
'import requests
url = "https://api.staging.loci.ng/v1/frames/insight"
payload = {
"frameIds": ["2163551"],
"question": "What is here?"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({frameIds: ['2163551'], question: 'What is here?'})
};
fetch('https://api.staging.loci.ng/v1/frames/insight', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.staging.loci.ng/v1/frames/insight",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'frameIds' => [
'2163551'
],
'question' => 'What is here?'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.staging.loci.ng/v1/frames/insight"
payload := strings.NewReader("{\n \"frameIds\": [\n \"2163551\"\n ],\n \"question\": \"What is here?\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.staging.loci.ng/v1/frames/insight")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"frameIds\": [\n \"2163551\"\n ],\n \"question\": \"What is here?\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.staging.loci.ng/v1/frames/insight")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"frameIds\": [\n \"2163551\"\n ],\n \"question\": \"What is here?\"\n}"
response = http.request(request)
puts response.read_body{
"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"
]
}
}Authorizations
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.
Body
The frames the question is about. At most 50.
1 - 50 elementsWhat to find out. Empty asks for the frames' profile.
1000A reply (the default) or a sectioned report.
answer, report Everyday words, or precise GIS terms. Inferred from the question when left out.
plain, gis Response
The answer. narrative is the model's wording when there is one; headline, facets and table are the computed figures it was checked against.
Show child attributes
Show child attributes