Skip to main content
Download .md

Data API

The Grillo Cloud data API gives your own software read-only access to one project's sensors, health reports, earthquake events, and triggers. Use it to feed a building-management system, a SCADA, a report, or your own dashboard.

  • Base URL: https://api.grillo.io
  • Every response is JSON.
  • The API reads data; it cannot change anything in your project.
  • The full OpenAPI 3.1 description is at https://api.grillo.io/v1/openapi.yaml. It needs no key.

The API is included in every plan. For waveform data, use the waveform download or the real-time export.

Get an API key​

Contact Grillo Support with your project name and what the key is for, for example "Partner SCADA". Grillo issues the key and sends it to you.

  • Each key reads one project.
  • A project can have up to 10 active keys. Use a separate key for each system, so you can revoke one without affecting the others.
  • Grillo stores only a fingerprint of the key, so a lost key cannot be recovered. Ask for a new one.
  • To revoke a key, contact Grillo Support. Anything using it stops working within a few seconds.

Keep keys out of web pages, apps, and source control. Anyone with the key can read the project's data.

Authentication​

Send the key in the Authorization header:

curl -H "Authorization: Bearer grillo_sk_..." https://api.grillo.io/v1/project

Keys start with grillo_sk_. A missing, malformed, or revoked key gets 401 unauthorized.

Conventions​

Times are RFC 3339 in UTC, for example 2026-10-12T14:30:00Z. Query parameters also accept a plain date, such as 2026-10-12, meaning midnight UTC.

Lists are paged with limit (1–500, default 100) and offset (default 0). Responses include the total:

{
"data": [ ... ],
"total": 142,
"limit": 100,
"offset": 0
}

Sensors can be named by Device ID (9C139EAE2360) or station code (HT012).

Locations. If your project has location masking on, sensor positions are 500–1000 m from the true site, as in Grillo Cloud. Location names and GPS positions are then withheld, and elevation is rounded to 100 m.

Freshness. Data can be a few seconds behind Grillo Cloud.

Limits​

Each key can make 60 requests a minute, with bursts of up to 20. Over the limit, the API answers 429 rate_limited with a Retry-After header giving the seconds to wait.

A query that runs too long is stopped and gets 503 timeout. Narrow the time range or the page size and try again.

Errors​

Errors have a code and a message:

{
"error": {
"code": "invalid_range",
"message": "The range can be at most 7 days. Page through longer periods."
}
}
StatusCodeMeaning
400invalid_parameter, invalid_rangeA parameter is wrong. The message says which
401unauthorizedThe key is missing, malformed, or revoked
404not_foundNot found in this project
429rate_limitedToo many requests. Wait for Retry-After seconds
503timeoutThe query took too long
500internal_errorSomething went wrong at Grillo. Try again, then contact support

Endpoints​

All endpoints use GET.

EndpointReturns
/v1/projectThe project the key belongs to
/v1/sensorsThe project's sensors with their current status
/v1/sensors/{id}One sensor
/v1/sensors/{id}/sohA sensor's health reports
/v1/eventsDetected earthquakes
/v1/events/{id}One earthquake with every solution issued for it
/v1/triggersTriggered recordings

Project​

GET /v1/project

{
"id": "…",
"name": "Riverside Tower",
"network_code": "GR",
"sensor_count": 6,
"location_masked": false,
"api_key_id": "…"
}

Sensors​

GET /v1/sensors lists sensors ordered by station code, paged with limit and offset. GET /v1/sensors/{id} returns one.

{
"device_id": "9C139EAE2360",
"station": "TWR01",
"network": "GR",
"sensor_type": "grillo-pulse",
"firmware": "1.0.14",
"status": "online",
"last_seen": "2026-10-12T14:30:05Z",
"location": {
"latitude": 51.5072,
"longitude": -0.1276,
"elevation_m": 42,
"name": "Level 12, east core",
"masked": false
},
"power": { "battery_volts": null, "charging": null, "source": "DC" },
"connectivity": { "type": "cell", "signal_strength": -71 }
}
  • status is online if the sensor reported in the last 150 seconds, as in Grillo Cloud.
  • connectivity.type is wifi, cell, or none.
  • signal_strength is in dBm when negative, and a percentage otherwise.

Sensor health reports​

GET /v1/sensors/{id}/soh

ParameterMeaning
fromStart of the range, exclusive. Defaults to 24 hours before to
toEnd of the range, inclusive. Defaults to now
limitReports per response, 1–5000. Default 1000

The range can be at most 7 days. Reports are oldest first. When more reports remain, next_from is set: request again with from set to next_from and the same to. When next_from is null, you have them all.

{
"device_id": "9C139EAE2360",
"station": "TWR01",
"from": "2026-10-11T14:30:00Z",
"to": "2026-10-12T14:30:00Z",
"next_from": null,
"data": [
{
"timestamp": "2026-10-11T14:30:28Z",
"received_at": "2026-10-11T14:30:28Z",
"firmware": "1.0.14",
"power_source": "DC",
"connection_type": "cell",
"signal_strength": -71,
"uptime_s": 86400,
"reboot_reason": "normal",
"clock_quality": "synced",
"sync_age_s": 120,
"data_tx_bytes": 1048576,
"data_rx_bytes": 65536
}
]
}

Each report can also include battery_volts, charging, temperature_c, free_heap_bytes, buffer_usage_pct, and GPS position. A field the sensor does not report is null.

  • timestamp is the sensor's time for the report; received_at is when Grillo received it.
  • data_tx_bytes and data_rx_bytes count the sensor's data sent and received since it last started. They do not include network overhead, so they are lower than the usage your mobile operator bills.

Events​

GET /v1/events lists earthquakes detected for the project, newest first. Events need a plan with earthquake detection.

ParameterMeaning
from, toIssued at or after from, and at or before to
min_magnitudeSmallest magnitude to include
bboxEpicentre within min_lon,min_lat,max_lon,max_lat, for example -74.5,17.9,-71.6,20.1
orderdesc (default) or asc
limit, offsetPaging
{
"id": "…",
"issued_at": "2026-10-07T03:12:44Z",
"magnitude": 3.6,
"origin": {
"id": "…",
"latitude": 18.52,
"longitude": -72.31,
"depth": 10.2,
"origin_time": 1791342750.4,
"issued_at": 1791342764.1
},
"station_count": 5,
"pick_stations": ["HT012", "HT014", "HT020", "HT031", "HT033"]
}

origin is the latest location. Its depth is in kilometres, and its times are Unix seconds.

GET /v1/events/{id} returns one event with every solution issued for it: origins, magnitudes, picks, amplitudes, and descriptions.

Triggers​

GET /v1/triggers lists triggers, newest first.

ParameterMeaning
from, toStarted at or after from, and at or before to
device_idOnly this sensor's triggers
orderdesc (default) or asc
limit, offsetPaging
{
"id": 4182,
"device_id": "9C139EAE2360",
"station": "TWR01",
"location_name": "Level 12, east core",
"start_time": "2026-10-12T13:55:46Z",
"end_time": "2026-10-12T13:57:49Z",
"peak_time": "2026-10-12T13:56:46Z",
"pga_horizontal_mg": 273.8,
"pga_hne_mg": 201.5,
"pga_hnn_mg": 186.2,
"pga_hnz_mg": 95.4,
"pgv_ehz_mm_s": 12.7,
"peak_ehz_counts": 1843021,
"peak_hz": 7.8,
"status": "closed"
}
  • Accelerations are in mg and velocity in mm/s, measured after filtering to 0.5–25 Hz.
  • status is open while the sensor is still recording, then closed. Values of an open trigger can still change.
  • station is null if the sensor has since moved to another project.

Example: poll for new triggers​

This script checks every minute for triggers that have finished, and prints each one once.

import time
from datetime import datetime, timedelta, timezone

import requests

API = "https://api.grillo.io/v1"
HEADERS = {"Authorization": "Bearer grillo_sk_..."}
seen = set()

while True:
since = datetime.now(timezone.utc) - timedelta(hours=1)
r = requests.get(f"{API}/triggers", headers=HEADERS,
params={"from": since.isoformat(), "order": "asc", "limit": 500})
if r.status_code == 429:
time.sleep(int(r.headers.get("Retry-After", "5")))
continue
r.raise_for_status()
for t in r.json()["data"]:
if t["status"] == "closed" and t["id"] not in seen:
seen.add(t["id"])
print(t["station"], t["peak_time"], t["pga_horizontal_mg"], "mg")
time.sleep(60)

It looks back an hour on every request, so a trigger that was still open on one request is picked up once it closes.