Download everything as a CSV
Get a CSV file with your readings across one or more devices over a time range.
Picking a time range
Either use ?last=... (last=30d for the last 30 days,
last=7d for last week), or set ?from=...&to=... with ISO
8601 UTC timestamps. With nothing set, you get the last 24
hours.
Maximum range per call: 90 days.
Two shapes
?shape=long(default), one row per (device, bucket). Columns:bucket_at, device_id, display_name, co2_ppm, temp_c, humidity_pct, lux, spl_db, valid_mask. Easy to filter and group.?shape=wide, one row per bucket, one column per (device × metric). The shape spreadsheets actually want. Column names likelocker_north.co2_ppmare derived from each device’s display name.
Auth: header or query string
For automated scripts, use the Authorization header like
every other endpoint. For Google Sheets =IMPORTDATA() and
Excel “From Web”, pass the key as ?key=... instead, those
tools can’t set headers.
URLs containing ?key=... get logged everywhere they go.
Create a dedicated key per spreadsheet feed so rotation has
small blast radius.
Limits
- Range cap: 90 days per request
- Row cap: 250,000 rows per request. Wider requests return 413, narrow the range or device list and retry
- Floats rounded to 2 decimals
- Timestamps ISO 8601 UTC (Excel parses them natively)
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.
Authorizations
Your API key. Create one at dashboard.1st.app/integrations/api-keys.
Query Parameters
Optional. The earliest time you want readings from. Format:
ISO 8601 UTC like 2026-05-10T00:00:00Z (the Z means UTC).
If you leave this off, the default range is the last 24
hours. To pull a different range, either set from and
to, or use the simpler ?last=... shorthand below.
If your team thinks in local time, convert to UTC first.
"2026-05-10T00:00:00Z"
Optional. The end time. Defaults to "now" if you set from
and leave this off, so "everything from May 10 onwards" is
just ?from=2026-05-10T00:00:00Z.
Same format as from: 2026-05-11T00:00:00Z.
Must be later than from. The total range can't be more
than 90 days.
"2026-05-11T00:00:00Z"
Shorthand for "the last N minutes / hours / days, up to
right now". Use this instead of from and to when you
don't want to compute timestamps yourself.
Format: a number followed by m (minutes), h (hours), or
d (days). Examples:
last=15m— last 15 minuteslast=24h— last 24 hourslast=7d— last 7 dayslast=30d— last 30 days
Maximum 90 days. If you pass last, any from / to you
also sent are ignored.
"30d"
Optional. Scope readings to a single assignment's period
(when that athlete owned the device, when that room used it).
Get IDs from GET /v1/devices/{device_id}/assignments.
With this set, the time window defaults to the assignment's
[started_at, ended_at). If you also pass ?from/?to/
?last, the result is the intersection of your range and
the assignment's window — a from outside the assignment
period is valid but returns an empty page.
Returns assignment_not_found (404) if the ID isn't visible
to your team or belongs to a different device on your
team than the one in the URL. (The two cases are
intentionally not distinguished — the response shape can't
be used to enumerate which UUIDs map to which sensors within
your team.)
"f4e9b8d2-1c3a-4f5b-9d6e-2a8b1c4d5e6f"
Optional. Limit the export to specific devices. Pass a
comma-separated list of device IDs (the UUIDs you get from
GET /v1/devices).
Example: ?devices=8a72a1c7-3c91-...,b3f9d2e8-7a4c-...
Leave this off to export every device on your team. If
you've also set ?assignment_id=, the device filter is
forced to the assignment's device — pass the matching ID
or omit ?devices= to avoid an empty result.
"8a72a1c7-3c91-4f5b-b39e-1d2c4e3f5a7b,b3f9d2e8-7a4c-4e6f-8d1b-0c5a9e7b3d2f"
How to organize the rows.
long (default), one row per reading. Each row tells
you "at this time, this device measured these values."
Easy to filter and group by device or by metric.
wide, one row per timestamp. The row has one column
per (device × metric), like
locker_north.co2_ppm, locker_north.temp_c, recovery_pool.co2_ppm.
This is the layout Google Sheets and Excel charts want.
long, wide Your API key, passed in the URL. Use this only when
you're calling from Google Sheets =IMPORTDATA() or
Excel's "From Web" connector, those tools can't set a
custom Authorization header. For every other case, use
the Authorization: Bearer ... header instead.
URLs with ?key=... show up in your browser history,
anyone you share the sheet with, and any HTTP log along
the way. Treat them like passwords. Create a separate
dedicated key per spreadsheet so rotating it costs you
one feed instead of all of them.
"1st_sk_abcdwxyz23456789.qrstuvwxyz23456789abcdefghijklmn"
Response
The CSV body. Save it to a file.
The response is of type string.
"bucket_at,device_id,display_name,co2_ppm,temp_c,humidity_pct,lux,spl_db,valid_mask\n2026-05-14T20:35:00Z,8a72a1c7-3c91-4f5b-b39e-1d2c4e3f5a7b,Locker Room North,920,21.8,51.2,410,52.0,55\n2026-05-14T20:30:00Z,8a72a1c7-3c91-4f5b-b39e-1d2c4e3f5a7b,Locker Room North,905,21.7,51.0,415,49.8,55\n2026-05-14T20:25:00Z,8a72a1c7-3c91-4f5b-b39e-1d2c4e3f5a7b,Locker Room North,890,21.6,50.8,420,48.2,55\n2026-05-14T20:35:00Z,b3f9d2e8-7a4c-4e6f-8d1b-0c5a9e7b3d2f,Recovery Pool Deck,685,24.1,58.4,220,38.5,55\n2026-05-14T20:30:00Z,b3f9d2e8-7a4c-4e6f-8d1b-0c5a9e7b3d2f,Recovery Pool Deck,690,24.0,58.6,225,37.9,55"