Threshold alert history and irrigation, spray-window and frost advisory history

The append-only record of threshold alerts sent and of advisories generated, sent or held, with metric, rule, channel, delivery status, validity window and suppression reason. 28 fields across 2 datasets.

Catalogue entries: Alert history, Advisory history.

Questions this data answers

  • Did the farm act on the alerts it paid for: how many fired last month, how many were delivered, how many read?
  • Which fields trigger the same soil-moisture alert week after week, and is the threshold wrong or the irrigation?
  • When was the last spray-window advisory, and did the spray happen inside it?
  • Which advisories were generated but never reached a phone, and why?
  • Did a frost warning go out before last night's minimum, with how much lead time?

2 logs, 2 rules

Threshold alerts are frequent and simple: a reading crossed a bound, a message went out, a row records it, nothing more. Advisories work the other way, rare and worth auditing individually: the platform decided something about irrigation, spraying, frost or crop condition, and the row records that decision whether or not a message followed it.

Threshold alerts

Every farm’s readings get checked continuously against the rules for its agro-ecological zone and crop, each carrying a low bound and a high one. A reading hovering right at a bound never fires twice, and 1 notification per metric per day is the hard ceiling on top of that. Anything raised between 22:00 and 06:00 East Africa Time waits and goes out together at 06:00 as 1 digest. Each row keeps the metric, the reading and which bound it crossed, the rule and its context, the type and channel, the delivery status and the exact text as delivered. When nothing crosses a bound, nothing gets written at all, so this table is, by construction, the history of notifications rather than of readings.

Advisories

4 types exist. Irrigation fires when the water balance crosses readily available water with no rain in the forecast, and comes with a recommended depth attached. Spray window fires when a sprayable threat is elevated in the risk dataset and the hours ahead are suitable, naming the window itself; soil-borne threats such as bacterial wilt never trigger spray advice at all. Frost fires on a forecast minimum below the crop’s own threshold. Vegetation anomaly fires when a field’s satellite index falls against its own history. Every advisory carries its validity window, severity, the model detail behind it and the farmer-facing text, and where one wasn’t sent the suppression reason says why: quiet hours, no phone on the account, cooldown, not prioritised, dry run, send failed. Even an account with no phone numbers at all, the zero-notification setting, keeps building the full advisory record and can read it straight from here.

A worked example

Once a day, in the morning, the advisory modules run. A station’s water balance shows depletion above readily available water, and the forecast carries no rain for the next 3 days, so an irrigation advisory gets generated with a recommended depth and a severity set by how far past the threshold the reading sits, valid for the coming day. The farm has 1 phone slot with the irrigation category switched on and the cooldown already passed, so the row lands as sent by SMS with the delivered text attached. That same evening, the hourly spray module finds late blight at moderate with a clear window the next morning, but the farm’s last spray was only yesterday, so a row gets written with cooldown as its suppression reason and nothing goes out at all.

Alert history

Append-only audit of every threshold evaluation that produced a notification: value, direction, channel, delivery status.

The engine cycle

ColumnMeaning
metricair_temperature, humidity, soil_moisture, soil_temperature, rainfall, wind_speed
value, directionthe reading and whether it crossed the low or high bound
threshold_rule_id, aez_zone_id, crop_type_idwhich rule fired and the context it was resolved in
notification_type, channelthreshold or digest; sms or whatsapp
statussent, failed, queued for the 06:00 digest
message_textthe text as delivered

Rows are append-only. A rule that evaluates and does not fire writes nothing, so the table is the history of notifications, not of evaluations.

Dataset idfarm.alert_events
Cadence10 min engine cycle
EndpointGET /farm/alerts
Key scopefarm:read
Declared fields15
History2026-03 to today
Coverageyour own account
StatusOwn sensors, own account

Narrowed to your own gateways and to devices approved for API exposure.

Only for the account that owns the hardware. Readings and everything computed from them are served to keys on the owning account and to nobody else. The way to get this dataset is a station on your own field, or your own sensors sent in through Sensor Ingest. A NuaSense station on the field is what produces these rows; the hardware is described at nuasense.com/products.

Fields

FieldUnitNote
id—
gateway_id—
dev_eui—
metric—
value—
direction—
triggered_at—
aez_zone_id—
crop_type_id—
threshold_rule_id—
notification_type—
channel—
status—
message_text—
created_at—

Filters

No tag filters. Narrow with start, end, fields and limit.

The same call, three ways

curl -G https://api.nuasense.com/api/nuasense/v1/farm/alerts \
  -H "Authorization: Bearer nua_live_…" \
  -d start=-30d -d start=-30d -d limit=200

Through the MCP connector the same read is the platform_dataset tool with dataset=farm.alert_events. Through a scheduled export it is one job with the same filters, delivered as a file. Every dataset takes the same arguments: start and end, every with agg, fields, limit up to 5,000, order, and the tags it declares.

Advisory history

Irrigation / spray-window / frost / NDVI-anomaly advisories, including dry-run rows with suppressed_reason set.

How a spray-window advisory is decided

suppressed_reasonMeaning
quiet_hoursgenerated between 22:00 and 06:00, held for the digest
no_phonethe account has no phone slot with this category enabled
cooldownthe same advisory had gone out shortly before
not_prioritizeda higher-scored threat was sent instead
dry_runwritten for the record without sending
send_failedthe provider rejected the message; the row keeps the error

Every advisory is written whether or not it was sent. An account with no phone on file still reads its full advisory history here.

Dataset idfarm.advisory_alerts
Cadencedaily
EndpointGET /farm/advisories
Key scopefarm:read
Declared fields13
History2026-03 to today
Coverageyour own account
StatusOwn sensors, own account

Narrowed to your own gateways and to devices approved for API exposure.

Only for the account that owns the hardware. Readings and everything computed from them are served to keys on the owning account and to nobody else. The way to get this dataset is a station on your own field, or your own sensors sent in through Sensor Ingest. A NuaSense station on the field is what produces these rows; the hardware is described at nuasense.com/products.

Fields

FieldUnitNote
id—
gateway_id—
advisory_type—
severity—
valid_from—
valid_until—
message_text—
payload—model detail jsonb
notification_sent—
notification_sent_at—
channel—
suppressed_reason—set when the advisory was generated but not sent
created_at—

Filters

No tag filters. Narrow with start, end, fields and limit.

The same call, three ways

curl -G https://api.nuasense.com/api/nuasense/v1/farm/advisories \
  -H "Authorization: Bearer nua_live_…" \
  -d start=-30d -d start=-30d -d limit=200

Through the MCP connector the same read is the platform_dataset tool with dataset=farm.advisory_alerts. Through a scheduled export it is one job with the same filters, delivered as a file. Every dataset takes the same arguments: start and end, every with agg, fields, limit up to 5,000, order, and the tags it declares.