API access
Create a key and call the Live3 Data API — auth, endpoints, pagination, and errors.
The Live3 Data API is a read-only, server-to-server REST interface for pulling your organization's weather and sensor-station data into your own systems (SCADA, BI tools, custom dashboards) using an API key — no browser login. Every request is authenticated with the key and returns JSON.
| Base URL | https://live3.aeronsystems.com/api/v1 |
| Protocol | HTTPS, REST, JSON responses |
| Auth | Authorization: Bearer API key (organization-scoped) |
| Version | v1 |
The API exposes three endpoints:
| Endpoint | Purpose |
|---|---|
GET /stations | List the stations this key can access. |
GET /parameters | List the parameter codes a station reports, with units and human-readable names. |
GET /timeseries | Retrieve the data: the latest packet, or a paginated window of history. |
A typical integration calls /stations once to discover station IDs,
/parameters to learn what each station measures, then polls or back-fills
/timeseries for the data.
Getting an API key
API keys are created from the Live3 dashboard. They are owned by your organization, not by an individual user, so a key keeps working regardless of who created it.
Open the API Keys page. Sign in to the Live3 dashboard, open the account menu in the top-right, and choose API Keys. This is an admin-only area — if you don't see it, ask an organization admin.
Create a key. Click Create key, give it a descriptive name (e.g.
scada-prod or grafana-readonly), and choose its station scope:
- All stations — the key reaches every station your organization can access.
- Specific stations — narrow the key to a chosen subset.
Copy the secret now. The full key (live3_sk_…) is shown exactly once,
at creation, with a copy button. Store it somewhere safe immediately — it
cannot be retrieved again. If you lose it, revoke the key and create a new one.
Treat the key like a password. Use it only from a server or trusted backend,
never in public or browser-side code. You can revoke or rotate a key at any
time from the same API Keys page; a revoked or unknown key immediately returns
401 invalid_api_key.
Authentication
Every request must carry your API key as a Bearer token in the Authorization
header. There are no other credentials, cookies, or sessions.
curl -H "Authorization: Bearer live3_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
"https://live3.aeronsystems.com/api/v1/stations"Key facts
- Organization-scoped. A key reaches every station your organization can access, unless it was narrowed to specific stations at creation.
- Secret. Shown only once, at creation. Store it securely.
- Revocable. Revoke or rotate from the dashboard at any time.
Conventions
Timestamps
Every reading carries its time in two forms: timestamp, an ISO 8601 string in
UTC (e.g. 2026-06-22T10:38:00.000Z), and epoch_ms, the same instant as
integer milliseconds since the Unix epoch. The from, to, and cursor query
parameters accept either form.
Errors
Any non-2xx response uses a single, stable JSON shape:
{
"error": {
"code": "forbidden_station",
"message": "The requested station is not accessible with this API key.",
"request_id": "a772dc54-6060-42d6-a1e3-87c9ecf7556f"
}
}Branch your code on the machine-readable code, not on message (which may be
reworded). Include request_id when contacting support so the request can be
found in the logs. The full list is in Error reference.
Rate limits
Requests are metered per key in three independent cost buckets. Every response includes the current budget in headers:
X-RateLimit-Bucket: range_raw
X-RateLimit-Limit-Minute: 5
X-RateLimit-Remaining-Minute: 4
X-RateLimit-Limit-Day: 500
X-RateLimit-Remaining-Day: 498
X-RateLimit-Reset: 1782124740Exceeding a limit returns 429 rate_limited with a Retry-After header
(seconds to wait). A robust client reads Retry-After and pauses. The buckets
and ceilings:
| Bucket | Applies to | Per minute | Per day |
|---|---|---|---|
cheap | /stations, /parameters, /timeseries (latest) | 60 | 10,000 |
range_1min | /timeseries range, resolution=1min | 20 | 2,000 |
range_raw | /timeseries range, resolution=raw | 5 | 500 |
Endpoints
GET /stations
Lists the stations the key can access. No query parameters.
{
"stations": [
{
"id": "50eae27f-1231-4393-a0b5-91dbd3d42983",
"imei": "220011572929158",
"name": "WMS_AERON 2_5TH FLOOR",
"type": "Data logger (Standard)",
"location": null,
"latitude": null,
"longitude": null,
"timezone": "Asia/Kolkata",
"status": "active",
"lastDataAt": "2026-06-22T10:38:00.000Z",
"expiryDate": "2027-04-23T19:09:02.274Z",
"region": "india",
"pinned": false
}
]
}The id is what you pass as the station parameter everywhere else. status
is the live state (active / offline). lastDataAt is the most recent
packet time.
GET /parameters
Lists the parameter codes reported by your stations, each with a unit and a
human-readable description. The optional query parameter station=<id> scopes
the catalog to one station; without it, the catalog is the union across all
accessible stations.
GET /api/v1/parameters?station=50eae27f-1231-4393-a0b5-91dbd3d42983{
"parameters": [
{ "code": "6a01dccd88500", "unit": "m/s", "description": "Wind Speed" },
{ "code": "6a01dd8a5c599", "unit": "°", "description": "Wind Direction" },
{ "code": "6a02fad23fa0d", "unit": "mm", "description": "RAIN DAILY CUMMULTIV" },
{ "code": "6a02fb5ce224e", "unit": "W/m²", "description": "RaZon+_DHI" }
]
}Parameter codes are device-specific identifiers (for example
6a01dccd88500), not friendly names. The data returned by /timeseries is
keyed by these same codes, so use /parameters to map a code to its
description and unit.
GET /timeseries
The data endpoint. It has two modes, selected by whether you pass a time range:
- Latest — pass only
station. Returns the single most recent packet. - Range — pass
station,from, andto. Returns packets in that window, oldest first, paginated.
Query parameters
| Parameter | Required | Description |
|---|---|---|
station | yes | Station id (from /stations). Must be reachable by the key. |
from | range | Start of window, inclusive. ISO 8601 or epoch ms. |
to | range | End of window, inclusive. ISO 8601 or epoch ms. |
resolution | no | raw = every packet; 1min = one row per minute. Default 1min. |
parameters | no | Comma-separated codes to return (e.g. 6a01dccd88500,6a01dd8a5c599). Omit for all. |
limit | no | Page size, 1 to 5000. Default 1000. |
cursor | no | Pagination token: pass the previous response's next_cursor to get the next page. |
Latest mode
GET /api/v1/timeseries?station=50eae27f-1231-4393-a0b5-91dbd3d42983{
"station": "50eae27f-1231-4393-a0b5-91dbd3d42983",
"resolution": "latest",
"reading": {
"timestamp": "2026-06-22T10:38:00.000Z",
"epoch_ms": 1782124680000,
"values": {
"6a01dccd88500": 4.025274,
"6a01dd8a5c599": 203.144996,
"6a02fad23fa0d": 0.254
}
}
}values has one entry per parameter code; resolve each code to its name and
unit via /parameters. If the station has never reported, reading is null.
Range mode
GET /api/v1/timeseries?station=50eae27f-1231-4393-a0b5-91dbd3d42983
&from=2026-06-22T08:40:00Z&to=2026-06-22T10:40:00Z
&resolution=1min&limit=3¶meters=6a01dccd88500,6a01dd8a5c599{
"station": "50eae27f-1231-4393-a0b5-91dbd3d42983",
"resolution": "1min",
"from": "2026-06-22T08:40:00.000Z",
"to": "2026-06-22T10:40:00.000Z",
"count": 3,
"has_more": true,
"next_cursor": 1782117720000,
"readings": [
{ "timestamp": "2026-06-22T08:40:00.000Z", "epoch_ms": 1782117600000,
"values": { "6a01dccd88500": 2.079987, "6a01dd8a5c599": 227.103155 } },
{ "timestamp": "2026-06-22T08:41:00.000Z", "epoch_ms": 1782117660000,
"values": { "6a01dccd88500": 2.253820, "6a01dd8a5c599": 248.702174 } },
{ "timestamp": "2026-06-22T08:42:00.000Z", "epoch_ms": 1782117720000,
"values": { "6a01dccd88500": 2.859851, "6a01dd8a5c599": 250.531489 } }
]
}Response fields (range mode)
| Field | Meaning |
|---|---|
count | Number of readings in this page. |
has_more | true if more packets remain in the window after this page. |
next_cursor | Pass as cursor on the next call to fetch the next page. null when done. |
readings[].timestamp | Packet time, ISO 8601 UTC. |
readings[].epoch_ms | Packet time, epoch milliseconds (this is the cursor value). |
readings[].values | Object of parameter code → value for that packet. |
Window caps. A single range request may span at most 30 days at
resolution=raw and 90 days at resolution=1min. A larger from..to
returns 400 range_too_large. For longer history, advance the window (see the
recipe below).
Common recipes
Get the current snapshot
One call, no range. Returns the latest packet for a station.
curl -H "Authorization: Bearer $LIVE3_KEY" \
"https://live3.aeronsystems.com/api/v1/timeseries?station=$STATION"Get all data packets for a period
The canonical "give me everything" pattern: request a window, then keep calling
with cursor=next_cursor until has_more is false. Each packet appears
exactly once, in time order. The helpers below also advance the window so you
can pull spans longer than the per-request cap.
import requests, time
BASE = "https://live3.aeronsystems.com/api/v1"
KEY = "live3_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
HEAD = {"Authorization": f"Bearer {KEY}"}
DAY = 86_400_000
CAP = {"raw": 30 * DAY, "1min": 90 * DAY}
def fetch_history(station, start_ms, end_ms, resolution="1min", parameters=None):
"""Yield every packet in [start_ms, end_ms], oldest first."""
win_start = start_ms
while win_start <= end_ms:
win_end = min(win_start + CAP[resolution], end_ms)
cursor = None
while True:
q = {"station": station, "to": win_end,
"resolution": resolution, "limit": 5000}
if parameters: q["parameters"] = parameters
if cursor is None: q["from"] = win_start
else: q["cursor"] = cursor
r = requests.get(f"{BASE}/timeseries", headers=HEAD, params=q)
if r.status_code == 429: # respect rate limits
time.sleep(int(r.headers.get("Retry-After", "5"))); continue
r.raise_for_status()
body = r.json()
for row in body["readings"]:
yield row
if not body["has_more"]:
break
cursor = body["next_cursor"]
win_start = win_end + 1 # next window
# Example: pull a full day of raw packets
rows = list(fetch_history(STATION, 1782086400000, 1782172800000, "raw"))
print(len(rows), "packets")cursor=""; to=$(($(date +%s%3N)))
from=$((to - 3600000))
while :; do
url="$BASE/timeseries?station=$STATION&to=$to&resolution=raw&limit=5000"
[ -z "$cursor" ] && url="$url&from=$from" || url="$url&cursor=$cursor"
resp=$(curl -s -H "Authorization: Bearer $LIVE3_KEY" "$url")
echo "$resp" | jq -c ".readings[]"
[ "$(echo "$resp" | jq .has_more)" = "true" ] || break
cursor=$(echo "$resp" | jq .next_cursor)
doneResolutions: raw vs 1min
raw returns every ingested packet exactly as the station sent it, at the
station's own reporting interval. Use it when you need full fidelity. 1min
returns one pre-aggregated row per minute and is lighter and faster for charts
and long spans. Both share the same values shape, so you can switch with a
single parameter.
Only the station's catalog parameters — those listed by /parameters — are
returned. Calculated/derived and unregistered channels are not exposed.
Error reference
| HTTP | code | When |
|---|---|---|
| 401 | invalid_api_key | Missing, malformed, unknown, or revoked key. |
| 400 | invalid_request | Missing station, or an unrecognized resolution value. |
| 403 | forbidden_station | The station is not reachable by this key. |
| 400 | incomplete_range | A range is missing from/to (and no cursor was given). |
| 400 | invalid_range | from/to/cursor not parseable, or from is after to. |
| 400 | range_too_large | Window exceeds the cap (30d raw, 90d 1min). Narrow it or page. |
| 400 | unknown_parameter | A requested parameter code is not in the station's catalog. |
| 429 | rate_limited | Bucket limit exceeded. Wait Retry-After seconds and retry. |
| 500 | internal_error | Unexpected server error. Retry; if it persists, send the request_id to support. |
Notes on values
- Values are numbers in the parameter's native unit (see
/parameters). A value may be absent from a packet if the sensor did not report it. - A sentinel of
999999.999999means the sensor reported no usable value for that parameter at that time; treat it as missing, not a real reading. - Every parameter code returned by
/timeseriesalso appears in/parametersfor that station, so you can always resolve it to a name and unit. Codes are stable per device but differ between devices — always resolve through/parametersrather than hard-coding names.

