Fetch fields for up to 25 locations in one request
curl --request POST \
--url https://api.example.com/v1/fetch/batch \
--header 'Content-Type: application/json' \
--data '
{
"locations": [
{
"lat": 123,
"lng": 123,
"address": "<string>"
}
],
"fields": [
"<string>"
]
}
'import requests
url = "https://api.example.com/v1/fetch/batch"
payload = {
"locations": [
{
"lat": 123,
"lng": 123,
"address": "<string>"
}
],
"fields": ["<string>"]
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({locations: [{lat: 123, lng: 123, address: '<string>'}], fields: ['<string>']})
};
fetch('https://api.example.com/v1/fetch/batch', 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.example.com/v1/fetch/batch",
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([
'locations' => [
[
'lat' => 123,
'lng' => 123,
'address' => '<string>'
]
],
'fields' => [
'<string>'
]
]),
CURLOPT_HTTPHEADER => [
"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.example.com/v1/fetch/batch"
payload := strings.NewReader("{\n \"locations\": [\n {\n \"lat\": 123,\n \"lng\": 123,\n \"address\": \"<string>\"\n }\n ],\n \"fields\": [\n \"<string>\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
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.example.com/v1/fetch/batch")
.header("Content-Type", "application/json")
.body("{\n \"locations\": [\n {\n \"lat\": 123,\n \"lng\": 123,\n \"address\": \"<string>\"\n }\n ],\n \"fields\": [\n \"<string>\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/fetch/batch")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"locations\": [\n {\n \"lat\": 123,\n \"lng\": 123,\n \"address\": \"<string>\"\n }\n ],\n \"fields\": [\n \"<string>\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}Fetch
POST /v1/fetch/batch
The same field fetch for up to 25 locations in one request.
POST
/
v1
/
fetch
/
batch
Fetch fields for up to 25 locations in one request
curl --request POST \
--url https://api.example.com/v1/fetch/batch \
--header 'Content-Type: application/json' \
--data '
{
"locations": [
{
"lat": 123,
"lng": 123,
"address": "<string>"
}
],
"fields": [
"<string>"
]
}
'import requests
url = "https://api.example.com/v1/fetch/batch"
payload = {
"locations": [
{
"lat": 123,
"lng": 123,
"address": "<string>"
}
],
"fields": ["<string>"]
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({locations: [{lat: 123, lng: 123, address: '<string>'}], fields: ['<string>']})
};
fetch('https://api.example.com/v1/fetch/batch', 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.example.com/v1/fetch/batch",
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([
'locations' => [
[
'lat' => 123,
'lng' => 123,
'address' => '<string>'
]
],
'fields' => [
'<string>'
]
]),
CURLOPT_HTTPHEADER => [
"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.example.com/v1/fetch/batch"
payload := strings.NewReader("{\n \"locations\": [\n {\n \"lat\": 123,\n \"lng\": 123,\n \"address\": \"<string>\"\n }\n ],\n \"fields\": [\n \"<string>\"\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
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.example.com/v1/fetch/batch")
.header("Content-Type", "application/json")
.body("{\n \"locations\": [\n {\n \"lat\": 123,\n \"lng\": 123,\n \"address\": \"<string>\"\n }\n ],\n \"fields\": [\n \"<string>\"\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/fetch/batch")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"locations\": [\n {\n \"lat\": 123,\n \"lng\": 123,\n \"address\": \"<string>\"\n }\n ],\n \"fields\": [\n \"<string>\"\n ]\n}"
response = http.request(request)
puts response.read_body{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}When to use this
You have a list — candidate properties, a portfolio, a county’s worth of addresses — and you want the same screen over all of them. This is/v1/fetch applied to up to 25 locations in one
request: one field selection, N locations, index-aligned results.
If you have one location, use /v1/fetch. If your list is bigger than 25,
page it — the bound exists so one request cannot stampede the ~84 upstream
sources every location fans out to.
Request
curl -s https://api.mireye.com/v1/fetch/batch \
-H "Authorization: Bearer $MIREYE_API_TOKEN" \
-H 'content-type: application/json' \
-d '{
"locations": [
{"lat": 29.7604, "lng": -95.3698},
{"address": "350 5th Ave, New York, NY 10118"},
{"lat": 39.7392, "lng": -104.9903}
],
"fields": ["elevation", "coast_distance_m"]
}' | jq
locations is the exact locator contract of /v1/fetch:
lat+lng or address, never both. The field selection (fields and/or
preset) is batch-wide by design — the batch exists for “the same screen over
a list”, and per-location field lists would make the response shape
unpredictable for exactly the clients (agents, spreadsheets) it serves.
Response
{
"fetched_at": "2026-07-27T09:00:00+00:00",
"results": [
{"index": 0, "ok": true, "lat": 29.7604, "lng": -95.3698,
"fetched_at": "…", "fields": {"…": "…"}, "partial_failures": [],
"resolved_location": {"lat": 29.7604, "lng": -95.3698, "source": "coordinate"}},
{"index": 1, "ok": true, "lat": 40.748377, "lng": -73.984854,
"fetched_at": "…", "fields": {"…": "…"}, "partial_failures": [],
"geocode": {"accuracy_type": "rooftop", "…": "…"},
"resolved_location": {"lat": 40.748377, "lng": -73.984854, "source": "address"}},
{"index": 2, "ok": false,
"error": {"error": "coord_out_of_bounds", "message": "…", "retryable": false}}
]
}
- Results are index-aligned with the request.
results[i]answerslocations[i], always, and carriesindexexplicitly so a filtered or logged entry stays attributable. - Each
ok: trueentry is a/v1/fetchresponse body. Samefieldsshape, same tri-state fieldstatus, samepartial_failures, samegeocodeecho when the location was an address. A client that parses the single endpoint parses the batch with no new code. - A location’s failure is an entry, never an HTTP failure. Location #7’s
bad address cannot cost you the other 24 results: it becomes
{"ok": false, "error": {…}}with the same error object (error,message,retryable) the single endpoint would have returned for that location — one error-handling code path for both endpoints.
Two levels of partial failure
Don’t conflate them:- Entry-level (
ok: false): the location itself failed — the address didn’t geocode, the coordinate is outside the US envelope, or the worker shed the location at capacity (fetch_busy, retryable). - Field-level (
partial_failuresinside anok: trueentry): the location was fine, but individual fields failed upstream — identical semantics to/v1/fetch.
400 fields_unknown), no
fields (400 no_fields_requested), too many explicit fields
(400 fields_too_many), an over-long list (422), or a malformed locator
(422).
Limits
- 25 locations maximum per request (
422above it). - The
/v1/fetchfield rules apply unchanged: 50 explicit fields max, presets exempt. - Locations are processed 4 at a time inside the batch, and every location
counts against the same per-worker admission gate as a single
/v1/fetch— a batch is 25 requests’ worth of work and is metered as such, not smuggled under one admission slot. A location shed at capacity returns as anok: falsefetch_busyentry (retryable) rather than failing the batch. - Worst-case latency ≈ 90 s (every location cold, every source at its
budget). Typical is far lower, but set your client timeout to 120 s+,
same guidance as
/v1/ask. - Addresses bill one geocode lookup each (cache-served repeats are free, 30-day TTL), and metered sources (e.g. parcel lookups) bill per location — a 25-location batch with a parcel field is 25 metered calls.
- Batch is REST-only for now: the MCP
mireye_fetchtool stays single-location (agents iterate naturally), and MCP hosts impose their own tool-response size limits a 25-location payload would fight.
Retries and lost responses (Idempotency-Key)
Billing commits per location as it computes, and the response arrives as one JSON body at the end — so a response lost in transit (a gateway502, a
client read timeout) would otherwise mean paying for results you never
received. The Idempotency-Key header closes that gap:
curl -X POST https://api.mireye.com/v1/fetch/batch \
-H "Authorization: Bearer $MIREYE_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: site-screen-2026-08-07-run1" \
-d '{"locations": [...], "fields": ["elevation"]}'
- Shape: 8–128 characters of
[A-Za-z0-9_-]. Use a fresh key per distinct batch (a run id, a UUID). - On a lost response, retry with the SAME key and the SAME body. If the
original computation finished server-side, you get the already-computed
result back at zero additional charge, marked
"replayed": true. The stored result lives 24 hours. 409 batch_in_progress(withRetry-After): your retry raced the original computation — it is still running server-side. Wait and retry with the same key; do not switch keys (that recomputes and re-bills).409 idempotency_key_reused: the key was already used with a different request body. Keys pin the exact batch they answered.- Replays are free of credit charges but still count against your per-minute rate limit — a replay is a request.
- The header is optional; requests without it behave exactly as before.
POST /v1/runs (kind: fetch_batch) — submit, poll,
and collect results; it survives disconnects by design.Headers
Body
application/json
N locations, one field selection.
The field selection is deliberately batch-wide, not per-location: the batch exists for "the same screen over a list of candidate properties", and per-location field lists would make the response shape unpredictable for exactly the clients (agents, spreadsheets) the batch serves.
Required array length:
1 - 25 elementsShow child attributes
Show child attributes
Available options:
terrain, flood_risk, wildfire_underwrite, land_cover, site_selection, building_lookup, points_of_interest, utilities, boundaries, solar_siting, wind_siting, storage_siting, data_center_siting, grid_interconnect, natural_hazard Response
Successful Response