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.
Error envelope
Every error response uses the same envelope:code, not on message. Messages are tuned over time for clarity;
codes are a stable contract.
The message field contains the FIX inline — written so an AI agent reading
just the response body can self-correct. Lean on this.
The request_id is also returned in the X-Request-Id header on every
response (including 2xx). Share it with support if you need help.
Status code summary
| HTTP | When |
|---|---|
| 400 | Validation failed (cursor, time range, body). |
| 401 | Auth failed (missing / invalid / revoked / expired key). |
| 403 | Key lacks the required scope (PATCH without read_write). |
| 404 | Sensor not in your team or doesn’t exist. |
| 413 | CSV result exceeds 250,000 rows — narrow the range or sensor list. |
| 422 | Idempotency-Key reused with a different request body. |
| 429 | Rate limit exceeded (see Retry-After). |
| 500 | Backend hiccup. Retry with backoff. |
Code reference
Each entry expands to a short fix; click through for the full page.api_key_missing (401)
api_key_missing (401)
No
Authorization header on the request.
Fix: add Authorization: Bearer 1st_sk_<key_id>.<secret>.
Full page →api_key_invalid (401)
api_key_invalid (401)
Header malformed, key prefix doesn’t match an active key, or wrong
secret. Fix: re-copy the key from
/integrations/api-keys.
Full page →api_key_revoked (401)
api_key_revoked (401)
The key was revoked. Fix: mint a new one.
Full page →
api_key_expired (401)
api_key_expired (401)
The key passed its
expires_at date. Fix: mint a new key,
optionally with a longer expiry preset.
Full page →insufficient_scope (403)
insufficient_scope (403)
Read-scope key calling
PATCH /v1/sensors/{id}. Fix: mint a
new key with read_write scope.
Full page →rate_limit_exceeded (429)
rate_limit_exceeded (429)
600 requests per 10 minutes per key. Fix: wait
Retry-After
seconds. For bulk reads use GET /v1/readings.csv instead of
looping per-sensor.
Full page →cursor_invalid (400)
cursor_invalid (400)
Pagination cursor is malformed or expired. Fix: drop the
cursor parameter and start over.
Full page →cursor_team_mismatch (400)
cursor_team_mismatch (400)
Cursor was issued under a different team’s key. Fix: drop the
cursor and start fresh under the current key.
Full page →
time_range_too_wide (400)
time_range_too_wide (400)
Range exceeds 90 days. Fix: split into windows ≤90 days and
concatenate. For bulk pulls use
/v1/readings.csv.
Full page →time_range_invalid (400)
time_range_invalid (400)
from/to not strict ISO 8601 UTC, or to ≤ from. Fix: use
2026-05-11T00:00:00Z format, to after from.
Full page →sensor_not_found (404)
sensor_not_found (404)
Sensor with this id isn’t in your team (or doesn’t exist).
Fix: list sensors via
GET /v1/sensors first.
Full page →validation_failed (400 / 422)
validation_failed (400 / 422)
Generic input rejection. Also fires as 422 when an
Idempotency-Key is reused with a different request body.
Fix: check the param field for what was rejected.
Full page →internal_error (500)
internal_error (500)
Backend hiccup. Fix: retry with exponential backoff. Share the
request_id with support if it persists.
Full page →