{"name":"havatahmin api","version":"v1","self":"/index.json","latest_run":"2026-10-10T12","model_version":"v4_20261010_1050_v4b","generated_at":"2026-10-10T19:54:05+00:00","coverage":{"provinces_total":81,"provinces_answering":81,"provinces_station_model":76,"provinces_transfer":5,"provinces_transfer_slugs":["bitlis","kahramanmaras","karabuk","sirnak","sivas"],"provinces_with_verification":0,"districts_total":973,"districts_answering":911,"summary":"81 of 81 provinces (76 with their own station model, 5 by transfer from the nearest station: bitlis, kahramanmaras, karabuk, sirnak, sivas, which have no /v1/verification file) and 911 of 973 districts answer in the latest run","note":"counted from the published files of this run. Not every served place has a forecast in every run; a 404 on /v1/forecast/... means no forecast in the latest run, too far from every station, or a misspelled slug: /v1/status.json `missing` lists the exact slugs. `provinces_with_forecast` counts provinces with their own station model; a province without one is served by transfer from the nearest station (`method` \"transfer\") and still answers at /v1/forecast/{il}.json, but is counted in `districts_with_forecast`. Use `missing` for what really 404s; index.json `coverage` has the counts as served."},"freshness":"`stale` = today_istanbul > `issued` + 1 day, evaluated when the file was written; the file is static, so compare now with `stale_after` (issued + 2 days, 00:00 Istanbul): at or past it, treat the run as stale even if `stale` reads false. `produced_at` is when the calculator computed the run (data time, +03:00), usually hours after the ECMWF cycle named by `issued` + `cycle`; `age_hours` = hours since `produced_at` at export; `status.time` is when the files were rendered. There is no fixed schedule: a run is published when the operator's machine runs the update, usually twice a day (the 00Z run between ~10:30 and 11:00, the 12Z run between ~20:30 and 23:00 Istanbul). `next_run_expected` is the usual earliest time of the following run, an expectation, not a promise: in the past means not published yet, so rely on `stale` and `age_hours`. `hres_latest_run` is the newest ECMWF HRES input per cycle (YYYYMMDD), not a forecast. `missing.provinces`/`missing.districts` list the slugs that 404 in this run.","timezone":"All dates are Istanbul (UTC+3, no DST). `issued` and `valid` are calendar dates; to answer for a date D pick the day whose `valid` equals D; `lead` = `valid` - `issued` in days (lead 1 = the day after `issued`). `produced_at`, `status.time` and `next_run_expected` carry +03:00; `generated_at` (places.json, index.json) is UTC with offset. `cycle` (00Z/12Z) is the UTC hour of the ECMWF run.","province_vs_district":"a province forecast is the point forecast at its main station (verified_against.station); for province-wide alerts, query every district via /v1/districts/{il}.json","alerts":"frost: likely when tmin <= 0, possible when tmin.p05 <= 0; heat: likely when tmax >= 35, possible when tmax.p95 >= 35; sudden_change when |day-to-day change of tmean| >= 6. Thresholds are applied to the published rounded values, so 0.0 and 35.0 do trigger. For a village/neighbourhood apply them to the offset-shifted values.","products":{"raw":true,"precip":"precip_v1_20261010","hourly":true,"aq":"aq_v1_20261010","notes":{"raw":"ECMWF HRES values for the place and local day, uncorrected and unverified (the honesty label is the name): gust = day max, precip/snowfall = day accumulation (water equivalent), cloud/humidity/pressure = day mean, rh_min = day min, precip_type = most frequent ECMWF ptype of the day (none|rain|freezing rain|snow|wet snow|rain-snow mix|ice pellets). Null when the store lacks the run.","precip":"rain probability and amount for the 06-06 UTC day (09:00-09:00 Istanbul) of each lead, from the calculator's precipitation model on the HRES raw fields: pop_01/pop_1/pop_5 = P(>= 0.1 / 1 / 5 mm), mm = median amount, q90 = 90th percentile; method 'model' on a station, 'anchor' on a district (the nearest station's values, rain is local). Not the same window as tmax/tmin (calendar day).","hourly":"/v1/hourly/{path}.json: 24 local hours per lead; t = the served curve (HRES shape rescaled to our Tmin/Tmax plus a per-hour correction fitted on thermometer reports), t_raw = ECMWF HRES's own curve interpolated to the hour. Hour 0..23 Istanbul of the day `valid`.","aq":"/v1/aq/{il}.json: daily mean PM2.5, PM10, NO2, O3, SO2, CO (µg/m³) for D+1..D+3 at the province's air-quality stations, issued with the 00Z run from observations to the end of D-1; persistence = the D-1 observed mean. Stations: /v1/aq/stations.json."}},"endpoints":[{"path":"/v1/status.json","auth":"none","description":"latest run, model version, freshness (stale, age_hours, next_run_expected, missing)"},{"path":"/index.json","auth":"none","description":"endpoint list, coverage, errors, limits, license and province slugs in one document"},{"path":"/v1/places.json","auth":"none","description":"every province + district: name, slug, path, lat, lon, elev_m (~112 KB)"},{"path":"/v1/places.min.json","auth":"none","description":"compact place index: [path, name, lat, lon] rows for provinces and districts (~45 KB)"},{"path":"/v1/forecast/{il}.json","auth":"none","description":"7-day forecast for a province (point forecast at its main station), plus each anchoring station's own days"},{"path":"/v1/forecast/{il}/{ilce}.json","auth":"none","description":"7-day forecast for a district"},{"path":"/v1/hourly/{il}.json","auth":"none","description":"hourly temperature curve for a province: 24 local hours for each of the 7 days (t = corrected, t_raw = ECMWF HRES)"},{"path":"/v1/hourly/{il}/{ilce}.json","auth":"none","description":"hourly temperature curve for a district"},{"path":"/v1/aq/{il}.json","auth":"none","description":"air quality: daily mean PM2.5, PM10, NO2, O3, SO2, CO (µg/m³) for D+1..D+3 at the province's monitoring stations"},{"path":"/v1/aq/stations.json","auth":"none","description":"every air-quality station with its coordinates, district and the province file that holds its forecast"},{"path":"/v1/districts/{il}.json","auth":"none","description":"districts of a province, each with slug, path, display_name and has_forecast"},{"path":"/v1/search/{prefix}.json","auth":"none","description":"name search shard: provinces, districts and ~54,000 villages/neighbourhoods whose folded name starts with {prefix}"},{"path":"/v1/search/index.json","auth":"none","description":"search rules, lapse rates and the list of existing shard prefixes"},{"path":"/v1/alerts/{il}.json","auth":"api_key","description":"frost / heat / sudden-change flags for the week at the province's main station"},{"path":"/v1/alerts/{il}/{ilce}.json","auth":"api_key","description":"same flags for a district"},{"path":"/v1/verification/{il}.json","auth":"api_key","description":"MAE vs raw HRES per lead over the last 30 days, against the station thermometer; 404 until a run is verified or for a province without a station"},{"path":"/v1/verification/all.json","auth":"api_key","description":"same, across all served places"}],"resolve_place_name":"any province, district, village or neighbourhood: fold the name -> first 3 characters -> /v1/search/{abc}.json -> entry whose `slug` matches -> /v1/forecast/{path}.json + `offset`; coordinates: nearest district row (path with a slash) in /v1/places.min.json; full recipe in /llms-full.txt","auth":{"methods":["x-api-key header","?key= query parameter"],"on_missing_key":401,"on_invalid_key":403,"note":"the edge checks the path first: an unknown path or slug is 404 whether or not a key is sent; then a present but unknown key is 403 on every path, free ones included; then a missing key on a key endpoint is 401. 401/403 bodies carry key_request_url","how_to_get_a_key":"Request an API key via the contact form: https://www.abso.net/iletisim-formu/ (state name, purpose and expected daily requests)."},"rate_limit":{"per_ip":"300 requests per client IP per 5 minutes, with or without a key","exceeded":"HTTP 403 with the JSON body in errors.403_rate_limit until the rate drops (CloudFront cannot emit 429; no Retry-After header); cache responses, they change twice a day; fetching every place is ~1000 requests, spread them over 20 minutes","how_to_get_a_key":"Request an API key via the contact form: https://www.abso.net/iletisim-formu/ (state name, purpose and expected daily requests)."},"aliases":{"/latest.json":"same as /v1/status.json"},"errors":{"404":{"error":"not found","hint":"unknown slug, or no forecast in this run (see /v1/status.json missing); slugs: /v1/places.json","docs":"https://havatahmin.abso.net/llms-full.txt"},"400":{"error":"invalid path","docs":"https://havatahmin.abso.net/llms-full.txt"},"401":{"error":"api key required: send x-api-key header or ?key=","key_request_url":"https://www.abso.net/iletisim-formu/","docs":"https://havatahmin.abso.net/llms-full.txt"},"403":{"error":"invalid api key","key_request_url":"https://www.abso.net/iletisim-formu/","docs":"https://havatahmin.abso.net/llms-full.txt"},"403_rate_limit":{"error":"rate limit exceeded: more than 300 requests from this IP in 5 minutes (or a method other than GET/HEAD/OPTIONS: the API is read-only)","fix":"cache responses (they change twice a day) and spread requests out; building something on this API? request a free API key via key_request_url and tell us your expected volume","key_request_url":"https://www.abso.net/iletisim-formu/","docs":"https://havatahmin.abso.net/llms-full.txt"}},"slugs":"lowercase ASCII with single hyphens for spaces/punctuation (ankara/cankaya, sanliurfa/birecik, mugla/yesilyurt): fold a name (lowercase; ç->c ğ->g ı/İ->i ö->o ş->s ü->u, NFKD-strip â/î/û) and compare with `slug`/`path` of /v1/places.json or a search entry, never with `name` (the calculator's spelling, ASCII for provinces, mixed for districts) or `display_name` (proper Turkish, for display)","ordering":"provinces in this file and in openapi.json are alphabetical by slug; /v1/places.json is in plate-code order","license":{"name":"CC BY 4.0","url":"https://creativecommons.org/licenses/by/4.0/","attribution":"havatahmin (https://havatahmin.abso.net); place names: GeoNames (CC BY 4.0, https://www.geonames.org/)"},"openapi":"/openapi.json","llms_txt":"/llms.txt","llms_full_txt":"/llms-full.txt","docs":"/api/","sitemap":"/sitemap.xml","discovery":["/.well-known/api-catalog","/.well-known/ard.json","/.well-known/ai-catalog.json"],"provinces":["adana","adiyaman","afyonkarahisar","agri","aksaray","amasya","ankara","antalya","ardahan","artvin","aydin","balikesir","bartin","batman","bayburt","bilecik","bingol","bitlis","bolu","burdur","bursa","canakkale","cankiri","corum","denizli","diyarbakir","duzce","edirne","elazig","erzincan","erzurum","eskisehir","gaziantep","giresun","gumushane","hakkari","hatay","igdir","isparta","istanbul","izmir","kahramanmaras","karabuk","karaman","kars","kastamonu","kayseri","kilis","kirikkale","kirklareli","kirsehir","kocaeli","konya","kutahya","malatya","manisa","mardin","mersin","mugla","mus","nevsehir","nigde","ordu","osmaniye","rize","sakarya","samsun","sanliurfa","siirt","sinop","sirnak","sivas","tekirdag","tokat","trabzon","tunceli","usak","van","yalova","yozgat","zonguldak"]}