Logger and gateway integrators
Observations webhook
POST readings for a site from a logger, gateway or app: the key, the item fields, the accepted properties and units, the all-or-nothing and idempotence rules, and the response shape.
Last reviewed
What it is for
A logger, a buoy gateway or a phone app that wants to push readings for a site sends them to POST /sites/{site_id}/observations. The readings land next to the site's other observations (source webhook, scale site) and are used by the signals and the assistant like any other site-scale observation. They are operator records: the agency tools never return them (REST API).
Authentication
Send the deployment's webhook key in the X-API-Key header. It is one machine credential set by the deployment's administrator (not an agency key, not issued through the access request form) and it is administrator-equivalent: give it only to systems you control. When no key is configured the endpoint is disabled and answers 503; a missing or wrong key is 401.
Request
A JSON array of 1 to 10,000 items, each shaped like a SOSA observation (observed property, simple result with unit, result time, sensor):
| Field | Type | Required | Meaning |
|---|---|---|---|
property | string | yes | The observed property: one of the ids in the table below (sst, salinity, hs, ...). |
value | number | yes | The result, a finite number. |
unit | string | yes | The property's canonical unit or a convertible spelling (unit table below); the value is stored in the canonical unit. |
time | string | yes | Result time, ISO 8601 with an offset (Z or +HH:MM); not more than 1 hour in the future. |
depth_m | number | no | Metres below the surface (0 to 11 000); omit for the surface or a value without depth. |
sensor_id | string | no | The sensor, letters, digits and . _ : - (up to 64 characters); each sensor is its own series. |
Observable properties
The property ids the webhook accepts, with the canonical unit each is stored in:
| Property | Canonical unit (QUDT) | Stored as |
|---|---|---|
air_temp | DEG_C | degC |
catchment_rain_mm | MilliM | mm |
chl | MilliGM-PER-M3 | mg/m3 |
condition_index | UNITLESS | 1 |
dir | DEG | deg |
fai | UNITLESS | 1 |
harvest_kg | KiloGM | kg |
hs | M | m |
length_m | M | m |
mld | M | m |
ndvi | UNITLESS | 1 |
no3 | MilliMOL-PER-M3 | mmol/m3 |
o2 | MilliMOL-PER-M3 | mmol/m3 |
par | MOL-PER-M2-DAY | mol/m2/d |
pct_mortality | PERCENT | % |
po4 | MilliMOL-PER-M3 | mmol/m3 |
rain | MilliM | mm |
salinity | PSU | PSU |
sst | DEG_C | degC |
sst20m | DEG_C | degC |
swell_hs | M | m |
tp | SEC | s |
tp_peak | SEC | s |
turbidity | UNITLESS | 1 |
tz | SEC | s |
u | M-PER-SEC | m s-1 |
v | M-PER-SEC | m s-1 |
water_level_lat | M | m |
wet_kg | KiloGM | kg |
wind | KN | kn |
wind_dir | DEG | deg |
Units
Case and spaces are ignored; the QUDT name (for example DEG_C or unit:DEG_C) and the stored spelling are always accepted. Other spellings are converted to the canonical unit; the original value and unit are kept in the row's metadata.
| Canonical (QUDT) | Stored as | Also accepted (converted where needed) |
|---|---|---|
DEG_C | degC | C, celsius, deg C, deg_c, degF, degree_C, degree_Celsius, degrees_C, K, kelvin, °C, °F |
PSU | PSU | 1e-3, PSS-78, pss78, psu |
MilliGM-PER-M3 | mg/m3 | mg m-3, mg.m-3, mg/m^3, milligram m-3, ug L-1, ug/L, ug/l |
MilliMOL-PER-M3 | mmol/m3 | millimol m-3, mmol m-3, mmol.m-3, mmol/m^3, uM, umol L-1, umol/L |
MOL-PER-M2-DAY | mol/m2/d | mol m-2 d-1, mol m-2 day-1, mol/m2/day, mol/m^2/d |
M | m | cm, ft, meter, meters, metre, metres |
SEC | s | min, sec, second, seconds |
DEG | deg | deg true, degree, degrees, degT, ° |
KN | kn | km/h, knot, knots, kt, kts, m s-1, m/s |
M-PER-SEC | m s-1 | cm/s, kn, m.s-1, m/s, ms-1 |
MilliM | mm | cm, in, kg m-2, m |
UNITLESS | 1 | , -, dimensionless, none, ratio, unitless |
KiloGM | kg | g, lb, t, tonnes |
PERCENT | % | fraction, pct, percent |
Rules
- All or nothing. Any invalid item answers
422and nothing is stored.detailis a list of{loc, msg, type}withloc = ["body", <item index>, <field>]. Two items with the same property, sensor, depth and time are refused too (422on the later one). - Idempotent. Re-sending an observation (same site, property, sensor, depth and time) updates its value in place; the response counts it under
updated. - Stored row. Variable = property, value and unit in the canonical unit, time in UTC, product
webhook:<sensor_id>(orwebhookwithout a sensor), metadata with the sensor, the time received and, when converted, the value and unit as sent. 404for an unknown site;409for a site that has no operator account to own the records.- Clock skew: a
timemore than 1 hour in the future is refused.
Example of the shape
Shape only: the values are placeholders, not readings.
curl -X POST https://coastal-intelligence.triskelionaisystems.com/api/sites/<site id>/observations \
-H "X-API-Key: $WEBHOOK_API_KEY" -H "Content-Type: application/json" \
-d '[{"property":"<property id>","value":"<number>","unit":"<unit>","time":"<YYYY-MM-DDThh:mm:ssZ>","depth_m":"<number, optional>","sensor_id":"<id, optional>"},{"property":"<property id>","value":"<number>","unit":"<unit>","time":"<YYYY-MM-DDThh:mm:ss+01:00>"}]'201 Created with:
{
"site_id": "<site id>",
"received": "<number of items>",
"observations": {
"inserted": "<count>",
"updated": "<count>"
},
"variables": [
"<property id>",
"<...>"
],
"products": [
"webhook",
"webhook:<sensor_id>"
]
}Related
Authentication and keys
The request and approval flow, the Authorization and X-API-Key headers, what a key's scope means, revocation, the exact meaning of 401, 403, 404, 422 and 429, and what the access log records.
Last reviewed 1 Nov 2026Data engineers, legal and complianceData model and licences
The five provenance fields on every row, the three scales, what lead_hours means, the public dataset registry, the licence ids and the attribution line each one requires, and the product catalogue.
Last reviewed 1 Nov 2026