Meta Fields
curl --request GET \
--url https://api.example.com/v1/meta/fieldsimport requests
url = "https://api.example.com/v1/meta/fields"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://api.example.com/v1/meta/fields', 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/meta/fields",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/v1/meta/fields"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.example.com/v1/meta/fields")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/meta/fields")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_bodyCatalog
GET /v1/meta/fields
Self-discovery: field + preset catalog with ETag-based caching.
GET
/
v1
/
meta
/
fields
Meta Fields
curl --request GET \
--url https://api.example.com/v1/meta/fieldsimport requests
url = "https://api.example.com/v1/meta/fields"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://api.example.com/v1/meta/fields', 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/meta/fields",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/v1/meta/fields"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.example.com/v1/meta/fields")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/meta/fields")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_bodyWhen to use this
Hit/v1/meta/fields once at startup to discover the catalog. The
response sets ETag and Cache-Control: public, max-age=3600, so a
long-lived agent or service fetches it once an hour at most. The catalog
drives:
- Client-side validation — reject unknown field names before sending
a
/v1/fetchrequest. - Tool descriptions — agents that build their own UI from the catalog populate field pickers and preset selectors from this endpoint.
- LLM planners — third-party agents that route to Mireye via the MCP
server can render the catalog into their own planner system prompt,
same as Mireye’s
/v1/askdoes internally.
Response shape
{
"billing": {
"fetch_credits_per_field": 1,
"metered_groups": {
"parcel_record": {
"credits_per_location_by_plan": {
"build": 300,
"free": 300,
"growth": 300,
"market": 150,
"scale": 150
},
"fields": [
"developable_acres_proxy",
"easement_acres_on_parcel",
"easement_fraction_of_parcel",
"onsite_solar_potential_mwac_high",
"onsite_solar_potential_mwac_low",
"parcel_address",
"parcel_apn",
"parcel_area_m2",
"parcel_boundary_geojson",
"parcel_data_source",
"parcel_geometry_wkt",
"parcel_id",
"parcel_match_distance_m",
"parcel_match_radius_m",
"parcel_match_type",
"parcel_owner",
"parcel_zoning",
"wetland_acres_on_parcel",
"wetland_fraction_of_parcel"
],
"note": "Per-record-licensed county parcel data. A request whose field selection includes ANY of these bills the group price ONCE per location \u2014 not once per field, because it is one purchased record \u2014 on top of fetch_credits_per_field for the other fields in the same request."
}
},
"note": "Credits for one location = fetch_credits_per_field x the non-group fields requested, plus each metered group's price once if any of its fields is requested. A batch multiplies that by the number of locations. Full price list: GET /v1/meta/plans.",
"unit": "credits"
},
"fields": [
{
"billing": {
"credits_per_location": 1,
"metered_group": null
},
"derivation": null,
"description": "Ground elevation above the NAVD88 vertical datum at the queried point. Sourced from the USGS 3DEP 1/3 arc-second seamless DEM (~10m), with the USGS EPQS dynamic service as a coverage fallback.",
"interpretation_hints": "Below 10m within 5km of MARINE coast \u2192 storm-surge exposure relevant, but coast_distance_m does NOT establish that the nearby shoreline is marine \u2014 it also measures tidal rivers and estuaries, so it reads a few metres at points tens of km inland (Philadelphia ~13 m, the Anacostia in DC ~215 m). Confirm the shoreline is genuinely marine before drawing the surge conclusion from this pair. Above 3000m \u2192 alpine permitting, snow loads. Combine with coast_distance_m and within_floodplain_polygon for flood-zone reasoning.",
"layer": "terrain",
"lifecycle": "stable",
"name": "elevation",
"null_meaning": null,
"nullable": false,
"presets": [
"terrain",
"flood_risk",
"wildfire_underwrite",
"site_selection",
"wind_siting"
],
"source": "USGS_3DEP",
"source_url": "https://www.usgs.gov/3d-elevation-program",
"ttl_seconds": 31536000,
"type": "float",
"unit": "meters"
}
/* ...365 more fields, ordered by layer */
],
"presets": {
"terrain": ["elevation", "slope_degrees", "aspect_cardinal", "coast_distance_m", "soil_drainage_class", "bedrock_depth_cm"],
"flood_risk": ["elevation", "coast_distance_m", "within_floodplain_polygon", "intersects_nhd_area", "intersects_wetland", "wetland_type", "wetland_subtype", "wetland_acres", "nearest_wetland_distance_m", "wetlands_within_100m_count", "wetlands_within_500m_count", "surface_water_permanence_pct", "nearest_waterbody_name"]
/* ...13 more presets */
},
"us_envelope": {
"lat_max": 72.0,
"lat_min": 18.0,
"lng_max": -65.0,
"lng_min": -180.0
},
"version": "0.16.0"
}
Self-discovery flow
The recommended client pattern:import httpx, time
BASE = "https://api.mireye.com"
class CatalogClient:
def __init__(self):
self._etag: str | None = None
self._catalog: dict | None = None
self._fetched_at: float = 0.0
async def get(self) -> dict:
# Refresh at most once an hour, even if our cache lies.
if self._catalog and time.time() - self._fetched_at < 3600:
return self._catalog
headers = {}
if self._etag:
headers["If-None-Match"] = self._etag
async with httpx.AsyncClient() as c:
r = await c.get(f"{BASE}/v1/meta/fields", headers=headers)
if r.status_code == 304:
self._fetched_at = time.time()
return self._catalog # unchanged
r.raise_for_status()
self._etag = r.headers["ETag"]
self._catalog = r.json()
self._fetched_at = time.time()
return self._catalog
If-None-Match returns 304 Not Modified with no body when the catalog
hasn’t changed. The ETag covers the whole payload, so any catalog
change — new fields, edited hints, preset changes — produces a fresh
ETag alongside the version bump (see the versioning
policy).
Each field object also carries honesty metadata: nullable marks fields
that can legitimately return null at valid coordinates, and
null_meaning says what such a null means (e.g. “no wetland within the
search radius”) so clients don’t misread semantic absence as a fetch
failure.
How the catalog drives the planner
Mireye’s own/v1/ask planner renders the catalog into its system
prompt:
You are a geospatial data planner. Given a US coordinate and a natural-
language question, pick the catalog fields needed to answer it.
Available fields:
elevation (terrain, float, meters) — Ground elevation above NAVD88...
Hint: Use this when the caller asks about elevation...
Presets: terrain, flood_risk, site_selection, wildfire_underwrite
slope_degrees (terrain, float, degrees) — Slope at the coordinate...
Hint: Spread potential for wildfire; higher = faster spread...
/* ... */
Presets (use one of these instead of listing fields if it fits):
flood_risk: elevation, coast_distance_m, within_floodplain_polygon, ...
/* ... */
Versioning
Theversion field is the wire-envelope protocol version from
catalog/taxonomy.yaml. Additive field, source, or preset growth does not
bump it; clients discover those additions through the catalog payload and its
ETag. The version changes only when the response envelope or another
wire-visible protocol contract changes.
Read the version from the JSON response body (there is no version response
header), cache the payload by ETag, and feature-detect field names instead of
assuming that one protocol version freezes catalog membership.
us_envelope remains the backwards-compatible coarse primary envelope. The
runtime validator also accepts the positive-longitude Western Aleutian
rectangle: lat ∈ [51, 54], lng ∈ [172, 180). Client-side validation
must accept either region.Response
200 - application/json
Successful Response