Skip to main content

Documentation Index

Fetch the complete documentation index at: https://dev.1st.app/llms.txt

Use this file to discover all available pages before exploring further.

All timestamps are UTC

Every timestamp the API accepts or returns is ISO 8601 UTC with an explicit Z suffix.
2026-05-11T13:45:00Z
If your AMS or BI tool wants local time, convert client-side. Most clubs ingest UTC and let the visualisation layer present in local time — that sidesteps DST headaches.

Missing fields are NULL, not zero

Per-bucket, each metric is independently nullable. A sensor that fails to read CO2 in a bucket will have co2_ppm: null for that row, while temp / humidity still populate.
{
  "bucket_at": "2026-05-11T13:45:00Z",
  "co2_ppm": null,
  "temp_c": 21.4,
  "humidity_pct": 48.2,
  "lux": null,
  "spl_db": null
}
Do NOT treat null as 0. Filter nulls before averaging.

”Stale” sensors are not necessarily broken

The status field on /sensors:
Statuslast_seen_at age
online< 1 hour
stale1–24 hours
offline> 24 hours or never
A cellular outage shows up as stale then offline. Buffered readings DO arrive when the device reconnects — they just land in a burst, with old bucket_at timestamps. So a sensor in offline status today may still deliver yesterday’s data tomorrow.

Rate-limit timing

The 600-per-10-minutes budget is per API key, not per IP and not per integration. If two scripts share the same key, they share the budget. After a 429, the Retry-After header tells you how many seconds to wait. Worst case: 600 seconds (a full window). Cheap option: throttle to 1 req/sec average and you’ll never hit the limit. For nightly bulk pulls, prefer GET /v1/readings.csv over a /readings loop — one streaming request instead of hundreds of paginated calls, and one rate-limit hit instead of many.

Cursor expiry

Cursors are issued for the moment-in-time of the request. If you save a cursor and resume hours later, the underlying anchor row may have been deleted (sensor archived, retention policy applied) and the cursor returns cursor_invalid. Walk pagination in a single loop, not across runs.

Confirming your team

The docs don’t ask you to substitute a team_id anywhere — the API key already binds you to your team. Call GET /v1/team to see which team your key is scoped to (returns the team’s id and name).

Notecard vs sensor_id

A sensor has TWO IDs:
  • sensor_id — UUID assigned by the platform at factory provisioning. Stable across firmware/hardware changes.
  • hardware_id — Notecard device UID (e.g. dev:868050050000123). Stable across firmware changes but tied to specific physical hardware.
Use sensor_id everywhere in the API. hardware_id is informational (useful for matching against your physical inventory list).

Buffered data is delivered eventually

When a sensor’s cellular link fails, data accumulates in the device’s flash buffer and arrives when connectivity returns — sometimes hours or days later. The ingested_at field tells you WHEN the row reached our backend; bucket_at tells you WHEN the bucket was measured. For “did we get last night’s data” queries, filter on bucket_at. For “what landed in the last 5 minutes” cron jobs, filter on ingested_at.