REST API
Base URL: https://kaicast.com. All endpoints are GET, return JSON, and allow cross-origin requests. The machine-readable spec is /openapi.json (OpenAPI 3.1) — import it into Postman, generate a client, or hand it to an agent framework as a tool definition.
| Endpoint | Layer | Purpose |
|---|---|---|
GET /api/v1/conditions | Fact | Conditions for one spot over up to 7 days |
GET /api/v1/spots | Fact | Spot directory (metadata only), filter by region / text / nearest |
GET /api/v1/coverage | Fact | Covered regions, decision support, activities |
GET /api/v1/recommendations | Decision | Ranked spot picks for an activity, area and day |
GET /api/v1/best-day | Decision | Best day in a window for an activity at a destination |
Conventions #
- Attribution: every JSON body — including errors — has a
sourceobject:{ "name": "KaiCast", "url": "https://kaicast.com", "docs": "https://kaicast.com/developers", "citation": "…" }. Cite it when you surface the data. Responses also carry aLinkheader pointing at these docs and/openapi.json.
- Units are in field names —
visibility_ft,swell_height_m,wind_speed_kt,water_temp_c. No separate unit strings to lose. - Directions are where it comes FROM, degrees true:
swell_from_deg,wind_from_deg. Decision responses also give an 8-point compass (swell_from: "NW"). - Times are ISO 8601 with the spot's UTC offset, plus the IANA timezone on the spot. Dates (
from,to,date) are spot-local calendar days. - Every key is always present. Unknown values are
null, never omitted. - Tide heights are feet above MLLW (
tide_datum: "MLLW"). - Scores are 0–100. Tiers:
excellent≥ 80,great≥ 60,good≥ 40,fair≥ 20, elseno-go. Low visibility caps the score — calm water can't buy back murky water. - Location is one of:
spot(an id),region(an id likeus-hi-oahu), orlat+lon. Never more than one.
Authentication #
None required today. Facts are free and keyless; decisions are free during early access.
Keys aren't issued yet (API keys). When they are, they'll be required for decisions once metering starts, sent like this — the header is already accepted and ignored, so you can wire it in now:
Authorization: Bearer kc_live_xxxxxxxx_xxxxxxxxxxxxxxxxor X-KaiCast-Key: kc_live_…. Test keys start with kc_test_. Keys never go in the URL.
GET /api/v1/conditions #
Facts for one spot, up to 7 days, in 3-hour periods.
| Parameter | Type | Notes |
|---|---|---|
spot | string | Spot id, e.g. electric-beach. Or use lat + lon. |
lat, lon | number | Matched to the nearest covered spot within 30 km; the response echoes query.resolved_from with the distance. |
from | date | Spot-local YYYY-MM-DD. Default today. |
to | date | Inclusive. Default from + 6. At most 7 days. |
curl "https://kaicast.com/api/v1/conditions?spot=electric-beach&from=2026-10-06&to=2026-10-06"{
"schema": "kaicast.conditions.v1",
"spot": {
"id": "electric-beach", "name": "Electric Beach",
"region": { "id": "us-hi-oahu", "name": "Oʻahu", "country": "US", "admin1": "Hawaii" },
"lat": 21.355, "lon": -158.131, "timezone": "Pacific/Honolulu"
},
"query": { "spot": "electric-beach", "lat": null, "lon": null, "from": "2026-10-06", "to": "2026-10-06", "resolved_from": null },
"range": { "from": "2026-10-06", "to": "2026-10-06", "available_from": "2026-10-05", "available_to": "2026-10-12" },
"generated_at": "2026-10-05T10:00:00-10:00",
"next_update_after": "2026-10-05T11:15:00-10:00",
"data_quality": { "visibility_source": "satellite", "freshness": "live" },
"tide_datum": "MLLW",
"now": null,
"periods": [
{
"kind": "forecast",
"start": "2026-10-06T08:00:00-10:00", "end": "2026-10-06T11:00:00-10:00",
"rating": "great", "score": 72,
"visibility_ft": 45, "visibility_m": 13.7,
"swell_height_ft": 2.1, "swell_height_m": 0.6, "swell_period_s": 12, "swell_from_deg": 310,
"wind_speed_kt": 8, "wind_gust_kt": 12, "wind_from_deg": 65,
"tide_state": "rising", "tide_height_ft": 0.9,
"water_temp_c": 27.4,
"confidence": 0.82, "visibility_source": "satellite",
"why": "Clean water and light trades; small northwest swell wraps in but stays under the shelf."
}
],
"tide_events": [{ "type": "high", "time": "2026-10-06T13:42:00-10:00", "height_ft": 1.8 }],
"sources": ["ndbc:51201", "noaa-tides:1612340", "pacioos-ww3", "openweather"],
"links": { "page": "https://kaicast.com/conditions/electric-beach", "app": "https://kaicast.com/spot/electric-beach", "docs": "https://kaicast.com/openapi.json" },
"attribution": "KaiCast (https://kaicast.com). …"
}now is the current-hour nowcast when today is in range. The period containing now is kept; fully elapsed periods are dropped.
GET /api/v1/spots #
The spot directory — ids, names, regions, coordinates. No conditions.
| Parameter | Notes |
|---|---|
region | Region id, e.g. us-hi-maui |
q | Name search |
lat, lon | Sort by distance; each spot gets distance_km |
curl "https://kaicast.com/api/v1/spots?region=us-hi-maui"GET /api/v1/coverage #
Which regions are covered, whether each supports decisions (decision_support: "full") or facts only ("conditions_only"), its spots and bounding box, and the activities with how directly each is modeled.
curl "https://kaicast.com/api/v1/coverage"GET /api/v1/recommendations #
Ranked picks for one activity, in one area, on one spot-local day.
| Parameter | Type | Notes |
|---|---|---|
activity | enum | Required. snorkel, dive, freedive, spearfish, surf |
region / spot / lat+lon | One location. Picks never span regions. | |
radius_km | number | Around lat/lon. Default 40, max 100. |
date | date | Up to 6 days ahead. Default today — or tomorrow once today's daylight windows have passed, flagged by date_rolled_forward: true and a message. An explicit date is never moved. |
limit | integer | Default 3, max 5. |
curl "https://kaicast.com/api/v1/recommendations?region=us-hi-oahu&activity=snorkel&date=2026-10-06&limit=2"{
"schema": "kaicast.recommendation.v1",
"status": "ok",
"activity": { "id": "snorkel", "label": "Snorkeling", "model": "native", "note": "Scored by the KaiCast condition model, which is calibrated for snorkeling." },
"date": "2026-10-06",
"date_label": "Tomorrow",
"coverage": { "status": "covered", "regions": ["us-hi-oahu"] },
"picks": [
{
"rank": 1,
"spot": { "id": "electric-beach", "name": "Electric Beach", "region": { "id": "us-hi-oahu", "name": "Oʻahu" }, "lat": 21.355, "lon": -158.131 },
"distance_km": null,
"date": "2026-10-06",
"score": 72, "rating": "great",
"best_window": { "start": "2026-10-06T08:00:00-10:00", "end": "2026-10-06T11:00:00-10:00", "start_local": "08:00", "end_local": "11:00", "score": 72, "rating": "great" },
"why": {
"summary": "Great: 45 ft visibility, 2.1 ft swell at 12s from the NW, 8 kt wind from the NE.",
"factors": ["45 ft visibility", "2.1 ft swell at 12s from the NW", "8 kt wind from the NE", "rising tide"],
"model_note": "Clean water and light trades; small northwest swell wraps in but stays under the shelf."
},
"metrics": { "visibility_ft": 45, "swell_height_ft": 2.1, "swell_period_s": 12, "swell_from": "NW", "wind_speed_kt": 8, "wind_from": "NE", "water_temp_f": 81, "tide_state": "rising" },
"confidence": { "value": 0.74, "label": "high", "factors": { "model": 0.82, "visibility_source": 1, "freshness": 1, "lead_time": 0.95, "activity_fit": 1 } },
"caveats": [],
"card": { "…": "see Spot card below" }
}
],
"cards": ["…"],
"card_markdown": "**Electric Beach** · Oʻahu — Tomorrow\nGreat (72/100) · best 08:00–11:00\n…",
"disclaimer": "KaiCast forecasts are model estimates, not a safety guarantee. …",
"pricing": { "layer": "decision", "plan": "free_early_access" }
}How picks are made. Each spot's value for the day is its best daylight period (starting 06:00–17:59 local). Spots are ranked by that period's score; ties break on what matters most for the activity (calmer surface for snorkeling, clearer water for diving). Spots unsuited to the activity are excluded and listed in considered.excluded (e.g. deep boat dives aren't snorkel picks).
GET /api/v1/best-day #
Which day in a window is best at a destination.
| Parameter | Type | Notes |
|---|---|---|
activity | enum | Required. |
spot / region / lat+lon | One destination. A region or radius picks the best spot per day. | |
from, to | date | Default today … +6. At most 7 days; a past from is clamped to today. |
curl "https://kaicast.com/api/v1/best-day?spot=hanauma-bay&activity=snorkel"The response has best (a full pick, plus why.versus_next — e.g. "9 points ahead of the next-best option (2026-10-05, 72/100)"), runner_up, and days[] with every day's best score, spot and window.
Statuses — honest answers, not errors #
Decision endpoints return 200 with a status for anything that isn't a malformed request:
status | Meaning | What to do |
|---|---|---|
ok | Picks are present. | Show them, with caveats. |
no_data | Covered, but no daylight forecast for that date (e.g. an explicit date of today, asked after dark). | Try another date. |
not_covered | Outside KaiCast coverage. coverage.nearest_covered names the closest covered spot and distance. | Tell the user KaiCast doesn't cover it yet. |
conditions_only | Region has facts but no calibrated visibility model. | Use /api/v1/conditions. |
activity_not_modeled | E.g. surf — KaiCast doesn't model breaks. facts_hint points to the conditions endpoint. | Use swell/wind facts directly. |
Activities #
| Activity | Model | Meaning |
|---|---|---|
snorkel | native | The condition score is calibrated for snorkeling. |
dive | proxy | Same score; caveat added; confidence × 0.85. Conditions at depth aren't scored. |
freedive | proxy | Same score; visibility breaks ties. |
spearfish | proxy | Same score. Spots in Hawaiʻi DAR areas closed to spearfishing are excluded (considered.excluded, reason closed_to_spearfishing); bag limits and seasons are not modeled — a caveat says so. |
surf | unsupported | Not ranked. Use the facts. |
Confidence and caveats #
confidence.value (0–1) is the product of its factors — the model's own visibility confidence, visibility source (satellite 1.0, estimated 0.8), input freshness, lead time (−5%/day), and activity fit (native 1.0, proxy 0.85). label is high ≥ 0.65, medium ≥ 0.4, else low.
caveats[] items have a stable code and a human message: visibility_estimated, stale_inputs, long_lead_time, marginal_conditions, location_snapped, proxy_activity_model, verify_fishing_rules, regulated_area, regulations_not_checked.
Spot card #
Every decision pick carries card, and the response carries cards[] and card_markdown. Render it as-is so users see the same card they'd see in the KaiCast app:
{
"version": 1, "spot_id": "electric-beach", "spot_name": "Electric Beach", "region_name": "Oʻahu",
"date": "2026-10-06", "date_label": "Tomorrow", "activity": "snorkel",
"tier": "great", "tier_label": "Great", "tier_color": "#27D667", "score": 72,
"headline": "Great: 45 ft visibility, 2.1 ft swell at 12s from the NW, 8 kt wind from the NE.",
"best_window": { "start_local": "08:00", "end_local": "11:00" },
"metrics": { "visibility": "45 ft", "swell": "2.1 ft @ 12s", "wind": "8 kt NE", "water_temp": "81°F" },
"image_url": "https://kaicast.com/generateSpotShareImage?spot=electric-beach&date=2026-10-06",
"links": {
"share": "https://kaicast.com/s/electric-beach/2026-10-06?utm_source=agent&utm_medium=api&utm_campaign=spot_card_snorkel",
"app": "kaicast://spot/electric-beach/2026-10-06",
"forecast": "https://kaicast.com/spot/electric-beach?utm_source=agent&utm_medium=api&utm_campaign=spot_card_snorkel"
},
"cta": { "label": "Full forecast in KaiCast", "text": "Hour-by-hour windows, 7-day outlook and live hazard alerts in the KaiCast app.", "url": "https://kaicast.com/spot/electric-beach?…" },
"attribution": "Conditions by KaiCast",
"credits_text": "Weather data provided by OpenWeather · Buoy observations: NOAA NDBC · Data provided by PacIOOS (www.pacioos.org)"
}credits_text is the licence-required upstream credit line — render it with the card. Each pick also carries data_credits (structured), and the response carries their union.
image_url is a 1200×630 PNG of the same card, for surfaces that show images. tier_color values: excellent #09A1FB, great #27D667, good #FFD321, fair #FF9D25, no-go #F73726.
Errors #
Malformed requests return RFC 9457 application/problem+json with a stable code:
{
"type": "https://kaicast.com/openapi.json#problem-unknown_activity",
"title": "unknown activity",
"status": 400,
"detail": "Unknown activity \"kitesurf\". Use one of snorkel, dive, freedive, spearfish, surf.",
"code": "unknown_activity"
}| Status | Codes |
|---|---|
| 400 | missing_location, ambiguous_location, invalid_coordinates, invalid_date, invalid_range, range_too_long, missing_activity, unknown_activity, invalid_limit, invalid_radius, one_spot_per_request |
| 401 | invalid_key, key_revoked, key_required (decisions, once metering starts) |
| 402 | free_allotment_exhausted — body includes upgrade_url |
| 404 | unknown_spot, no_spot_nearby (conditions with lat/lon) |
| 422 | outside_horizon — dates beyond the 7-day forecast |
| 429 | rate_limited — honor Retry-After |
| 503 | data_unavailable / unavailable — retry after Retry-After |
Limits #
KaiCast serves answers point by point — one spot, one region, or one radius per call, for at most 7 days. There is no bulk export.
- Facts: generous per-caller hourly and daily caps, and a cap on how many distinct spots a caller can pull per hour and per day. Re-asking about a spot you already asked about today never counts against the distinct-spot cap. Requests from user-triggered AI fetchers (ChatGPT-User, Claude-User, Perplexity-User…) get higher caps because they share IPs.
- Decisions: at most 20 spots considered, 5 picks returned and 7 days per call; short bursts are throttled per IP.
Data refreshes hourly (next_update_after); polling faster than that returns the same answer.