Thirteen recipes against the live API, in curl, Python, JavaScript and TypeScript. Pick a language once and every recipe switches. Anything with a Free badge needs no key.
Run a free one. Recipes 1, 7 and 12 return real data with no key.
Read the price. Every paid pull has a free preview that shows the exact cost.
Add your key. Paid examples read GETFIXN_KEY. Keep the key out of code you share.
Building a bot? Prediction-market signals are recipe 12. Paying from a crypto wallet is recipe 13.
Language
Setup: Python 3.9+ with pip install requests. Node 18+ for JavaScript. TypeScript runs on Node 22.6+ (node file.ts) or with tsx. Paid examples read GETFIXN_KEY from the environment: export GETFIXN_KEY=tfx_live_...
01 / RECIPE
Your first call
Free, no key
Ask the API what it can do, then ask for live weather at one spot. No key, no signup. If this prints a temperature, your setup works.
Calling with no parameters returns the free menu: every dataset, its example call and its price. Previews are free and limited to 60 requests per minute per IP.
curl -s https://data.truefixr.com/v1/data | head -c 400curl -s "https://data.truefixr.com/v1/data?dataset=weather&lat=32.78&lon=-96.80"
import requests # pip install requests
BASE = "https://data.truefixr.com/v1/data"
menu = requests.get(BASE).json() # no params = the free menuprint(list(menu["datasets"]))
wx = requests.get(BASE, params={"dataset": "weather", "lat": 32.78, "lon": -96.80}).json()
print(wx["temperature_f"], wx["nowcast"]["raining_now"], wx["alerts"], wx["billed"])
const BASE = "https://data.truefixr.com/v1/data";
const get = async (params = {}) => (await fetch(`${BASE}?${new URLSearchParams(params)}`)).json();
const menu = await get(); // no params = the free menu
console.log(Object.keys(menu.datasets));
const wx = await get({ dataset: "weather", lat: 32.78, lon: -96.8 });
console.log(wx.temperature_f, wx.nowcast.raining_now, wx.alerts, wx.billed);
Reported and radar-detected storms matched to street addresses. The free preview says how many addresses match and what the pull would cost. Look at that before you pay, and cap the spend with limit.
Price: $0.05 per address. A paid pull returns at most 2,000 addresses unless you raise limit (up to 5,000), so read estimated_cost_usd first. The preview is free and needs no key. The paid half only runs when GETFIXN_KEY is set.
# free preview: counts and the exact pricecurl -s -G https://data.truefixr.com/v1/data \
-d dataset=storms -d county=Loving -d state=TX -d peril=HAIL -d days=90# paid pull (needs a key): 25 addresses at most = $1.25 at mostcurl -s -G https://data.truefixr.com/v1/data \
-d dataset=storms -d county=Dallas -d state=TX -d peril=HAIL -d days=90 \
-d addresses=true -d limit=25 \
-H "Authorization: Bearer $GETFIXN_KEY"
import os, requests
BASE = "https://data.truefixr.com/v1/data"
q = {"dataset": "storms", "county": "Loving", "state": "TX", "peril": "HAIL", "days": 90}
preview = requests.get(BASE, params=q).json() # freeprint(preview["total_leads"], "addresses, cost $", preview["estimated_cost_usd"])
key = os.environ.get("GETFIXN_KEY") # paid half only runs with a keyif key and preview["total_leads"]:
r = requests.get(BASE, params={**q, "addresses": "true", "limit": 25},
headers={"Authorization": f"Bearer {key}"})
print(r.status_code, str(r.json())[:300])
{
"dataset": "storms",
"county": "dallas",
"state": "TX",
"total_leads": 1244040,
"storm_types": { "HEAVY_RAIN": 1307242, "THUNDERSTORM_WIND": 88497 },
"repeat_addresses": 117589,
"estimated_cost_usd": 100.0
} // free preview, trimmed. Dallas figures are from the API docs.
03 / RECIPE
Storm History by Address within 1 to 25 miles of a point
Free preview, paid pull needs a key
Send a latitude and longitude (or a street address) and a radius. The free preview counts the report lines inside the circle by kind of storm and gives the exact price.
radius_mi runs from 1 to 25 and defaults to 5. The preview is free. The full report is $0.05 per line with a $10 minimum, and it is one report with all 11 event categories. Add addresses=true and your key to buy it.
{
"dataset": "history",
"radius_mi": 10.0,
"real_line_count": 994,
"estimated_cost_usd": 49.7,
"by_peril": { "hail": 223, "wind": 414, "flood": 71, "lightning": 23, "tornado": 24, "hurricane": 27, "winter": 139, "...": "..." }
} // free preview, trimmed, real output for this point on 2026-10-03
04 / RECIPE
Storm History by Address by date of loss
Free preview, paid pull needs a key
Was there reported weather near an address on a given day? Add a date and the free preview answers yes or no. The paid report adds the events just before and after, with a reference ID you can cite.
The yes or no depends on the radius you set (default 5 miles). A storm record is reported and radar-detected weather near the address, not an inspection or a damage assessment. Paid report: $0.05 per line, $10 minimum.
curl -s -G https://data.truefixr.com/v1/data \
-d dataset=history \
--data-urlencode "address=891 Lake Hollow Blvd SW, Marietta, GA 30064" \
-d date_of_loss=2026-04-14
import requests
r = requests.get("https://data.truefixr.com/v1/data", params={
"dataset": "history",
"address": "891 Lake Hollow Blvd SW, Marietta, GA 30064",
"date_of_loss": "2026-04-14",
}).json()
print(r["date_of_loss_supported"], "-", r["answer"])
print("full report would cost $", r["estimated_cost_usd"])
const q = new URLSearchParams({
dataset: "history",
address: "891 Lake Hollow Blvd SW, Marietta, GA 30064",
date_of_loss: "2026-04-14",
});
const r = await (await fetch(`https://data.truefixr.com/v1/data?${q}`)).json();
console.log(r.date_of_loss_supported, "-", r.answer);
console.log("full report would cost $" + r.estimated_cost_usd);
type Dol = { date_of_loss_supported: boolean; answer: string; estimated_cost_usd: number };
const q = new URLSearchParams({
dataset: "history",
address: "891 Lake Hollow Blvd SW, Marietta, GA 30064",
date_of_loss: "2026-04-14",
});
const r = (await (await fetch(`https://data.truefixr.com/v1/data?${q}`)).json()) as Dol;
console.log(r.date_of_loss_supported, "-", r.answer);
console.log("full report would cost $" + r.estimated_cost_usd);
response, trimmed
{
"geocoded_address": "891 Lake Hollow Blvd SW, Marietta, GA 30064, USA",
"radius_mi": 5.0,
"real_line_count": 362,
"estimated_cost_usd": 18.1,
"date_of_loss": "2026-04-14",
"date_of_loss_supported": false,
"answer": "No severe-weather event is on file within 5.0 miles of this address on 2026-04-14."
}
05 / RECIPE
ML Storm Forecast by Address, 24 hours to 10 days
Free preview, paid pull needs a key
ML Storm Forecast by Address (dataset=risk) uses a separate machine-learning model for each of 10 perils. A paid pull returns addresses in the forecast footprint, each with structure replacement value (not market value), people, storm type, forecast time and a risk label. Forecasts show what could happen, not what will. The calls below show the free county grade teaser and the paid county forecast status batch, a county-level screen, not an address-level forecast.
Address-level paid pull: dataset=risk with addresses=true, $0.05 per address, windows 24h, 48h, 3d, 5d, 7d, 10d, add limit=10 for a 50 cent sample. Free: dataset=address returns current_forecast_risk, and dataset=risk without addresses returns the preview. Batch: POST /v1/portfolio/risk, $0.05 per location, up to 500 per call. Forecasts refresh 4 times a day; check data_age_hours.
# free: this address's past events plus the county forecast gradecurl -s -G https://data.truefixr.com/v1/data -d dataset=address -d lat=32.78 -d lon=-96.80# free: ML Storm Forecast by Address preview for a window (24h, 48h, 3d, 5d, 7d, 10d)curl -s -G https://data.truefixr.com/v1/data -d dataset=risk -d county=Dallas -d state=TX -d window=10d
# paid (needs a key): $0.05 per locationcurl -s -X POST https://data.truefixr.com/v1/portfolio/risk \
-H "Authorization: Bearer $GETFIXN_KEY" -H "Content-Type: application/json" \
-d '{"locations":[{"lat":32.78,"lon":-96.80,"label":"Dallas yard"}],"window":"10d"}'
import os, requests
BASE = "https://data.truefixr.com/v1/data"
a = requests.get(BASE, params={"dataset": "address", "lat": 32.78, "lon": -96.80}).json()
g = a["current_forecast_risk"]
print("free:", g["grade"], g["grade_label"], "| past events here:", a["storm_event_count"])
key = os.environ.get("GETFIXN_KEY") # paid half only runs with a keyif key:
r = requests.post("https://data.truefixr.com/v1/portfolio/risk",
headers={"Authorization": f"Bearer {key}"},
json={"locations": [{"lat": 32.78, "lon": -96.80, "label": "Dallas yard"}], "window": "10d"})
print(r.status_code, r.json())
const a = await (await fetch("https://data.truefixr.com/v1/data?dataset=address&lat=32.78&lon=-96.80")).json();
const g = a.current_forecast_risk;
console.log("free:", g.grade, g.grade_label, "| past events here:", a.storm_event_count);
const key = process.env.GETFIXN_KEY; // paid half only runs with a keyif (key) {
const r = await fetch("https://data.truefixr.com/v1/portfolio/risk", {
method: "POST",
headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
body: JSON.stringify({ locations: [{ lat: 32.78, lon: -96.8, label: "Dallas yard" }], window: "10d" }),
});
console.log(r.status, await r.json());
}
type Addr = { storm_event_count: number; current_forecast_risk: { grade: "A" | "B" | "C" | "D" | "F"; grade_label: string } };
const a = (await (await fetch("https://data.truefixr.com/v1/data?dataset=address&lat=32.78&lon=-96.80")).json()) as Addr;
console.log("free:", a.current_forecast_risk.grade, a.current_forecast_risk.grade_label, "| past events here:", a.storm_event_count);
const key = process.env.GETFIXN_KEY; // paid half only runs with a keyif (key) {
const r = await fetch("https://data.truefixr.com/v1/portfolio/risk", {
method: "POST",
headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
body: JSON.stringify({ locations: [{ lat: 32.78, lon: -96.8, label: "Dallas yard" }], window: "10d" }),
});
console.log(r.status, await r.json());
}
One property's storm past, counted by kind of storm: events nearby since 2003. The free preview gives the counts. The paid score adds the most recent event date and the years since it, per peril.
Paid: $0.50 per property, flat, no matter how many events. Hail and wind also carry a most-severe-event size. Flood, lightning and tornado are count only.
# free previewcurl -s -G https://data.truefixr.com/v1/data -d dataset=hazard_score \
--data-urlencode "address=891 Lake Hollow Blvd SW, Marietta, GA 30064"# paid ($0.50, needs a key)curl -s -G https://data.truefixr.com/v1/data -d dataset=hazard_score -d addresses=true \
--data-urlencode "address=891 Lake Hollow Blvd SW, Marietta, GA 30064" \
-H "Authorization: Bearer $GETFIXN_KEY"
import requests
p = requests.get("https://data.truefixr.com/v1/data", params={
"dataset": "hazard_score",
"address": "891 Lake Hollow Blvd SW, Marietta, GA 30064",
}).json()
print(p["geocoded_address"], "|", p["total_events"], "events since 2003")
for peril, v in sorted(p["by_peril"].items(), key=lambda kv: -kv[1]["event_count"])[:5]:
print(f"{peril:<13}{v['event_count']}")
# add "addresses": "true" plus an Authorization header for the paid $0.50 score
const q = new URLSearchParams({ dataset: "hazard_score", address: "891 Lake Hollow Blvd SW, Marietta, GA 30064" });
const p = await (await fetch(`https://data.truefixr.com/v1/data?${q}`)).json();
console.log(p.geocoded_address, "|", p.total_events, "events since 2003");
Object.entries(p.by_peril).sort((a, b) => b[1].event_count - a[1].event_count).slice(0, 5)
.forEach(([peril, v]) => console.log(peril.padEnd(13), v.event_count));
// add addresses: "true" plus an Authorization header for the paid $0.50 score
type Hazard = { geocoded_address: string; total_events: number; by_peril: Record<string, { event_count: number }> };
const q = new URLSearchParams({ dataset: "hazard_score", address: "891 Lake Hollow Blvd SW, Marietta, GA 30064" });
const p = (await (await fetch(`https://data.truefixr.com/v1/data?${q}`)).json()) as Hazard;
console.log(p.geocoded_address, "|", p.total_events, "events since 2003");
Object.entries(p.by_peril).sort((a, b) => b[1].event_count - a[1].event_count).slice(0, 5)
.forEach(([peril, v]) => console.log(peril.padEnd(13), v.event_count));
// add addresses: "true" plus an Authorization header for the paid $0.50 score
Model-Based Weather and Hourly Forecast by Address
Free, no key
Current conditions and any active severe weather alerts for a US location. The lean response is free with no key. Add hours_ahead (0 to 48) for Model-Based Hourly Forecast by Address, $0.0002 per call with details=true.
Free lean response has about 10 fields. details=true returns the full detail pull at $0.0002 per call and needs a key. Batch version: POST /v1/weather/batch, up to 500 locations, $0.0002 each. Not affiliated with NOAA or the NWS.
Save your places as a CSV with lat, lon and label columns and send up to 500 rows per call. The script prints the row count and the price first, and only pays when you add --go.
POST /v1/weather/batch is $0.0002 per location. POST /v1/portfolio/risk is $0.05 per location. Max 500 locations per call, so split bigger files. The paid half needs GETFIXN_KEY. The response shape (results list) comes from the API's own documentation; check one small call before a big file.
import csv, os, sys, requests
rows = list(csv.DictReader(open("places.csv"))) # columns: lat,lon,label
locs = [{"lat": float(r["lat"]), "lon": float(r["lon"]), "label": r.get("label", "")} for r in rows]
print(f"{len(locs)} locations x $0.0002 = ${len(locs) * 0.0002:.4f}")
if"--go"notin sys.argv ornot os.environ.get("GETFIXN_KEY"):
sys.exit("dry run. Set GETFIXN_KEY and add --go to pay.")
out = []
for i in range(0, len(locs), 500): # 500 per call is the maximum
r = requests.post("https://data.truefixr.com/v1/weather/batch",
headers={"Authorization": "Bearer " + os.environ["GETFIXN_KEY"]},
json={"locations": locs[i:i + 500]})
r.raise_for_status()
out += r.json()["results"]
print(len(out), "results")
import { readFileSync } from"node:fs";
const [head, ...lines] = readFileSync("places.csv", "utf8").trim().split(/\r?\n/); // lat,lon,labelconst cols = head.split(",");
const locs = lines.map((l) => Object.fromEntries(l.split(",").map((v, i) => [cols[i], v])))
.map((r) => ({ lat: +r.lat, lon: +r.lon, label: r.label ?? "" }));
console.log(`${locs.length} locations x $0.0002 = $${(locs.length * 0.0002).toFixed(4)}`);
if (!process.argv.includes("--go") || !process.env.GETFIXN_KEY) { console.log("dry run. Set GETFIXN_KEY and add --go to pay."); process.exit(0); }
const out = [];
for (let i = 0; i < locs.length; i += 500) { // 500 per call is the maximumconst r = await fetch("https://data.truefixr.com/v1/weather/batch", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.GETFIXN_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ locations: locs.slice(i, i + 500) }),
});
if (!r.ok) thrownew Error(`HTTP ${r.status}`);
out.push(...(await r.json()).results);
}
console.log(out.length, "results");
import { readFileSync } from"node:fs";
type Loc = { lat: number; lon: number; label: string };
const [head, ...lines] = readFileSync("places.csv", "utf8").trim().split(/\r?\n/); // lat,lon,labelconst cols = head.split(",");
const locs: Loc[] = lines.map((l) => Object.fromEntries(l.split(",").map((v, i) => [cols[i], v])))
.map((r) => ({ lat: +r.lat, lon: +r.lon, label: r.label ?? "" }));
console.log(`${locs.length} locations x $0.0002 = $${(locs.length * 0.0002).toFixed(4)}`);
if (!process.argv.includes("--go") || !process.env.GETFIXN_KEY) { console.log("dry run. Set GETFIXN_KEY and add --go to pay."); process.exit(0); }
const out: unknown[] = [];
for (let i = 0; i < locs.length; i += 500) { // 500 per call is the maximumconst r = await fetch("https://data.truefixr.com/v1/weather/batch", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.GETFIXN_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ locations: locs.slice(i, i + 500) }),
});
if (!r.ok) thrownew Error(`HTTP ${r.status}`);
out.push(...((await r.json()) as { results: unknown[] }).results);
}
console.log(out.length, "results");
response, trimmed
{
"results": [
{ "lat": 30.27, "lon": -97.74, "county": "Travis", "state": "TX", "grade": "B" }
]
} // portfolio/risk shape from the API docs. Paid calls were not run for this page.
09 / RECIPE
Errors, 402 balance-low and spend caps
Free, no key
A paid call without a valid key or enough balance does not return 401. It returns 402 with the price in the body. Catch it, stop, and tell the user to top up. There is no cursor in the API: size your pulls with limit and narrow with filters.
400: missing or bad parameter, read detail. 402: payment required, the price is accepts[0].amount in atomic USDC (divide by 1,000,000). 422: wrong type, see loc. 429: more than 60 free previews a minute per IP. This snippet is safe to run: the unpaid batch call is refused before anything is charged.
# an unpaid paid-route call: HTTP 402 and the price, nothing is chargedcurl -s -w "\nHTTP %{http_code}\n" -X POST https://data.truefixr.com/v1/weather/batch \
-H "Content-Type: application/json" -d '{"locations":[{"lat":32.78,"lon":-96.80}]}'
import time, requests
class PaymentRequired(Exception):
pass
def call(method, url, retries=3, **kw):
for attempt in range(retries + 1):
r = requests.request(method, url, timeout=60, **kw)
if r.ok:
return r.json()
if r.status_code == 402: # no valid key or balance too low
body = r.json()
try: # x402 v2: amount is atomic USDC, 6 decimals
price = int(body["accepts"][0]["amount"]) / 1_000_000except (KeyError, IndexError, TypeError, ValueError):
price = (body.get("for_ai_agents") or {}).get("price_usd") # legacy bodyraise PaymentRequired(f"402 payment required: this call costs ${price}. Add a valid key or fund your balance.")
if r.status_code in (429, 502, 503, 504) and attempt < retries:
time.sleep(5 * 2 ** attempt) # back off, then retrycontinue
r.raise_for_status() # 400/422: read r.json()["detail"]try:
call("POST", "https://data.truefixr.com/v1/weather/batch", json={"locations": [{"lat": 32.78, "lon": -96.80}]})
except PaymentRequired as e:
print(e)
class PaymentRequired extends Error {}
asyncfunction call(url, init = {}, retries = 3) {
for (let attempt = 0; ; attempt++) {
const r = await fetch(url, init);
if (r.ok) return r.json();
if (r.status === 402) { // no valid key or balance too lowconst body = await r.json();
const price = Number(body.accepts?.[0]?.amount) / 1e6 || body.for_ai_agents?.price_usd; // atomic USDC, legacy fallbackthrownew PaymentRequired(`402 payment required: this call costs $${price}. Add a valid key or fund your balance.`);
}
if ([429, 502, 503, 504].includes(r.status) && attempt < retries) {
awaitnew Promise((ok) => setTimeout(ok, 5000 * 2 ** attempt)); // back off, then retrycontinue;
}
thrownew Error(`HTTP ${r.status}: ${await r.text()}`); // 400/422: read detail
}
}
try {
await call("https://data.truefixr.com/v1/weather/batch", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ locations: [{ lat: 32.78, lon: -96.8 }] }),
});
} catch (e) {
if (e instanceof PaymentRequired) console.log(e.message); elsethrow e;
}
class PaymentRequired extends Error {}
asyncfunction call<T>(url: string, init: RequestInit = {}, retries = 3): Promise<T> {
for (let attempt = 0; ; attempt++) {
const r = await fetch(url, init);
if (r.ok) return (await r.json()) as T;
if (r.status === 402) { // no valid key or balance too lowconst body = (await r.json()) as { accepts?: { amount?: string }[]; for_ai_agents?: { price_usd?: number } };
const price = Number(body.accepts?.[0]?.amount) / 1e6 || body.for_ai_agents?.price_usd; // atomic USDC, legacy fallbackthrownew PaymentRequired(`402 payment required: this call costs $${price}. Add a valid key or fund your balance.`);
}
if ([429, 502, 503, 504].includes(r.status) && attempt < retries) {
awaitnew Promise((ok) => setTimeout(ok, 5000 * 2 ** attempt)); // back off, then retrycontinue;
}
thrownew Error(`HTTP ${r.status}: ${await r.text()}`); // 400/422: read detail
}
}
try {
await call("https://data.truefixr.com/v1/weather/batch", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ locations: [{ lat: 32.78, lon: -96.8 }] }),
});
} catch (e) {
if (e instanceof PaymentRequired) console.log(e.message); elsethrow e;
}
response, trimmed
HTTP 402
{
"x402Version": 2,
"error": "Payment required",
"accepts": [{ "scheme": "exact", "network": "eip155:8453", "amount": "200", "payTo": "0xFf23...", "maxTimeoutSeconds": 300 }],
"extensions": { /* ... */ }
}
// amount "200" = $0.0002. The same object is in the PAYMENT-REQUIRED header (base64 JSON).
10 / RECIPE
Caching
Free, no key
Don't pay twice for the same answer. Forecasts refresh 4 times a day and live weather hourly, so even a short cache saves calls. Key the cache on the full request URL.
Pick a time-to-live that matches the data: minutes for weather, an hour or more for county forecast grades, a day for history. Cache only what your terms allow you to store.
# reuse a saved answer if it is under 60 minutes old
f=/tmp/dallas-risk.json
if [ -z "$(find "$f" -mmin -60 2>/dev/null)" ]; thencurl -s -G https://data.truefixr.com/v1/data -d dataset=risk -d county=Dallas -d state=TX -o "$f"echo"fetched"elseecho"from cache"fihead -c 200"$f"; echo
import time, requests
_cache = {}
def cached_get(params, ttl=3600):
key = tuple(sorted(params.items()))
hit = _cache.get(key)
if hit and time.time() - hit[0] < ttl:
return hit[1], True
data = requests.get("https://data.truefixr.com/v1/data", params=params).json()
_cache[key] = (time.time(), data)
return data, False
q = {"dataset": "risk", "county": "Dallas", "state": "TX"}
for _ in range(2):
data, from_cache = cached_get(q)
print(data["grade"], "from cache"if from_cache else"fetched")
const cache = new Map<string, { t: number; data: unknown }>();
asyncfunction cachedGet<T>(params: Record<string, string>, ttlMs = 3_600_000): Promise<[T, boolean]> {
const url = `https://data.truefixr.com/v1/data?${new URLSearchParams(params)}`;
const hit = cache.get(url);
if (hit && Date.now() - hit.t < ttlMs) return [hit.data as T, true];
const data = (await (await fetch(url)).json()) as T;
cache.set(url, { t: Date.now(), data });
return [data, false];
}
const q = { dataset: "risk", county: "Dallas", state: "TX" };
for (let i = 0; i < 2; i++) {
const [data, fromCache] = await cachedGet<{ grade: string }>(q);
console.log(data.grade, fromCache ? "from cache" : "fetched");
}
response, trimmed
first call: fetched from the API
second call: served from cache (0 requests, $0)
11 / RECIPE
Simple rate limiting
Free, no key
Free previews are limited to 60 requests a minute per IP address. If you loop over many places, add a delay so you never see a 429. One call a second stays well under the limit.
A key is not subject to the free-preview limit in the docs, but a delay is still polite and makes costs easy to watch. If you do get a 429, wait a minute and retry (see the errors recipe).
for state in TX FL GA; docurl -s -G https://data.truefixr.com/v1/data -d dataset=coverage -d state=$state | head -c 60; echosleep1# one call per seconddone
import time, requests
MIN_GAP = 1.0# seconds between calls
_last = 0.0def throttled_get(params):
global _last
wait = MIN_GAP - (time.time() - _last)
if wait > 0:
time.sleep(wait)
_last = time.time()
return requests.get("https://data.truefixr.com/v1/data", params=params).json()
for state in ["TX", "FL", "GA"]:
r = throttled_get({"dataset": "coverage", "state": state})
print(state, len(r["storms_counties"]), "counties with storm data")
let last = 0;
const MIN_GAP = 1000; // ms between callsasyncfunction throttledGet(params) {
const wait = MIN_GAP - (Date.now() - last);
if (wait > 0) awaitnew Promise((ok) => setTimeout(ok, wait));
last = Date.now();
return (await fetch(`https://data.truefixr.com/v1/data?${new URLSearchParams(params)}`)).json();
}
for (const state of ["TX", "FL", "GA"]) {
const r = await throttledGet({ dataset: "coverage", state });
console.log(state, r.storms_counties.length, "counties with storm data");
}
let last = 0;
const MIN_GAP = 1000; // ms between callsasyncfunction throttledGet<T>(params: Record<string, string>): Promise<T> {
const wait = MIN_GAP - (Date.now() - last);
if (wait > 0) awaitnew Promise((ok) => setTimeout(ok, wait));
last = Date.now();
return (await (await fetch(`https://data.truefixr.com/v1/data?${new URLSearchParams(params)}`)).json()) as T;
}
for (const state of ["TX", "FL", "GA"]) {
const r = await throttledGet<{ storms_counties: string[] }>({ dataset: "coverage", state });
console.log(state, r.storms_counties.length, "counties with storm data");
}
response, trimmed
TX 254 counties with storm data
FL 67 counties with storm data
GA 157 counties with storm data // one call per second
12 / RECIPE
Kalshi-style weather contracts: a signals file for your own model
Free, no key
Pull the forecast and live weather for the cities you trade and write one CSV row per city. Your model reads the file. This recipe has no order code in it and never will. It only fetches weather.
Per city it saves temperature, rain and gusts now, 24 hours out and 48 hours out (hours_ahead, a point forecast), the number of active severe alerts, and the county storm grade for 24h and 48h. Swap in the coordinates of the stations or cities your contracts name. A contract settles on whatever source its rules name, so read those rules. Our numbers are an input to your model, not the settlement source. About 1 second between calls, so 4 cities take under a minute. Everything here is on the free tier, so it costs nothing.
GetFixn sells data, not trading advice. Nothing here is investment advice. Forecasts can be wrong. You are responsible for your own strategies and for following the rules of any exchange or market you use. No exchange endorses or partners with GetFixn.
# one city, three forecast hours. Loop over your own cities in the shell.for h in02448; docurl -s "https://data.truefixr.com/v1/data?dataset=weather&lat=40.78&lon=-73.97&hours_ahead=$h" \
| python3 -c 'import sys,json; w=json.load(sys.stdin); print(w["valid_time"], w["temperature_f"], w["precip_in"], w["wind_gust_mph"])'
sleep1done
curl -s "https://data.truefixr.com/v1/data?dataset=risk&lat=40.78&lon=-73.97&window=24h" \
| python3 -c 'import sys,json; print("storm grade 24h:", json.load(sys.stdin)["grade"])'
import csv, time, requests # pip install requests
BASE = "https://data.truefixr.com/v1/data"
CITIES = {"NYC": (40.78, -73.97), "CHI": (41.88, -87.63), "MIA": (25.79, -80.29), "AUS": (30.27, -97.74)}
HOURS = (0, 24, 48)
def get(**params):
r = requests.get(BASE, params=params, timeout=30)
r.raise_for_status()
return r.json()
rows = []
for city, (lat, lon) in CITIES.items():
row = {"city": city, "lat": lat, "lon": lon}
for h in HOURS:
wx = get(dataset="weather", lat=lat, lon=lon, hours_ahead=h)
row[f"temp_f_{h}h"] = wx["temperature_f"]
row[f"precip_in_{h}h"] = wx["precip_in"]
row[f"gust_mph_{h}h"] = wx["wind_gust_mph"]
time.sleep(1) # stay under 60 free calls a minute
row["alerts_now"] = len(get(dataset="weather", lat=lat, lon=lon)["alerts"])
row["storm_grade_24h"] = get(dataset="risk", lat=lat, lon=lon, window="24h")["grade"]
row["storm_grade_48h"] = get(dataset="risk", lat=lat, lon=lon, window="48h")["grade"]
rows.append(row)
with open("signals.csv", "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=rows[0])
w.writeheader()
w.writerows(rows)
print(open("signals.csv").read())
import { writeFile } from"node:fs/promises";
const BASE = "https://data.truefixr.com/v1/data";
const CITIES = { NYC: [40.78, -73.97], CHI: [41.88, -87.63], MIA: [25.79, -80.29], AUS: [30.27, -97.74] };
constsleep = (ms) => new Promise((r) => setTimeout(r, ms));
const get = async (params) => {
const res = await fetch(`${BASE}?${new URLSearchParams(params)}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
};
const rows = [];
for (const [city, [lat, lon]] of Object.entries(CITIES)) {
const row = { city, lat, lon };
for (const h of [0, 24, 48]) {
const wx = await get({ dataset: "weather", lat, lon, hours_ahead: h });
row[`temp_f_${h}h`] = wx.temperature_f;
row[`precip_in_${h}h`] = wx.precip_in;
row[`gust_mph_${h}h`] = wx.wind_gust_mph;
awaitsleep(1000); // stay under 60 free calls a minute
}
row.alerts_now = (await get({ dataset: "weather", lat, lon })).alerts.length;
row.storm_grade_24h = (await get({ dataset: "risk", lat, lon, window: "24h" })).grade;
row.storm_grade_48h = (await get({ dataset: "risk", lat, lon, window: "48h" })).grade;
rows.push(row);
}
consthead = Object.keys(rows[0]);
const csv = [head.join(","), ...rows.map((r) => head.map((k) => r[k]).join(","))].join("\n");
await writeFile("signals.csv", csv + "\n");
console.log(csv);
import { writeFile } from"node:fs/promises";
const BASE = "https://data.truefixr.com/v1/data";
const CITIES = { NYC: [40.78, -73.97], CHI: [41.88, -87.63], MIA: [25.79, -80.29], AUS: [30.27, -97.74] };
constsleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
const get = async (params: Record<string, string | number>): Promise<any> => {
const res = await fetch(`${BASE}?${new URLSearchParams(Object.entries(params).map(([k, v]) => [k, String(v)]))}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
};
const rows: Record<string, string | number>[] = [];
for (const [city, [lat, lon]] of Object.entries(CITIES)) {
const row = { city, lat, lon };
for (const h of [0, 24, 48]) {
const wx = await get({ dataset: "weather", lat, lon, hours_ahead: h });
row[`temp_f_${h}h`] = wx.temperature_f;
row[`precip_in_${h}h`] = wx.precip_in;
row[`gust_mph_${h}h`] = wx.wind_gust_mph;
awaitsleep(1000); // stay under 60 free calls a minute
}
row.alerts_now = (await get({ dataset: "weather", lat, lon })).alerts.length;
row.storm_grade_24h = (await get({ dataset: "risk", lat, lon, window: "24h" })).grade;
row.storm_grade_48h = (await get({ dataset: "risk", lat, lon, window: "48h" })).grade;
rows.push(row);
}
consthead = Object.keys(rows[0]);
const csv = [head.join(","), ...rows.map((r) => head.map((k) => r[k]).join(","))].join("\n");
await writeFile("signals.csv", csv + "\n");
console.log(csv);
A bot with a wallet can skip the key. It calls a paid route, gets a 402 with the price, pays in USDC on Base, retries and gets the data. No signup, no balance to top up. The first payment settles right away and later small calls add up on a $1.00 tab.
The command shows the 402 and nothing is charged. A working Python client, the payable routes and wallet advice are in the docs. The agents page has the longer walkthrough and x402.html explains the protocol. Use a small separate wallet. Not investment advice, and no profit claims from us.
Storm data means reported and radar-detected events matched to addresses. It is not an inspection or a damage assessment. Forecasts show what could happen, not what will, and can be wrong. Code is provided as is. You are responsible for your own app and for the cost of the paid calls you make. GetFixn sells data, not trading advice. Nothing here is investment advice.