Query
The Query API lets you run SQL against your Telemetry data.
POST https://api.telemetry.sh/query
Headers
| Name | Type | Description |
|---|---|---|
| Content-Type | String | application/json |
| Authorization | String | Your API key, either as the raw key or as Bearer <key> |
Body
| Name | Type | Required | Description |
|---|---|---|---|
| query | String | Yes | SQL query to execute. |
| realtime | Boolean | No | Defaults to true. Passed through to the query service. |
| json | Boolean | No | Defaults to true. Passed through to the query service. |
Successful response (200 OK)
{
"status": "success",
"data": [
{
"city": "paris",
"average_price": 42.0
}
],
"key_order": ["city", "average_price"]
}
data contains the result rows. key_order preserves the column order from the query result.
Example usage with cURL
To query the average Uber price grouped by city from the table named uber_rides using cURL, you can use the following command:
QUERY=$(cat <<'SQL'
SELECT
city,
AVG(price) AS average_price
FROM
uber_rides
GROUP BY
city
LIMIT
10000;
SQL
)
curl -X POST https://api.telemetry.sh/query \
-H "Content-Type: application/json" \
-H "Authorization: $API_KEY" \
-d "$(jq -n --arg query "$QUERY" '{query: $query, realtime: true, json: true}')"
Using the JavaScript SDK
We recommend using the SDKs for a better developer experience:
import telemetry from "telemetry-sh";
telemetry.init("YOUR_API_KEY");
const results =
await telemetry.query(`
SELECT
city,
AVG(price)
FROM
uber_rides
GROUP BY
city
`);
Async Query
For long-running queries or large result sets, you can use the async query API. Instead of waiting for the full result inline, the server returns a job reference that you poll until the query completes. Once finished, the status response includes a download_url for the result file.
This is especially useful for exporting entire tables, running heavy aggregations, or downloading results as JSON or Parquet files.
How it works
- Start —
POSTyour query to/query/async. The server returns ajob_idandstatus_url. - Poll —
GETthestatus_urlto check progress. - Read results — when the query is completed, download the result file from
download_url.
API Reference
Start an async query
POST https://api.telemetry.sh/query/async
Headers
| Name | Type | Description |
|---|---|---|
| Content-Type | String | application/json |
| Authorization | String | Your API key, either as the raw key or as Bearer <key> |
Body
| Name | Type | Required | Description |
|---|---|---|---|
| query | String | Yes | SQL query to execute. |
| realtime | Boolean | No | Defaults to true. Passed through to the query service. |
| json | Boolean | No | Defaults to true. Passed through to the query service. |
| format | String | No | Result format: "json" (default) or "parquet". |
Response (202 Accepted)
{
"status": "accepted",
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"format": "json",
"status_url": "/query/async/550e8400-e29b-41d4-a716-446655440000"
}
Poll query status
GET https://api.telemetry.sh/query/async/{job_id}
Headers
| Name | Type | Description |
|---|---|---|
| Authorization | String | Your API key, either as the raw key or as Bearer <key> |
Response (200 OK)
{
"status": "success",
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"query_status": "completed",
"format": "json",
"progress_pct": 100,
"message": "Query completed",
"created_at": "2025-02-24T12:00:00Z",
"completed_at": "2025-02-24T12:00:05Z",
"download_url": "https://storage.example.com/results/...",
"download_url_expires_in_seconds": 3600
}
When format is "json", the downloaded file contains query metadata plus the standard query result shape under result:
{
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"format": "json",
"result": {
"data": [
{
"city": "paris",
"average_price": 42.0
}
],
"key_order": ["city", "average_price"]
}
}
The query_status field will be one of:
| Status | Description |
|---|---|
queued |
The query is waiting to be executed |
running |
The query is currently executing |
completed |
Results are ready for download via download_url |
failed |
The query failed. Check the error field. |
Async query with cURL
# 1. Start the async query
QUERY='SELECT * FROM uber_rides'
RESPONSE=$(curl -s -X POST https://api.telemetry.sh/query/async \
-H "Content-Type: application/json" \
-H "Authorization: $API_KEY" \
-d "$(jq -n --arg query "$QUERY" '{query: $query, format: "json", realtime: true, json: true}')")
STATUS_URL="https://api.telemetry.sh$(echo "$RESPONSE" | jq -r '.status_url')"
# 2. Poll until complete
while true; do
STATUS=$(curl -s -H "Authorization: $API_KEY" "$STATUS_URL")
QUERY_STATUS=$(echo "$STATUS" | jq -r '.query_status')
echo "Status: $QUERY_STATUS"
if [ "$QUERY_STATUS" = "completed" ]; then
DOWNLOAD_URL=$(echo "$STATUS" | jq -r '.download_url // empty')
if [ -z "$DOWNLOAD_URL" ]; then
echo "Query completed, but no active download URL is available."
exit 1
fi
curl -sS -o results.json "$DOWNLOAD_URL"
break
elif [ "$QUERY_STATUS" = "failed" ]; then
echo "Query failed: $(echo "$STATUS" | jq -r '.error // .message')"
exit 1
fi
sleep 5
done
# 3. Inspect the standard query result inside the downloaded JSON file
jq '.result' results.json
Example: Exporting an entire table
The async query API is ideal for full table exports. Since there’s no row limit on async queries, you can export everything and download the result as a single file.
# Start the export as Parquet
QUERY='SELECT * FROM uber_rides'
RESPONSE=$(curl -s -X POST https://api.telemetry.sh/query/async \
-H "Content-Type: application/json" \
-H "Authorization: $API_KEY" \
-d "$(jq -n --arg query "$QUERY" '{query: $query, format: "parquet", realtime: true, json: true}')")
STATUS_URL="https://api.telemetry.sh$(echo "$RESPONSE" | jq -r '.status_url')"
# Poll until complete
while true; do
STATUS=$(curl -s -H "Authorization: $API_KEY" "$STATUS_URL")
QUERY_STATUS=$(echo "$STATUS" | jq -r '.query_status')
if [ "$QUERY_STATUS" = "completed" ]; then
DOWNLOAD_URL=$(echo "$STATUS" | jq -r '.download_url // empty')
if [ -z "$DOWNLOAD_URL" ]; then
echo "Export completed, but no active download URL is available."
exit 1
fi
curl -o uber_rides_export.parquet "$DOWNLOAD_URL"
echo "Export complete: uber_rides_export.parquet"
break
elif [ "$QUERY_STATUS" = "failed" ]; then
echo "Export failed: $(echo "$STATUS" | jq -r '.error // .message')"
exit 1
fi
sleep 5
done
JavaScript SDK async query options
The JavaScript SDK exposes a few convenience options on top of the HTTP API:
async: truetells the SDK to start an async query and pollstatus_urlfor youformat: "json" | "parquet"selects the result formattimeout_mscontrols how long the SDK polls before failingrealtimeandjsonare forwarded to the HTTP API
Example:
import telemetry from "telemetry-sh";
telemetry.init("YOUR_API_KEY");
const result = await telemetry.query("SELECT * FROM uber_rides", {
async: true,
format: "parquet",
timeout_ms: 5 * 60 * 1000
});
Common errors
400 Bad Requestif the JSON body is invalid400 Bad Requestifqueryis missing or empty401 Unauthorizedif the API key is missing or invalid402 Payment Requiredif the account is blocked by a paywall check429 Too Many Requestsif the API key exceeds the gateway rate limit