API overview

Base URL, auth, errors, idempotency, rate limits

The OpenSense HTTP API is a small REST surface plus a single ingest endpoint and a few non-REST helpers. Everything is JSON over HTTPS. There is one production base URL and no staging environment.

Base URL

All endpoints are versioned under /v1. The version is major version only. Backwards-compatible changes go inside v1; breaking changes ship as /v2 with a 12-month overlap.

Authentication

Three token types, all carried in the Authorization: Bearer <token> header unless otherwise noted.

TokenPrefixScopeRotation
Device tokends_live_one device's ingest endpointsper device, any time
User API tokenua_live_management endpoints (CRUD)per user, any time
Webhook secretwhk_inbound from Shelly / TTN URLsper integration

Device tokens are issued by the dashboard when you add a device. They can be embedded in a query string (?token=…) for the legacy Shelly path; everywhere else, use the Authorization header.

Magic-link auth is the web UI's auth, not the API's. The web UI exchanges the magic-link code for a session cookie; the cookie is not intended for use against the API.

Errors

All errors are JSON with a stable shape:

{
  "error": {
    "code":   "channel_not_found",
    "message": "No channel with id chn_4f3c... in this account",
    "request_id": "req_b1d2e3f4"
  }
}

code is the stable, machine-readable identifier. message is human and may change wording. request_id is what you put in a support email — we keep server-side logs keyed by it for 30 days.

HTTPMeaningCommon codes
400Bad request — payload invalidbad_json, invalid_measurement
401Missing or wrong tokenauth_required, token_invalid
403Token correct, scope wrongforbidden
404Resource does not exist for this accountdevice_not_found, channel_not_found
409Idempotency conflict — different body, same keyidempotency_conflict
422Payload parsed but semantically invalidout_of_range, unknown_kind
429Rate limitedrate_limited
5xxServer error — safe to retry with backoffinternal, db_unavailable

Idempotency

Mutating endpoints (everything that is POST/PUT/PATCH/DELETE and not strictly idempotent on its own) accept an Idempotency-Key request header. Pick any opaque string up to 128 chars; we recommend a UUID.

OpenSense remembers the (account, idempotency_key) tuple for 24 hours. A repeated request with the same key and the same body returns the same response (cached). A repeated request with the same key and a different body returns 409 idempotency_conflict.

The ingest endpoint (POST /v1/ingest) is content-idempotent: the deduplication key is (device_id, ts, channel_id). You do not need to send an Idempotency-Key for ingest; sending the same measurement twice is a no-op.

Rate limits

Per-account, per-route family. Limits are advisory — we will warn before enforcing, and the limit grows automatically with your subscription.

Route familyDefault limitHeader
POST /v1/ingest60 req / min / deviceX-RateLimit-Device
GET /v1/measurements120 req / min / accountX-RateLimit-Read
POST /v1/reports4 req / min / accountX-RateLimit-Report
* /v1/* (everything else)600 req / min / accountX-RateLimit-Mgmt

Responses include X-RateLimit-Remaining and X-RateLimit-Reset (epoch seconds). 429 responses include Retry-After (seconds).

Pagination

List endpoints use cursor pagination. Two query params:

  • limit: 1–500, default 100.
  • cursor: opaque, returned in the response body as next_cursor.

Response shape:

{
  "data": [  ],
  "next_cursor": "eyJ0cyI6...",
  "has_more": true
}

When has_more is false, next_cursor is null.

Timestamps

  • All timestamps are RFC 3339 with explicit timezone offset, always UTC (Z).
  • Resolution: microseconds for measurement ts, seconds elsewhere.
  • The server tolerates RFC 3339 with non-UTC offsets on input and converts.

Content negotiation

We accept and emit application/json. The reports endpoint additionally emits application/pdf. Accept: application/json is the default; no other formats are negotiated.

SDKs

There is no first-party SDK yet. The HTTP surface is small enough that hand- written code is fine. See the language tabs on each endpoint page for copy-paste recipes in Python, Go, ESPHome, and Arduino.