API
Unified point weather API
Requests, response structure, time series, features, daily forecasts and publication consistency.
Purpose and endpoint
POST /api/weather/query queries weather for one geographic point. It returns numerical fields, features and messages, daily forecasts, derived diagnostics or availability metadata. The URL is unversioned; schema_version identifies the contract.
On the portal use the relative path /api/weather/query. The portal forwards the request to Weather Data Hub, which selects the model publication and reads the data. One request may contain multiple named queries. A quick current-weather card, a longer forecast and features can load separately using the same contract.
The API accepts coordinates, not ICAO identifiers. For an airport widget resolve the airport by ICAO first, then pass its coordinates here. Exact-ICAO METAR/TAF retrieval remains a separate airport-report request; nearby feature search does not replace an exact ICAO lookup.
Quick start
Minimal request for temperature at 2 m and surface pressure. Omitting time uses the current time with valid_at selection. This is a read operation: POST does not create a calculation, scenario or record.
{
"schema_version": "weather-query-1",
"point": {
"lat": 55.41,
"lon": 37.91
},
"queries": [
{
"id": "current",
"kind": "samples",
"source": {
"model": "noaa"
},
"fields": [
{
"id": "temperature_2m",
"parameter": "air_temperature",
"level": {
"type": "height_agl",
"value": 2,
"unit": "m"
}
},
{
"id": "surface_pressure",
"parameter": "surface_air_pressure",
"level": {
"type": "surface"
}
}
]
}
]
}curl --fail-with-body 'https://YOUR_PORTAL/api/weather/query' \
-H 'Content-Type: application/json' \
--data-binary @request.jsonconst request = {
"schema_version": "weather-query-1",
"point": {
"lat": 55.41,
"lon": 37.91
},
"queries": [
{
"id": "current",
"kind": "samples",
"source": {
"model": "noaa"
},
"fields": [
{
"id": "temperature_2m",
"parameter": "air_temperature",
"level": {
"type": "height_agl",
"value": 2,
"unit": "m"
}
},
{
"id": "surface_pressure",
"parameter": "surface_air_pressure",
"level": {
"type": "surface"
}
}
]
}
]
};
const response = await fetch('/api/weather/query', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'omit',
body: JSON.stringify(request)
});
const payload = await response.json();
if (!response.ok) throw new Error(JSON.stringify(payload));
const current = payload.results.find(result => result.id === 'current');
const definitions = new Map(current.fields.map(field => [field.id, field]));
for (const sample of current.samples) {
for (const value of sample.values) {
const field = definitions.get(value.field_id);
console.log(sample.valid_time, field.parameter, value.value, field.unit, value.status);
}
}Request structure
| Field | Rule |
|---|---|
| schema_version | Required: weather-query-1. |
| point | Object {lat, lon}; required unless all queries are availability. Latitude −90…90, longitude −180…180; WGS84 degrees. |
| queries | 1–16 queries; each id is unique within the batch and at most 64 characters. Match results by id. |
| queries[].kind | samples | features | daily | diagnostics | availability |
| queries[].source | Model data: {model, member?, cid?}. NOAA is the default. cid selects a CKS centre (integer 0–65535), requires model:cks or CKS layers, filters CKS only and is part of publication identity. Features select collection and sources instead. |
| queries[].run | Default {policy:"latest"}; exact run: {policy:"exact", at:"…Z"}. publication_token pins a publication. |
| queries[].time | An at instant or a from/to interval with grid:"native". See time selection below. |
Send application/json. Coordinates must be finite numbers. Arbitrary SQL expressions, table names and column names are not accepted; the server controls source and parameter selection.
Query IDs and explicit field IDs use 1–64 characters from A–Z, a–z, 0–9, _, ., :, -. Unknown request keys are rejected. For daily and features, time supports at only; availability does not accept time. Diagnostics supports noaa, roshydro or gefs.
Sources, parameters and levels
| model | Purpose |
|---|---|
| noaa | NOAA deterministic fields. |
| roshydro | Roshydromet fields. |
| gefs | GEFS ensemble fields; supported member: "geavg". |
| cks | Additional CKS fields; ref_time may be absent. |
| noaa_turbulence | Additional composite turbulence fields. |
Parameter availability depends on published source data. For fields:"all" and native:… parameters the server preserves provider names and units with normalized:false. Do not infer temperature or pressure semantics from an unfamiliar field name.
| parameter | Native parameter | Response unit |
|---|---|---|
| air_temperature | TMP | degC |
| relative_humidity | RH | % |
| surface_air_pressure | PRES | hPa |
| air_pressure_at_mean_sea_level | PRMSL | hPa |
| cloud_area_fraction | TCDC | % |
| precipitation_rate | PRATE | mm/h |
| precipitation_amount | APCP | mm |
| eastward_wind | UGRD | m/s |
| northward_wind | VGRD | m/s |
| wind_speed | WIND_SPEED | m/s |
| wind_from_direction | WIND_DIRECTION | degree_from_true_north |
| wind_speed_of_gust | GUST | m/s |
| geopotential_height | HGT | m |
| level | Meaning |
|---|---|
| {"type":"surface"} | Model surface. |
| {"type":"height_agl","value":2,"unit":"m"} | Height above ground level. |
| {"type":"pressure","value":850,"unit":"hPa"} | Isobaric level; not height above the ground. |
| {"type":"mean_sea_level"} | Mean sea level. |
| {"type":"entire_atmosphere"} | Entire atmosphere; for example total cloud cover. |
| {"type":"native","value":"850hPa"} | Exact provider level identifier. |
Surface PRES is model surface pressure; it is neither observed METAR QNH nor PRMSL. Total TCDC uses entire_atmosphere. Wind direction indicates where the wind comes FROM relative to true north. APCP is accumulated precipitation: check accumulation intervals; do not blindly sum it as independent hourly amounts.
Numerical samples and time series
kind:"samples" accepts one selector: explicit fields with parameters and levels, fields:"all" for provider fields, or layers identifying published layers. Do not combine selectors within one query. Explicit fields limits reads to the required quantities.
{
"schema_version": "weather-query-1",
"point": {
"lat": 55.41,
"lon": 37.91
},
"queries": [
{
"id": "airport_forecast",
"kind": "samples",
"source": {
"model": "noaa"
},
"time": {
"from": "2026-10-20T06:00:00Z",
"to": "2026-10-23T06:00:00Z",
"grid": "native"
},
"fields": [
{
"id": "temperature_2m",
"parameter": "air_temperature",
"level": {
"type": "height_agl",
"value": 2,
"unit": "m"
}
},
{
"id": "surface_pressure",
"parameter": "surface_air_pressure",
"level": {
"type": "surface"
}
}
]
}
]
}Example dates are illustrative: replace them with times reported by availability. If to is omitted, published future times from from are returned within the limit. The API does not manufacture hourly steps or interpolate between native forecast times.
{
"schema_version": "weather-query-1",
"point": {
"lat": 55.41,
"lon": 37.91
},
"queries": [
{
"id": "map_fields",
"kind": "samples",
"layers": [
"image_tmp_noaa",
"image_pres_noaa",
"image_tcdc_noaa"
]
}
]
}Without a shared level, TMP and RH layers use 2 m, PRES uses surface, PRMSL mean_sea_level, TCDC entire_atmosphere, and other layers surface. For a height-specific request set level explicitly and inspect fields[].level. Layers from different models receive separate publication records.
{
"schema_version": "weather-query-1",
"point": {
"lat": 55.41,
"lon": 37.91
},
"queries": [
{
"id": "temperature_850",
"kind": "samples",
"source": {
"model": "noaa"
},
"fields": [
{
"id": "t850",
"parameter": "air_temperature",
"level": {
"type": "pressure",
"value": 850,
"unit": "hPa"
}
}
]
}
]
}Time and model-run selection
| time | Behavior |
|---|---|
| {"at":"…Z","selection":"valid_at"} | Selects a field valid at the requested instant; the standard source validity window is 3 hours. Returns the actual field time. |
| {"at":"…Z","selection":"exact"} | Only the exact native time. A missing time is not replaced by a neighboring time. |
| {"at":"…Z","selection":"nearest","max_offset_seconds":5400,"tie_break":"earlier"} | Nearest time within the explicit tolerance. max_offset_seconds and tie_break are required; tie_break is earlier or later. |
| {"from":"…Z","to":"…Z","grid":"native"} | Published native times in the interval. |
All input timestamps must include Z or a timezone offset. Model timestamps in responses use UTC. Requested time, model run_time, forecast valid_time and value validity valid_from/valid_to have different meanings. Do not label data with requested time when the actual forecast time differs.
run.policy:"latest" selects one latest model run and does not fill its gaps from older runs. Use run:{policy:"exact",at:"…Z"} for reproducibility. CKS run time may be unknown: null does not mean now.
valid_at/exact/nearest selection and native intervals apply to samples and diagnostics. nearest tolerance is 0–86400 seconds. Daily selects local dates relative to time.at; features selects features valid at time.at.
Consistency and publication_token
publications contains the publication ID, source, run time and opaque token. fields[].publication_id links each field to its publication; multiple sources do not imply a common run. Numerical batch reads use a consistent database snapshot. Observation messages have their own timestamps and are not part of a forecast model run.
To keep a separately loaded forecast consistent with the current-weather card, pass that publication token as run.publication_token in the next query for the same source. Do not compute tokens yourself or replace them with run time. Republishing data can change the token while run_time remains the same.
{
"id": "forecast",
"kind": "samples",
"source": {
"model": "noaa"
},
"run": {
"policy": "latest",
"publication_token": "TOKEN_FROM_PUBLICATIONS"
},
"time": {
"from": "2026-10-20T06:00:00Z",
"grid": "native"
},
"fields": [
{
"id": "temperature_2m",
"parameter": "air_temperature",
"level": {
"type": "height_agl",
"value": 2,
"unit": "m"
}
},
{
"id": "surface_pressure",
"parameter": "surface_air_pressure",
"level": {
"type": "surface"
}
}
]
}HTTP 409 publication_changed means the pinned publication no longer matches. Clear related old-card data, reload the card without the stale token, then request the forecast with the new token. Bound retries and do not display a mixture of old and new publications.
For layers spanning multiple models pass run.publication_tokens as {publication_id: token}. Copy id and token from publications unchanged. A batch cannot request different runs of the same source: this is a 422 mixed_runs error.
Features, zones and messages
kind:"features" returns a GeoJSON FeatureCollection. collection:"vectors" selects weather features, observations and messages; collection:"opmet" selects OPMET conditions covering the point. sources selects vector sources; omitting it uses the server set. Radius is in meters.
{
"schema_version": "weather-query-1",
"point": {
"lat": 55.41,
"lon": 37.91
},
"queries": [
{
"id": "nearby_reports",
"kind": "features",
"collection": "vectors",
"sources": [
"metars_noaa",
"tafs_noaa"
],
"spatial": {
"relation": "within_radius",
"radius_m": 50000
},
"page": {
"limit": 20,
"offset": 0
}
},
{
"id": "covering_opmet",
"kind": "features",
"collection": "opmet",
"spatial": {
"relation": "covers_point"
}
}
]
}Vector sources: clouds, turbulence, icing_noaa, clouds_noaa, turbulence_noaa, jet_stream, jet_stream_points, jet_stream_noaa, jet_stream_points_noaa, tropopause, tropopause_noaa, fronts, fronts_noaa, front_points, front_points_noaa, volcano, volcano_noaa, volcanic_ash_clouds, volcanic_ash_volcanoes, cyclone_forecast_points, cyclone_forecast_tracks, active_tropical_cyclones_history, recent_tropical_cyclones, aircraft_reports_noaa, metars, metars_noaa, tafs, tafs_noaa, tsa. Actual coverage depends on ingested data.
Response sources[] reports per-source status, coverage_verified and pagination. Request a next page for the specific source using its offset; a common limit is not a total limit for the merged collection. Provider attributes are retained in properties.source_properties. Inspect time validity, relation, distance_m and vertical datum; AGL, MSL and flight levels are not interchangeable.
Vectors supports within_radius; OPMET supports covers_point. An empty collection with coverage_verified:false means coverage is unknown, not that hazardous phenomena are proven absent. stale, unavailable and empty are different states.
Default vector sources omit aircraft_reports_noaa; this source depends on a server feature flag. Request only necessary sources: each has independent pagination limits and coverage.
Daily forecast and local dates
{
"schema_version": "weather-query-1",
"point": {
"lat": 55.41,
"lon": 37.91
},
"queries": [
{
"id": "daily",
"kind": "daily",
"source": {
"model": "noaa"
},
"preset": "near_surface_weather",
"timezone": "Europe/Moscow",
"lang": "ru",
"dates": [
"2026-10-20",
"2026-10-21"
]
}
]
}preset:"near_surface_weather" explicitly selects the full near-surface field set for deterministic descriptions. timezone is an IANA name, such as Europe/Moscow or UTC; lang is ru or en. dates accepts 1–64 unique local YYYY-MM-DD dates. Omitting dates selects the available future horizon.
Numerical data is stored in common fields and samples. days[].sample_times references samples[].valid_time; current.sample_time points to the current sample. days includes daily summaries, periods and text; additional metadata includes timezone, language, algorithm_version, native_steps_hours, quality, notes and available dates. Descriptions are deterministic, without AI inference.
Minima and maxima are calculated from available native samples, not from a continuous temperature curve. Quality metadata reports gaps and missing required fields. For a temperature and pressure chart use samples with two fields; the full daily preset is unnecessary.
Vertical column and diagnostics
{
"schema_version": "weather-query-1",
"point": {
"lat": 55.41,
"lon": 37.91
},
"queries": [
{
"id": "column",
"kind": "diagnostics",
"source": {
"model": "noaa"
},
"time": {
"at": "2026-10-20T06:00:00Z",
"selection": "exact"
}
}
]
}diagnostics uses the same numerical reads as fields:"all" and adds diagnostics, profile and algorithm_version:"point-column-1". profile is a derived presentation; original values remain in fields/samples. Isotherm heights and wind shear require the corresponding input fields. Missing input is not replaced by zero.
For additional columns submit a separate samples query with fields:"all" and source.model:"cks" or "noaa_turbulence". These fields may retain provider units and level identifiers; inspect normalized.
Available times and dates
{
"schema_version": "weather-query-1",
"queries": [
{
"id": "dates",
"kind": "availability",
"source": {
"model": "noaa"
},
"timezone": "Europe/Moscow"
}
]
}The result includes metadata_only:true, point_coverage_verified:false, available_dates, forecast_times, fields and a publication reference. This is a lightweight publication catalog: it does not run ST_Value or test values at a point. A catalog date does not guarantee full coverage for every coordinate or parameter.
Load the catalog independently of the numerical card. Once a date is selected, submit samples or daily; pin the returned publication token when consistency is required.
Common response and missing data
{
"schema_version": "weather-query-1",
"request_id": "example-request-id",
"status": "complete",
"point": {
"lat": 55.41,
"lon": 37.91
},
"publications": [
{
"id": "0123456789abcdef",
"source": {
"model": "noaa"
},
"run_time": "2026-10-20T00:00:00Z",
"token": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"consistency": "model_run_snapshot"
}
],
"results": [
{
"id": "current",
"kind": "samples",
"status": "complete",
"source": {
"model": "noaa"
},
"publication_id": "0123456789abcdef",
"fields": [
{
"id": "temperature_2m",
"parameter": "air_temperature",
"native_parameter": "TMP",
"native_level": "2 m above ground",
"level": {
"type": "height_agl",
"value": 2,
"unit": "m"
},
"unit": "degC",
"normalized": true,
"statistic": "unspecified",
"origin": "model",
"publication_id": "0123456789abcdef"
}
],
"samples": [
{
"valid_time": "2026-10-20T06:00:00Z",
"values": [
{
"field_id": "temperature_2m",
"value": 7.5,
"status": "available",
"quality": [],
"source_unit": "K",
"valid_from": "2026-10-20T06:00:00Z",
"valid_to": "2026-10-20T09:00:00Z",
"sampling": "bilinear"
}
]
}
],
"issues": []
}
]
}Top-level and query status can be complete, partial, unavailable or empty. HTTP 200 does not guarantee every value is present: inspect result.status, issues and each value.status. Value ordering is not a contract; join definitions through field_id. Units belong to field definitions; model provenance is recorded in origin and publication_id.
| status | Meaning |
|---|---|
| available | A value is available; zero is a valid value. |
| nodata | The raster field exists but the point has no value. |
| missing | A required field, level or time is missing. |
| conflict | Conflicting source rows prevent an unambiguous value. |
| unsupported_unit | The unit cannot be safely converted to the canonical unit. |
Missing values use value:null. Do not convert null to zero or draw across gaps without an explicit UI policy. sampling and quality describe sampling and quality limitations. source_unit retains the provider unit. For unknown native fields normalized:false means that canonical physical semantics are not guaranteed.
statistic:"unspecified" means source metadata does not establish the statistical meaning. APCP statistic:"accumulation" with quality:["interval_unknown"] does not identify accumulation boundaries: valid_from/valid_to describe field validity, not the precipitation accumulation interval. sampling:"derived_from_bilinear_components" identifies wind derived from normalized U/V components.
Errors, limits and retries
{
"schema_version": "weather-query-1",
"request_id": "example-request-id",
"status": "error",
"error": {
"code": "invalid_query",
"message": "point is required"
}
}| Code | Meaning and action |
|---|---|
| 200 | Response returned; partial/empty results and issues may be present. |
| 400 | Malformed JSON. Correct the request body. |
| 413 | Request body exceeds 64 KiB. Split the request. |
| 415 | The portal requires Content-Type: application/json. |
| 422 | Invalid coordinates, fields, levels, times or query cost. |
| 409 | publication_changed: reload related data with a new publication. |
| 502 | The portal could not obtain a valid bounded Hub response. |
| 503 | All requested data is unavailable. A bounded retry may be appropriate. |
| 504 | The deadline expired. Reduce the request or retry later. |
| Resource | Maximum |
|---|---|
| Request body | 65,536 bytes |
| Response | 8 MiB |
| Queries per batch | 16 |
| Explicit fields per query | 64 |
| Fields with fields:"all" | 3,000 |
| Native times per query | 512 |
| Sampled rows per batch | 16,000 |
| Explicit local dates | 64 |
| Features per source per page | 50 |
| Radius | 250,000 m |
| offset | 10,000 |
| Hub deadline / portal timeout | 45 s / 60 s |
When a budget is exceeded, reduce fields, levels, sources or the time range. A bounded subset must not be interpreted as a complete result. Keep request_id with diagnostics. Use bounded retries with backoff; do not retry validation errors without changing the request.
Request errors use error.code and error.message. An HTTP 503 response for entirely unavailable sources may retain the regular results with issues; inspect error first, then per-query results.
Browser use and CORS
On the portal page fetch("/api/weather/query", …) uses the same origin. External applications should use the full portal URL; public read access supports CORS and OPTIONS preflight for POST application/json. Use credentials:"omit"; do not send portal cookies to an external site.
const controller = new AbortController();
const pending = fetch('/api/weather/query', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'omit',
signal: controller.signal,
body: JSON.stringify(request)
});
// When another point is selected:
controller.abort();Canceling fetch prevents a stale result from being applied in the UI; it does not guarantee immediate server-side SQL cancellation. Interactive maps should keep current values, forecasts and features independent, and check the selected-point identity before displaying a result.
Aerogram through the common API
The Portal and SDK aerogram obtain forecast times through availability and the atmospheric column through samples. Both use POST /api/weather/query. SDK getSounding() converts the common fields/samples into the sounding-1 rendering model without calling a separate profile API.
{
"schema_version": "weather-query-1",
"point": {
"lat": 55.41,
"lon": 37.91
},
"queries": [
{
"id": "availability",
"kind": "availability",
"source": {
"model": "noaa"
}
},
{
"id": "sounding",
"kind": "samples",
"source": {
"model": "noaa"
},
"preset": "atmospheric_profile",
"time": {
"at": "2026-10-21T18:00:00Z",
"selection": "nearest",
"max_offset_seconds": 86400,
"tie_break": "earlier"
}
}
]
}preset:"atmospheric_profile" returns only the native parameters and levels required by the aerogram: TMP, DPT, RH, HGT, PRES, UGRD and VGRD. Read each field unit: values are not always normalized. Do not combine different valid_time samples; preserve gaps as null. HGT is not replaced with standard-atmosphere estimates; missing DPT may be derived from TMP/RH with an explicit quality flag.
The latest model run is selected by default. To select a specific run, pass run:{policy:"exact",at:"…Z"} to both queries. Availability does not establish coverage at the selected point. The old /api/grib/profile and /api/grib/profile-series endpoints are unchanged; other consumers may still use them.
Migration from existing weather APIs
| Existing scenario | New query |
|---|---|
| /api/weather/point | samples; forecasts use a separate samples query with time.from/grid:native. |
| /api/weather/point-info | Rasters use samples; features and observations use features. |
| /api/weather/point-context | OPMET/vectors use features; column uses diagnostics; extra uses samples fields:"all". |
| /api/weather/point-forecast | daily with preset:near_surface_weather; numerical-only requests use samples. |
| /api/weather/point-forecast/dates | availability |
The listed legacy routes have been removed from the Portal and Hub and return 404. Martin Tiles no longer exposes /api/weather/point either. Use POST /api/weather/query; there are no redirects or network fallbacks to the retired APIs. Internal Hub consumers use the same query engine.
The URLs and contracts of /api/grib/profile, /api/grib/profile-series and /api/grib/getMeteoByTypeInPoint are unchanged. The Portal and SDK aerogram now use the common API as described above; other legacy consumers migrate separately.
Atmospheric profile inputs
For samples, preset: "atmospheric_profile" replaces fields/layers and selects TMP, DPT, RH, UGRD, VGRD and HGT at available pressure levels; PRES/HGT at the surface; TMP/DPT/RH at 2 m and UGRD/VGRD at 10 m. The response keeps the common fields/samples structure, native units, quality and missing-value markers. It does not calculate diagnostics or invent missing levels. Batch availability and samples in one request to obtain dates and the profile from one snapshot. The SDK uses this batch and does not issue a separate capabilities request before opening the aerogram.