curl --request POST \
--url https://api.example.com/v1/fetch/quote \
--header 'Content-Type: application/json' \
--data '
{
"fields": [
"<string>"
],
"locations": 1
}
'import requests
url = "https://api.example.com/v1/fetch/quote"
payload = {
"fields": ["<string>"],
"locations": 1
}
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({fields: ['<string>'], locations: 1})
};
fetch('https://api.example.com/v1/fetch/quote', 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/quote",
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([
'fields' => [
'<string>'
],
'locations' => 1
]),
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/quote"
payload := strings.NewReader("{\n \"fields\": [\n \"<string>\"\n ],\n \"locations\": 1\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/quote")
.header("Content-Type", "application/json")
.body("{\n \"fields\": [\n \"<string>\"\n ],\n \"locations\": 1\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/fetch/quote")
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 \"fields\": [\n \"<string>\"\n ],\n \"locations\": 1\n}"
response = http.request(request)
puts response.read_body{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}POST /v1/fetch/quote
What a fetch would cost, and whether your allowance covers it — before you run it.
curl --request POST \
--url https://api.example.com/v1/fetch/quote \
--header 'Content-Type: application/json' \
--data '
{
"fields": [
"<string>"
],
"locations": 1
}
'import requests
url = "https://api.example.com/v1/fetch/quote"
payload = {
"fields": ["<string>"],
"locations": 1
}
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({fields: ['<string>'], locations: 1})
};
fetch('https://api.example.com/v1/fetch/quote', 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/quote",
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([
'fields' => [
'<string>'
],
'locations' => 1
]),
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/quote"
payload := strings.NewReader("{\n \"fields\": [\n \"<string>\"\n ],\n \"locations\": 1\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/quote")
.header("Content-Type", "application/json")
.body("{\n \"fields\": [\n \"<string>\"\n ],\n \"locations\": 1\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/fetch/quote")
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 \"fields\": [\n \"<string>\"\n ],\n \"locations\": 1\n}"
response = http.request(request)
puts response.read_body{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}When to use this
Before a big run, and before an agent starts a loop. Field prices are not uniform. Most fields cost 1 credit per location; the seventeen fields backed by per-record-licensed county parcel data cost 300 credits per location on free, build and growth (150 on scale and market) — charged once for the whole group, however many of its members you request. Five of those seventeen do not start withparcel_, so you cannot
tell from a field name:
wetland_acres_on_parcelandwetland_fraction_of_parcelclip wetland data to the parcel boundary, which means fetching the parcel.developable_acres_proxyand the twoonsite_solar_potential_mwac_*fields are derived from parcel inputs, so requesting one fetches the record.
Request
curl -s https://api.mireye.com/v1/fetch/quote \
-H "Authorization: Bearer $MIREYE_API_TOKEN" \
-H 'content-type: application/json' \
-d '{
"preset": "site_selection",
"locations": 25
}' | jq
fields / preset from the request you are about to send and add
locations — 1 for /v1/fetch, up to 25 for /v1/fetch/batch, or however
many a run (POST /v1/runs) will carry.
locations is a COUNT, not a list — an integer, unlike the locations
field on /v1/fetch/batch, which is the array of locators. Same name, different
type, because this endpoint prices a shape rather than resolving places. Sending
the batch’s list here gives int_type: Input should be a valid integer.
No coordinates. A fetch’s price depends on which fields you ask for and how
many locations you send, never on where those locations are, so this endpoint
does not accept them. (An address-form fetch bills the fields only — we absorb
the geocode.)
Response
{
"fields": ["elevation", "coast_distance_m", "parcel_owner"],
"field_count": 3,
"preset": null,
"locations": 25,
"plan": "build",
"credits_per_location": 302,
"credits_total": 7550,
"breakdown": {
"per_field": {"fields": 2, "credits_each": 1, "credits": 2},
"metered_groups": {
"parcel_record": {
"fields": ["parcel_owner"],
"credits": 300,
"billed": "once per location, however many of its fields you request"
}
}
},
"allowance": {
"credits_included": 25000,
"credits_used": 1200,
"credits_remaining": 23800,
"self_imposed_limit": null,
"effective_limit": 25000,
"resets_at": "2026-09-01T00:00:00+00:00",
"would_exceed_allowance": false,
"would_be_blocked": false
},
"notes": [
"1 of the 3 fields ride the metered parcel record (parcel_owner), which adds 300 credits per location on your plan — regardless of how many of them you request.",
"This request would take you past your included 25000 credits."
]
}
would_exceed_allowance and would_be_blocked answer different questions and
you should read both. Every plan stops at its included allowance: a request
that would land past it is refused with 402 credits_exhausted, on any tier.
(Overage is not billed — it is simply not served; your allowance resets on the
1st of each month, and upgrading raises it immediately.)
The 402 carries a Retry-After header as an HTTP-date, naming the same
instant as resets_at in the body — so a standard backoff layer has something
to read without parsing the body. It is a date rather than delta-seconds on
purpose: a reset is often weeks out, and a seven-figure second count is the kind
of value client libraries clamp into a wrong retry time. Note the header tells
you when the allowance returns; if you upgrade, you can retry immediately.
Selections containing a parcel-group field are additionally bounded by a
monthly pool shared across all customers. effective_limit already accounts
for everything, and limited_by tells you which ceiling is in force
(plan, self, or none).
For a batch, would_be_blocked does not mean the call fails. A batch is
billed per location, so one that straddles your ceiling returns 200:
locations_affordable run and are charged, and the rest come back as
per-location credits_exhausted entries.
Pricing and billing
- Quotes are free. They are authenticated (the answer depends on your plan and your month-to-date usage) but unmetered — an account that has run out still needs to be able to find out why. They do count against your per-minute rate limit.
- The quote is computed by the same code that charges you, so it cannot
drift from the bill. If a real fetch costs something other than the quoted
credits_per_location, that is a bug — report it. - The quote prices the request shape, which is the only honest pre-work
price. Fields that come back
status: "failed"are refunded after the fact, so a real run can cost less than quoted, never more.
Setting your own limit
Every plan already stops at its included allowance. A self-limit lets you stop EARLIER — a spend ceiling below your plan’s, for a project budget or a safety margin:curl -s -X PATCH https://api.mireye.com/v1/users/me/settings \
-H "Authorization: Bearer $MIREYE_FIREBASE_ID_TOKEN" \
-H 'content-type: application/json' \
-d '{"monthly_credit_limit": 50000}' | jq .settings
- It can only tighten. A limit above your plan’s allowance does not let you exceed the allowance, and it can never widen the per-account bound we apply to shared vendor data (parcel records, drive-time matrices).
- Set
0to stop all metered requests — a deliberate pause, not a mistake. It takes effect fleet-wide within seconds, not on a cache expiry. - Set
nullto remove the limit. - It applies to your whole account, across every metered endpoint, and resets with the billing period.
- Lowering it below what you have already used blocks further calls; it does not retroactively undo or refund anything.
402 credits_exhausted with
self_imposed_limit set to your number, so a client can tell “you configured
this” apart from “your plan ran out” — the remedies are different, and upgrading
does nothing for a self-imposed limit. /v1/fetch/quote predicts it too:
allowance.effective_limit is whichever ceiling is actually in force.
Related
GET /v1/meta/fieldscarries abillingblock per field and a top-level price list, if you would rather price things yourself.GET /v1/meta/planshas the full credit price list for every endpoint.
Body
What a /v1/fetch or /v1/fetch/batch request WOULD cost.
Subclasses _FieldSelection on purpose: a fetch's price is a function of
the resolved field list and the number of locations, and of nothing else.
Coordinates are therefore not accepted — not as an omission but as the
honest model. Taking them would imply the price varies by WHERE, invite
addresses into a payload that has no use for them, and tempt this endpoint
into a metered geocode to price something geocoding does not affect (an
address-form fetch bills the fields only; we absorb the geocode).
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 How many locations the real request would carry. 1 for /v1/fetch, up to the batch maximum for /v1/fetch/batch. A run (POST /v1/runs) prices the same way per location.
1 <= x <= 25Response
Successful Response