Browse docs
API ReferenceUpdated March 27, 2026

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

  1. StartPOST your query to /query/async. The server returns a job_id and status_url.
  2. PollGET the status_url to check progress.
  3. 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: true tells the SDK to start an async query and poll status_url for you
  • format: "json" | "parquet" selects the result format
  • timeout_ms controls how long the SDK polls before failing
  • realtime and json are 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 Request if the JSON body is invalid
  • 400 Bad Request if query is missing or empty
  • 401 Unauthorized if the API key is missing or invalid
  • 402 Payment Required if the account is blocked by a paywall check
  • 429 Too Many Requests if the API key exceeds the gateway rate limit