Aeron Systems
Product

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 URLhttps://live3.aeronsystems.com/api/v1
ProtocolHTTPS, REST, JSON responses
AuthAuthorization: Bearer API key (organization-scoped)
Versionv1

The API exposes three endpoints:

EndpointPurpose
GET /stationsList the stations this key can access.
GET /parametersList the parameter codes a station reports, with units and human-readable names.
GET /timeseriesRetrieve 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: 1782124740

Exceeding 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:

BucketApplies toPer minutePer day
cheap/stations, /parameters, /timeseries (latest)6010,000
range_1min/timeseries range, resolution=1min202,000
range_raw/timeseries range, resolution=raw5500

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, and to. Returns packets in that window, oldest first, paginated.

Query parameters

ParameterRequiredDescription
stationyesStation id (from /stations). Must be reachable by the key.
fromrangeStart of window, inclusive. ISO 8601 or epoch ms.
torangeEnd of window, inclusive. ISO 8601 or epoch ms.
resolutionnoraw = every packet; 1min = one row per minute. Default 1min.
parametersnoComma-separated codes to return (e.g. 6a01dccd88500,6a01dd8a5c599). Omit for all.
limitnoPage size, 1 to 5000. Default 1000.
cursornoPagination 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&parameters=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)

FieldMeaning
countNumber of readings in this page.
has_moretrue if more packets remain in the window after this page.
next_cursorPass as cursor on the next call to fetch the next page. null when done.
readings[].timestampPacket time, ISO 8601 UTC.
readings[].epoch_msPacket time, epoch milliseconds (this is the cursor value).
readings[].valuesObject 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)
done

Resolutions: 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

HTTPcodeWhen
401invalid_api_keyMissing, malformed, unknown, or revoked key.
400invalid_requestMissing station, or an unrecognized resolution value.
403forbidden_stationThe station is not reachable by this key.
400incomplete_rangeA range is missing from/to (and no cursor was given).
400invalid_rangefrom/to/cursor not parseable, or from is after to.
400range_too_largeWindow exceeds the cap (30d raw, 90d 1min). Narrow it or page.
400unknown_parameterA requested parameter code is not in the station's catalog.
429rate_limitedBucket limit exceeded. Wait Retry-After seconds and retry.
500internal_errorUnexpected 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.999999 means 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 /timeseries also appears in /parameters for 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 /parameters rather than hard-coding names.

On this page