Telemetry
Browse docs

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. Configure and send an event
  2. Send a batch
  3. Run a query
  4. Retry and failure policy
  5. Verify and troubleshoot

PHP HTTP Integration

PHP's cURL extension can call the Telemetry HTTP API without an additional client package. Keep the API key in server-side configuration and use explicit connection and request timeouts.

Configure and send an event

<?php

$apiKey = getenv("TELEMETRY_API_KEY");
if (!is_string($apiKey) || $apiKey === "") {
    throw new RuntimeException("TELEMETRY_API_KEY is not configured");
}

$payload = [
    "table" => "api_request_completed",
    "data" => [
        "event_id" => "evt_request_101",
        "route_template" => "/api/projects/:id",
        "method" => "GET",
        "status_code" => 200,
        "status" => "success",
        "latency_ms" => 184,
    ],
];

$request = curl_init("https://api.telemetry.sh/log");
curl_setopt_array($request, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CONNECTTIMEOUT_MS => 2_000,
    CURLOPT_TIMEOUT_MS => 10_000,
    CURLOPT_HTTPHEADER => [
        "Authorization: " . $apiKey,
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR),
]);

$body = curl_exec($request);
$curlError = curl_error($request);
$status = curl_getinfo($request, CURLINFO_HTTP_CODE);
curl_close($request);

if ($body === false || $status < 200 || $status >= 300) {
    throw new RuntimeException(
        "Telemetry log failed with HTTP " . $status .
        ($curlError !== "" ? " and a transport error" : "")
    );
}

$response = json_decode($body, true, flags: JSON_THROW_ON_ERROR);

Telemetry adds timestamp_utc. Do not send credentials, authorization headers, cookies, request input, exception text, or private customer content. Use a write-scoped key for ingestion.

Send a batch

The data value may be an array of compatible rows:

$payload = [
    "table" => "job_completed",
    "data" => [
        [
            "event_id" => "evt_job_101",
            "job_name" => "invoice_sync",
            "status" => "success",
            "duration_ms" => 912,
        ],
        [
            "event_id" => "evt_job_102",
            "job_name" => "invoice_sync",
            "status" => "failed",
            "duration_ms" => 2401,
            "error_type" => "provider_timeout",
        ],
    ],
];

Keep batches bounded and schema-compatible. If the application queues events, define maximum depth, maximum age, overflow behavior, retry budget, and shutdown handling.

Run a query

Use a read-scoped key:

<?php

$sql = <<<'SQL'
SELECT
  route_template,
  COUNT(*) AS requests,
  ROUND(AVG(latency_ms), 0) AS avg_latency_ms
FROM api_request_completed
WHERE timestamp_utc >= now() - INTERVAL '24 hours'
GROUP BY route_template
ORDER BY requests DESC;
SQL;

$request = curl_init("https://api.telemetry.sh/query");
curl_setopt_array($request, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CONNECTTIMEOUT_MS => 2_000,
    CURLOPT_TIMEOUT_MS => 30_000,
    CURLOPT_HTTPHEADER => [
        "Authorization: " . $apiKey,
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS => json_encode(["query" => $sql], JSON_THROW_ON_ERROR),
]);

$body = curl_exec($request);
$status = curl_getinfo($request, CURLINFO_HTTP_CODE);
curl_close($request);

if ($body === false || $status < 200 || $status >= 300) {
    throw new RuntimeException("Telemetry query failed with HTTP " . $status);
}

$results = json_decode($body, true, flags: JSON_THROW_ON_ERROR);
foreach ($results["data"] ?? [] as $row) {
    // Validate expected keys and nulls before using the row.
}

Use the asynchronous Query API for large JSON or Parquet exports.

Retry and failure policy

A timeout is ambiguous: the server may have accepted the event before the response was lost. Retry only transient connection failures, 429, 502, 503, and 504. Reuse event_id, apply backoff with jitter, and cap attempts. Do not retry an unchanged 400.

For ordinary analytics, telemetry failure should not replace a completed customer response. Use an application-owned durable outbox for billing or approved audit events that cannot be dropped.

Verify and troubleshoot

Query the newest rows and inspect the schema through GET /tables/<table>/schema. Exercise success, failure, retry, and timeout branches.

  • Empty API key: validate server-side configuration before constructing the request.
  • curl_exec returns false: record curl_errno and a controlled error category, not the key or payload.
  • 401 or 403: replace the key or correct its scope.
  • 400: inspect table naming, JSON shape, and type compatibility.
  • Process shutdown: direct HTTP calls have no background queue to flush; track required requests or persist events first.

See the Laravel integration, Log API, rate limits, and batching guide.

Related product capability

Capture stable event names, typed fields, and privacy-reviewed context.

Ownership and technical references

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

Review the editorial standard