HTTP JSON API¶
When a port is configured, the daemon serves HTTP requests concurrently on background threads with CORS enabled (Access-Control-Allow-Origin: *). Server requests are logged to <database_stem>_server.log. Live receiver requests to GET / serialize by normalized hostname or IP address, including retries. Lock selection performs no DNS lookup, and hostname/IP aliases do not share a lock. SQLite-only requests such as /health and /metrics can proceed while those live requests are blocked.
The server binds to localhost by default; set host in the config (or pass --host) to bind another address, e.g. 0.0.0.0 to serve the local network. When exposing the server, set auth_token in the config - every request must then send an Authorization: Bearer <token> header or it is rejected with 401.
Endpoints¶
| Endpoint | Description |
|---|---|
GET / |
Current readings fetched live from the station |
GET /daily |
Aggregates grouped by date |
GET /hourly |
Aggregates grouped by date and hour |
GET /range |
Aggregates over an arbitrary time window |
GET /health |
Health check; 503 when collection has stalled |
GET /metrics |
Database metrics as JSON or Prometheus text |
GET / - Live Data¶
Returns current sensor readings fetched directly from the weather station, along with human-readable labels.
Concurrent live requests wait for the host lock and each fetch a page. There is
no admission limit, shared-fetch response, or separate queue timeout. Existing
transport timeouts and retry behavior apply after acquiring the lock; DNS
resolution remains the transport's responsibility. Configuration loading rejects
structurally malformed live_data_url and alerts.webhook_url values before
startup without DNS or network requests. HTTP/HTTPS schemes are case-insensitive.
A valid hostname is required, and any supplied port must be in 1–65535.
Raw whitespace and ASCII control characters are rejected before parsing.
Valid URL strings and percent-encoded path/query characters are preserved.
Numeric port-range failures consistently
report URL port must be between 1 and 65535; nonnumeric ports, including
malformed values such as abc:70000, report Port could not be cast to integer value.
Manual URL entry during setup uses the same validator, accepts mixed-case
HTTP/HTTPS, silently re-prompts on blank input, and explains invalid nonblank
entries. Setup rejects surrogate characters in URL/database values before
writing and accepts server ports as ASCII decimal digits in 1–65535, or
blank to disable. Leading zeros are saved as a canonical integer. Generated
TOML preserves accepted strings through escaping; see
setup guidance.
Database paths containing NUL are rejected before SQLite is opened. An
invalid database default requires an explicit replacement. Output filenames
use native filesystem rules and are displayed safely; interactive filename
or save failures retry only output selection. Explicit --config failures
exit with a setup diagnostic.
At runtime, unsupported schemes and other URL validation failures
are reported as
Invalid receiver URL; UTF-8 response-decoding failures use
Receiver request failed. Existing timeout and network diagnostics still apply,
and runtime failures remain recoverable.
Expected webhook validation and delivery failures are logged without stopping
collection, including when an application constructs AlertConfig directly.
Each notifier caches validation for its current exact webhook URL. Invalid URLs
warn once and skip delivery while unchanged, including across concurrent events.
Replacing the URL triggers validation again. Validation does not serialize
webhook requests, and valid-URL transport failures are logged for each failed
attempt.
rainofhourly is the ObserverIP Hourly Rain Rate, measured in inches/hour
or millimeters/hour according to the station's rainfall setting. Its metadata
label includes in/hr or mm/hr; event and accumulated rain remain in inches
or millimeters. Other fields labeled Hourly Rain without Rate are
accumulations. Correcting these metadata units does not convert sensor values
or rename JSON fields.
Response:
{
"data": {
"outTemp": 75.5,
"outHumi": 60.0,
"windspeed": 3.2,
"gustspeed": 8.1,
"eventrain": 0.0
},
"metadata": {
"labels": {
"outTemp": "Outside Temperature",
"outHumi": "Outside Humidity",
"windspeed": "Wind Speed",
"gustspeed": "Gust Speed",
"eventrain": "Event Rain"
}
}
}
GET /daily - Daily Aggregated Data¶
Returns aggregated sensor data grouped by date.
Query Parameters:
| Parameter | Required | Default | Description |
|---|---|---|---|
tz |
Yes | - | Timezone (see Timezone Support) |
q |
Yes | - | Aggregation field(s), repeatable (see Aggregation Fields) |
days |
No | 7 |
Number of prior days to include |
Examples:
/daily?tz=America/New_York&q=avg_outHumi&days=7
/daily?tz=Europe/London&q=min_outTemp&q=sum_eventrain
Response:
{
"data": [
{
"date": "2025-06-26",
"avg_outHumi": 62.3,
"count": 1440
},
{
"date": "2025-06-27",
"avg_outHumi": 58.5,
"count": 1440
}
]
}
GET /hourly - Hourly Aggregated Data¶
Returns aggregated sensor data grouped by date and wall-clock hour. Both
start_date and end_date are inclusive calendar dates. Each date contains
exactly 24 slots (indices 0-23), with null for hours that have no data.
Query Parameters:
| Parameter | Required | Default | Description |
|---|---|---|---|
tz |
Yes | - | Timezone (see Timezone Support) |
q |
Yes | - | Aggregation field(s), repeatable (see Aggregation Fields) |
start_date |
Yes | - | Start date in YYYY-MM-DD format |
end_date |
No | today | Inclusive end date in YYYY-MM-DD format |
date |
- | - | Backward-compatible alias for start_date |
Examples:
/hourly?start_date=2025-06-27&tz=America/Chicago&q=avg_outHumi
/hourly?start_date=2025-06-26&end_date=2025-06-27&tz=%2B05%3A30&q=max_gustspeed
/hourly?date=2025-06-27&tz=UTC&q=avg_outHumi
Response (truncated for brevity):
{
"data": {
"2025-06-27": [
{
"date": "2025-06-27",
"hour": "00",
"avg_outHumi": 72.1,
"count": 60
},
null,
null
]
}
}
The actual array always contains 24 entries, one per wall-clock hour. On an
IANA-timezone fall-back day, both physical occurrences of the repeated hour are
combined in the same slot and its count includes both. On a spring-forward
day, the skipped hour is null.
GET /range - Arbitrary Range Aggregation¶
Returns aggregates over a single arbitrary [start, end) time window.
Query Parameters:
| Parameter | Required | Default | Description |
|---|---|---|---|
tz |
Yes | - | Timezone used to interpret start/end (see Timezone Support) |
q |
Yes | - | Aggregation field(s), repeatable (see Aggregation Fields) |
start |
Yes | - | Window start: ISO date (2026-06-22) or datetime (2026-06-22 06:00:00) |
end |
No | now | Window end (exclusive), same formats as start |
Example:
Response:
{
"data": {
"start": "2026-06-22 04:00:00",
"end": "2026-06-29 04:00:00",
"avg_outTemp": 71.2,
"max_gustspeed": 18.3,
"count": 10080
}
}
start and end in the response are the resolved UTC bounds of the window.
GET /health - Health Check¶
Returns the daemon health status, last observation timestamp, seconds since that observation, and row count.
Response:
{
"status": "ok",
"last_observation_ts": "2026-03-12 10:30:00",
"age_seconds": 42.5,
"row_count": 10000
}
If the most recent observation is older than 180 seconds (the same
threshold status uses for gap detection), status becomes "stale" and
the endpoint responds with HTTP 503 - so Docker HEALTHCHECK, Kubernetes
probes, and uptime monitors alert on stalled collection out of the box. An
empty database reports "ok" with age_seconds: null, so a freshly
deployed daemon passes its readiness probe before the first observation.
This 180-second readiness threshold is independent of the embedded collection monitor's configurable outage threshold (300 seconds by default). That monitor reports an existing stale database immediately at startup, grants startup grace to an empty database, and requires advancing committed observations for recovery. Monitor comparison and supplied start times use UTC for age calculations; naive times mean UTC and aware instants are preserved. Each production check fetches the first row before sampling its single comparison clock. The collector warns if an opaque native SIGTERM handler or worker-thread startup prevents installation of its shutdown translator, leaving the existing handler untouched. See the collection monitor for rejected-timestamp handling, notification behavior, and bounded shutdown.
See Recipes for container and Kubernetes probe configuration.
GET /metrics - Database Metrics¶
Returns database summary statistics. The default response is JSON:
{
"row_count": 10000,
"db_file_size_bytes": 1048576,
"earliest_ts": "2025-01-01 00:00:00",
"latest_ts": "2026-03-12 10:30:00",
"column_count": 25
}
Prometheus format: with ?format=prometheus, or when the Accept header
asks for text/plain/OpenMetrics (as Prometheus scrapers do), the endpoint
serves the Prometheus text exposition format instead - point a Prometheus
scrape job at /metrics and it works without configuration:
# HELP aw2sqlite_observations_total Total number of observations recorded.
# TYPE aw2sqlite_observations_total counter
aw2sqlite_observations_total 10000
# HELP aw2sqlite_db_file_size_bytes Size of the SQLite database file.
# TYPE aw2sqlite_db_file_size_bytes gauge
aw2sqlite_db_file_size_bytes 1048576
# HELP aw2sqlite_last_observation_age_seconds Seconds since the most recent observation.
# TYPE aw2sqlite_last_observation_age_seconds gauge
aw2sqlite_last_observation_age_seconds 42.5
# HELP aw2sqlite_sensor_value Latest sensor readings by sensor name.
# TYPE aw2sqlite_sensor_value gauge
aw2sqlite_sensor_value{sensor="outTemp"} 72.5
See Recipes for a ready-made scrape job.
Aggregation Fields¶
Aggregation fields use the format <function>_<column>, where:
- Functions:
avg,max,min,sum(case-insensitive) - Column: Any physical sensor column name in the database (e.g.
outTemp,outHumi,gustspeed,eventrain)
Ordinary station names remain unchanged. For a normalized or collision-encoded
custom name, find the corresponding physical column name in
aw2sqlite_sensor_columns, an export header, or the generated Datasette
metadata.
Multiple fields can be requested by repeating the q parameter. A count field is always included in the response indicating how many observations were aggregated.
Timezone Support¶
The tz parameter is required for aggregation endpoints and accepts:
| Format | Example | Description |
|---|---|---|
| IANA timezone name | America/New_York, Europe/London, UTC |
Full DST-aware conversion |
| UTC offset with colon | +05:30, -08:00 |
Fixed offset |
| UTC offset without colon | +0530, -0800 |
Fixed offset (HHMM interpreted) |
| Decimal offset | +5.5, -8.0 |
Fixed offset in hours |
URL-encode + as %2B when needed (e.g. %2B05%3A30 for +05:30).
Error Responses¶
All errors return JSON with an error field:
| Status | Cause |
|---|---|
| 400 | Invalid input: bad timezone, date format, date range, aggregation field, or missing required parameters |
| 401 | Missing or wrong bearer token (only when auth_token is configured) |
| 404 | Unknown endpoint |
| 500 | Server error (e.g. weather station unreachable) |
| 503 | /health only: most recent observation is older than 180 seconds |