Skip to main content
GET
/
readings.csv
Python
import os, requests

params = {
    "from": "2026-05-01T00:00:00Z",
    "to":   "2026-05-08T00:00:00Z",
    "shape": "wide",
}
with requests.get(
    "https://api.1st.app/v1/readings.csv",
    params=params,
    headers={"Authorization": f"Bearer {os.environ['ST_API_KEY']}"},
    stream=True,
) as r:
    r.raise_for_status()
    with open("readings.csv", "wb") as f:
        for chunk in r.iter_content(chunk_size=8192):
            f.write(chunk)
"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:35:00Z,b3f9d2e8-7a4c-4e6f-8d1b-0c5a9e7b3d2f,Recovery Pool Deck,685,24.1,58.4,220,38.5,55"

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

Authorization
string
header
required

Your API key. Create one at dashboard.1st.app/integrations/api-keys.

Query Parameters

from
string<date-time>

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.

Example:

"2026-05-10T00:00:00Z"

to
string<date-time>

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.

Example:

"2026-05-11T00:00:00Z"

last
string

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 minutes
  • last=24h — last 24 hours
  • last=7d — last 7 days
  • last=30d — last 30 days

Maximum 90 days. If you pass last, any from / to you also sent are ignored.

Example:

"30d"

assignment_id
string<uuid>

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.)

Example:

"f4e9b8d2-1c3a-4f5b-9d6e-2a8b1c4d5e6f"

devices
string

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.

Example:

"8a72a1c7-3c91-4f5b-b39e-1d2c4e3f5a7b,b3f9d2e8-7a4c-4e6f-8d1b-0c5a9e7b3d2f"

shape
enum<string>
default:long

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.

Available options:
long,
wide
key
string

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.

Example:

"1st_sk_abcdwxyz23456789.qrstuvwxyz23456789abcdefghijklmn"

Response

The CSV body. Save it to a file.

The response is of type string.

Example:

"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"