Telemetry
Browse docs
API ReferenceUpdated July 29, 2026Reviewed by the Telemetry editorial and product teams5 min read

Use this doc with your coding agent

Open a focused prompt pack for Claude Code, Codex, Cursor, or another coding agent, then adapt it to the workflow covered here.

On this page
  1. Example usage with cURL
  2. Using the JavaScript SDK
  3. Async Query
  4. How it works
  5. API Reference
  6. Async query with cURL
  7. Example: Exporting an entire table
  8. SDK boundary
  9. Common errors

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

SDK boundary

The current JavaScript SDK's query method calls the interactive /query endpoint. It does not start /query/async, poll status_url, enforce an asynchronous-job timeout, or download the finished artifact.

Use the HTTP start, poll, and download flow above for large JSON or Parquet exports. An application wrapper may encapsulate those three operations, but it should still enforce a total polling deadline, stop on failed, and treat an expired download_url as a new export rather than retrying the old download indefinitely.

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

Related product capability

Run read-only DataFusion SQL over structured-event tables and reuse the result.

Ownership and technical references

The Telemetry editorial team owns this explanation; the product team reviews behavior, examples, and boundaries.

Review the editorial standard