Skip to content
Telemetry
Browse docs
API ReferenceUpdated September 2, 2026Reviewed by the Telemetry editorial and product teams13 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. List alerts
  2. Alert fields
  3. Create an alert
  4. Create a query alert
  5. Query payloads
  6. Explorer payloads
  7. Create alerts with a coding agent
  8. Information the agent needs
  9. Recommended request sequence
  10. Prompt for a coding agent
  11. Choose both aggregations deliberately
  12. Example: sustained server error rate
  13. Example: p95 latency spike
  14. Example: missing heartbeat
  15. Example: review and enable a draft
  16. Slug normalization
  17. Edit an alert
  18. Delete an alert
  19. Common errors

Alert

The Alert API lets you provision and manage the same single-series threshold alerts available in the Telemetry UI.

If you are asking a coding agent to provision alerts, start with Create alerts with a coding agent. It gives the agent a safe discovery and validation sequence, an agent-ready prompt, and complete requests for common use cases.

Supported operations:

  • List: GET https://api.telemetry.sh/alert
  • Create: POST https://api.telemetry.sh/alert
  • Edit: PATCH https://api.telemetry.sh/alert
  • Delete: DELETE https://api.telemetry.sh/alert

Headers

Name Type Description
Content-Type String Must be application/json for create, edit, and delete requests.
Authorization String Your API key, either as the raw key or as Bearer <key>.

Scope rules:

  • GET /alert accepts read, write, or read-and-write
  • POST /alert, PATCH /alert, and DELETE /alert require write or read-and-write

List alerts

GET /alert returns alerts for the API key's team, ordered by updated_at DESC.

Supported query parameters:

Field Type Required Exact contract Default
page Integer No Positive, 1-based page number. 1
pageSize Integer No Positive page size. Values above 100 are clamped to 100. The legacy alias page_size is also accepted. 50
curl "https://api.telemetry.sh/alert?page=1&pageSize=25" \
  -H "Authorization: $API_KEY"

The response includes standard pagination metadata and complete alert records:

{
  "status": "success",
  "pagination": {
    "page": 1,
    "pageSize": 25,
    "total": 1,
    "totalPages": 1,
    "hasNextPage": false,
    "hasPreviousPage": false
  },
  "alerts": [
    {
      "id": "d7463946-8c6a-4a54-8a47-74cc98247c54",
      "team_id": "a7d4...",
      "name": "API error rate",
      "slug": "api-error-rate",
      "description": "Notify the API on-call rotation",
      "alert_type": "query",
      "payload": {
        "querySql": "SELECT time_bucket, error_rate FROM api_health",
        "timestampColumn": "time_bucket"
      },
      "aggregation": "avg",
      "metric": "error_rate",
      "last_n_data_points": 3,
      "ignore_last_data_point": true,
      "check_interval_minutes": 60,
      "comparison": "greater_than",
      "threshold": 0.05,
      "recipients": [
        { "type": "email", "recipient": "[email protected]" }
      ],
      "status": "inactive",
      "enabled": true,
      "last_evaluated_at": null,
      "last_value": null,
      "evaluation_version": 0,
      "created_by": null,
      "created_by_api_key_id": "key_123",
      "created_at": "2026-09-02T17:05:00.000Z",
      "updated_at": "2026-09-02T17:05:00.000Z",
      "url": "/team/acme/alert/api-error-rate"
    }
  ]
}

pagination.totalPages is 0 when the team has no alerts. Requesting a page beyond the last page returns an empty alerts array.

Alert fields

Field Type Writable Exact contract
id String No Alert id.
team_id String No Owning team id.
name String Yes Trimmed, non-empty display name of at most 200 characters.
slug String Yes Unique team-scoped slug. See slug normalization.
description String or null Yes Optional description. Empty strings are stored as null.
alert_type String Yes query or explorer. The payload must match the selected type.
payload Object Yes Saved query definition. See query payloads and Explorer payloads.
aggregation String Yes count, sum, avg, min, max, p50, p90, p95, or p99.
metric String or null Yes Numeric result column to aggregate. If null, the evaluator uses the first numeric non-timestamp column. An explicit value is recommended.
last_n_data_points Integer Yes One of 1, 3, 5, 10, 20, 50, or 100.
ignore_last_data_point Boolean Yes Whether to skip the newest result row, which can represent an incomplete time bucket.
check_interval_minutes Integer Yes One of 1, 60, or 1440.
comparison String Yes greater_than, less_than, greater_than_or_equal, or less_than_or_equal.
threshold Number Yes Finite comparison threshold. JSON strings such as "10" are rejected.
recipients Array Yes One to 25 unique email recipient objects. Addresses are trimmed and lowercased.
status String No Current evaluation state: active when the condition is met, otherwise inactive.
enabled Boolean Yes Whether the evaluator should run the alert.
last_evaluated_at String or null No Timestamp of the latest completed evaluation.
last_value Number or null No Latest aggregated value.
evaluation_version Integer No Internal optimistic-concurrency version.
created_by String or null No User id for UI-created alerts; null for API-created alerts.
created_by_api_key_id String or null No API key id for API-created alerts; null for UI-created alerts.
created_at String No Creation timestamp.
updated_at String No Latest update timestamp.
url String No Relative alert URL in the Telemetry UI.

Create an alert

POST /alert creates one alert and returns 201 Created.

Field Required Default
name Yes —
slug No Normalized from name
description No null
alert_type Yes —
payload Yes —
aggregation No avg
metric No null
last_n_data_points No 3
ignore_last_data_point No true
check_interval_minutes No 60
comparison No greater_than
threshold Yes —
recipients Yes —
enabled No true

Create a query alert

curl -X POST https://api.telemetry.sh/alert \
  -H "Authorization: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "API error rate",
    "description": "Notify the API on-call rotation",
    "alert_type": "query",
    "payload": {
      "querySql": "SELECT timestamp_utc AS time_bucket, error_rate FROM api_health ORDER BY timestamp_utc DESC",
      "timestampColumn": "time_bucket",
      "sourceUrl": "/team/acme/default/error-rate/1"
    },
    "aggregation": "avg",
    "metric": "error_rate",
    "last_n_data_points": 3,
    "ignore_last_data_point": true,
    "check_interval_minutes": 60,
    "comparison": "greater_than",
    "threshold": 0.05,
    "recipients": [
      { "type": "email", "recipient": "[email protected]" }
    ]
  }'

Successful response:

{
  "status": "success",
  "alert": {
    "id": "d7463946-8c6a-4a54-8a47-74cc98247c54",
    "name": "API error rate",
    "slug": "api-error-rate",
    "created_by": null,
    "created_by_api_key_id": "key_123",
    "url": "/team/acme/alert/api-error-rate"
  }
}

The returned alert object contains every field shown in Alert fields; the shortened example highlights the creation identity and URL.

Query payloads

Field Type Required Exact contract
querySql String Yes Non-empty, read-only SQL. Statements containing writes or DDL are rejected.
timestampColumn String or null No Result column used to order rows newest first. When omitted or null, Telemetry looks for common timestamp columns.
queryId String or null No Optional saved-query id for attribution.
sourceUrl String or null No Optional relative Telemetry URL for returning to the source query.

The query should return one ordered time series with one numeric value per row. The alert evaluator orders rows by the timestamp column, optionally skips the newest row, takes the requested number of points, aggregates metric, and applies comparison to threshold.

Explorer payloads

Explorer alerts persist a table name and Explorer state:

{
  "name": "Checkout failures",
  "alert_type": "explorer",
  "payload": {
    "tableName": "checkout_events",
    "explorerState": {
      "graphType": "line",
      "aggregation": "count",
      "metric": null,
      "timeZone": "UTC",
      "timePreset": "24h",
      "granularity": "hour",
      "splitBy": [],
      "filters": [
        {
          "logic": "AND",
          "conditions": [
            { "field": "outcome", "operator": "=", "value": "failed" }
          ]
        }
      ],
      "selectedColumns": [],
      "orderBy": null,
      "orderDirection": "DESC",
      "limit": 200
    },
    "fields": [
      { "name": "outcome", "type": "Utf8" }
    ]
  },
  "metric": "count",
  "threshold": 10,
  "recipients": [
    { "type": "email", "recipient": "[email protected]" }
  ]
}

Explorer alert rules:

  • payload.tableName must be a non-empty string.
  • payload.explorerState must be an object and its graphType must be line when specified.
  • splitBy must be empty because an alert evaluates one series.
  • aggregation accepts the same aggregation names as the top-level alert condition. A non-count Explorer aggregation requires explorerState.metric.
  • timePreset accepts 1h, 6h, 24h, 7d, 30d, 90d, or custom.
  • granularity accepts auto, minute, hour, day, week, or month.
  • fields is optional. Include { "name", "type" } records when filters or numeric field selection depend on schema types.
  • Filter operators are =, !=, >, >=, <, <=, LIKE, NOT LIKE, IS NULL, and IS NOT NULL.

Create alerts with a coding agent

An agent can create a syntactically valid alert that still watches the wrong table, uses the wrong unit, or emails the wrong people. Give it an operational goal and require it to inspect and validate the data before it calls POST /alert.

Query alerts are usually the easiest type for an agent to build because it can run the exact SQL through POST /query before saving it. Explorer alerts are useful for standard counts and percentiles because Telemetry generates the time buckets and fills missing buckets with zero.

Information the agent needs

Provide these inputs or tell the agent to stop and ask for them:

  • the condition that should trigger and the unit of its threshold
  • the expected table or event name, if known
  • the environment, service, route, account, or other population to monitor
  • the lookback, bucket size, and how many completed buckets must breach
  • a stable alert name and slug
  • the recipient who owns the response
  • whether the agent may enable delivery or should only create a disabled draft

Do not ask the agent to infer a production paging address or invent a threshold from a few sample rows.

  1. Call GET /tables to discover the canonical table name.
  2. Call GET /tables/<table>/schema and use only fields that actually exist with compatible types.
  3. Call GET /alert?page=1&pageSize=100 and look for the intended stable slug. If it exists, use PATCH /alert; do not create a duplicate.
  4. For a query alert, run the exact proposed SQL with POST /query. Confirm that it returns one timestamp column, one numeric metric column, the expected unit, and newest-first ordering.
  5. Create a new alert with enabled: false. Review the returned alert, query, threshold, point window, and recipients.
  6. Enable the reviewed alert with PATCH /alert. Enabling an alert can cause real email delivery after a state transition, so treat this as the side-effecting step.

There is no alert upsert endpoint. Repeating POST /alert with an existing slug returns 409 Conflict; a well-behaved agent lists first and patches the existing alert deliberately.

The discovery calls are:

curl "https://api.telemetry.sh/tables?page=1&pageSize=100" \
  -H "Authorization: $API_KEY"

curl https://api.telemetry.sh/tables/http_request_completed/schema \
  -H "Authorization: $API_KEY"

curl "https://api.telemetry.sh/alert?page=1&pageSize=100" \
  -H "Authorization: $API_KEY"

Prompt for a coding agent

Copy this prompt and replace the bracketed values:

Create a Telemetry alert for [operational condition] using https://api.telemetry.sh.

Use the API key already available as API_KEY. The expected event or table is
[table, or "unknown"]. Monitor [population] over [window and bucket size]. The
threshold is [value and unit], and the owner is [recipient]. Use the stable slug
[slug].

Before changing anything:
1. List tables and inspect the selected table's schema.
2. List existing alerts and look for the stable slug.
3. Build a single-series query and run the exact SQL through POST /query.
4. Show me the returned columns and representative rows, the proposed alert
   request, and how its bucket aggregation differs from its top-level aggregation.

Do not guess field names, units, thresholds, or recipients. Do not create a
duplicate or delete an alert. If the slug does not exist, create the alert with
enabled set to false. If it exists, propose a PATCH instead. Do not enable the
alert until I confirm the query, threshold, and recipient.

Choose both aggregations deliberately

Explorer alerts have two aggregation layers. explorerState.aggregation calculates the value inside each time bucket; the top-level aggregation reduces the recent bucket values selected by last_n_data_points. Query alerts calculate each row in SQL and use only the top-level aggregation across recent rows.

Operational goal Value per bucket Top-level condition
Sustained server error rate SQL calculates server_error_rate_pct Average the last three completed buckets and compare with 5 percent
Any latency spike Explorer calculates p95 duration_ms Maximum of the last five completed buckets exceeds 850 milliseconds
Missing heartbeat Explorer counts events and fills missing buckets with zero Sum of the last five completed buckets is less than 1 event

ignore_last_data_point: true is appropriate for these time-bucketed examples because the newest bucket can be incomplete. Set it to false for a query that returns only one fully calculated row; otherwise the evaluator drops that only row.

Example: sustained server error rate

This query calculates one error-rate percentage for each five-minute bucket and suppresses rate alerts below 100 requests per bucket. The alert then averages the three newest completed buckets and compares that average with 5 percent.

First run the exact SQL and inspect the result:

ALERT_QUERY=$(cat <<'SQL'
SELECT
  date_bin(
    INTERVAL '5 minutes',
    timestamp_utc,
    TIMESTAMP '1970-01-01'
  ) AS time_bucket,
  CASE
    WHEN COUNT(*) >= 100 THEN
      100.0 * SUM(CASE WHEN status_code >= 500 THEN 1 ELSE 0 END)
        / NULLIF(COUNT(*), 0)
    ELSE 0.0
  END AS server_error_rate_pct
FROM http_request_completed
WHERE
  timestamp_utc >= now() - INTERVAL '35 minutes'
  AND environment = 'production'
GROUP BY time_bucket
ORDER BY time_bucket DESC;
SQL
)

curl -X POST https://api.telemetry.sh/query \
  -H "Authorization: $API_KEY" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg query "$ALERT_QUERY" \
    '{query: $query, realtime: true, json: true}')"

After verifying that time_bucket is a timestamp and server_error_rate_pct is numeric, create a disabled draft:

curl -X POST https://api.telemetry.sh/alert \
  -H "Authorization: $API_KEY" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg query "$ALERT_QUERY" '{
    name: "Production API error rate",
    slug: "production-api-error-rate",
    description: "Investigate recent deploys and affected routes before escalating.",
    alert_type: "query",
    payload: {
      querySql: $query,
      timestampColumn: "time_bucket"
    },
    aggregation: "avg",
    metric: "server_error_rate_pct",
    last_n_data_points: 3,
    ignore_last_data_point: true,
    check_interval_minutes: 1,
    comparison: "greater_than",
    threshold: 5,
    recipients: [
      {type: "email", recipient: "[email protected]"}
    ],
    enabled: false
  }')"

Replace the example recipient with the reviewed owner before enabling the alert.

Example: p95 latency spike

This Explorer alert calculates p95 request duration inside each minute. The top-level max means one of the five most recent completed minutes must exceed 850 milliseconds.

curl -X POST https://api.telemetry.sh/alert \
  -H "Authorization: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production API p95 latency",
    "slug": "production-api-p95-latency",
    "description": "Inspect slow routes, dependencies, and the latest deploy.",
    "alert_type": "explorer",
    "payload": {
      "tableName": "http_request_completed",
      "explorerState": {
        "graphType": "line",
        "aggregation": "p95",
        "metric": "duration_ms",
        "timeZone": "UTC",
        "timePreset": "1h",
        "granularity": "minute",
        "splitBy": [],
        "filters": [
          {
            "logic": "AND",
            "conditions": [
              {
                "field": "environment",
                "operator": "=",
                "value": "production"
              }
            ]
          }
        ],
        "selectedColumns": ["duration_ms"],
        "orderBy": null,
        "orderDirection": "DESC",
        "limit": 200
      },
      "fields": [
        { "name": "duration_ms", "type": "Float64" },
        { "name": "environment", "type": "Utf8" }
      ]
    },
    "aggregation": "max",
    "metric": "duration_ms",
    "last_n_data_points": 5,
    "ignore_last_data_point": true,
    "check_interval_minutes": 1,
    "comparison": "greater_than",
    "threshold": 850,
    "recipients": [
      { "type": "email", "recipient": "[email protected]" }
    ],
    "enabled": false
  }'

The fields types must come from the schema response. They let the Explorer SQL generator serialize filter values correctly and identify numeric measures.

Example: missing heartbeat

Use an Explorer count when absence is the signal. Explorer time-series queries include zero-valued missing buckets, so this alert triggers when the sum across the last five completed one-minute buckets is less than one event.

curl -X POST https://api.telemetry.sh/alert \
  -H "Authorization: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production worker heartbeat missing",
    "slug": "production-worker-heartbeat-missing",
    "description": "Check the worker process, queue, and ingestion path.",
    "alert_type": "explorer",
    "payload": {
      "tableName": "worker_heartbeat",
      "explorerState": {
        "graphType": "line",
        "aggregation": "count",
        "metric": null,
        "timeZone": "UTC",
        "timePreset": "1h",
        "granularity": "minute",
        "splitBy": [],
        "filters": [
          {
            "logic": "AND",
            "conditions": [
              {
                "field": "environment",
                "operator": "=",
                "value": "production"
              }
            ]
          }
        ],
        "selectedColumns": [],
        "orderBy": null,
        "orderDirection": "DESC",
        "limit": 200
      },
      "fields": [
        { "name": "environment", "type": "Utf8" }
      ]
    },
    "aggregation": "sum",
    "metric": "count",
    "last_n_data_points": 5,
    "ignore_last_data_point": true,
    "check_interval_minutes": 1,
    "comparison": "less_than",
    "threshold": 1,
    "recipients": [
      { "type": "email", "recipient": "[email protected]" }
    ],
    "enabled": false
  }'

Do not use a query that only groups existing heartbeat events: if no events arrive, that query may return no row for the missing interval. The Explorer time-series form is useful here because it produces the zero-valued buckets the condition needs.

Example: review and enable a draft

List alerts again and inspect the saved record for the stable slug. Then enable only that alert:

curl "https://api.telemetry.sh/alert?page=1&pageSize=100" \
  -H "Authorization: $API_KEY"

curl -X PATCH https://api.telemetry.sh/alert \
  -H "Authorization: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "alertSlug": "production-api-error-rate",
    "enabled": true
  }'

If the stable slug already existed, use the same PATCH /alert shape to change only the reviewed fields. Keep enabled: false during query or recipient changes if notification delivery should remain paused.

Slug normalization

For create and edit requests, Telemetry trims and lowercases slug, removes characters other than ASCII letters, numbers, spaces, and -, converts whitespace to -, collapses repeated -, and trims leading or trailing -.

If create omits slug, the API normalizes name. For example, "API Errors!!!" becomes "api-errors". The normalized slug must contain at least one letter or number and must be unique within the team. A duplicate returns 409 Conflict.

Edit an alert

PATCH /alert is a partial update. Identify the alert with alertId or alertSlug; every other omitted field remains unchanged.

curl -X PATCH https://api.telemetry.sh/alert \
  -H "Authorization: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "alertSlug": "api-error-rate",
    "threshold": 0.08,
    "last_n_data_points": 5
  }'

When both identifiers are provided, they must resolve to the same alert. Send payload together with alert_type when changing between query and explorer.

Updating the query, condition, schedule, or recipients clears last_value and last_evaluated_at, returns status to inactive, and increments evaluation_version. Renaming, changing the description or slug, and toggling enabled do not clear evaluation state.

The response is 200 OK with the same { "status": "success", "alert": { ... } } shape as create.

Delete an alert

DELETE /alert accepts alertId, alertSlug, or both. Deleting an alert also deletes its evaluation history.

curl -X DELETE https://api.telemetry.sh/alert \
  -H "Authorization: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "alertSlug": "api-error-rate" }'

Successful response:

{
  "status": "success",
  "deleted_alert": {
    "id": "d7463946-8c6a-4a54-8a47-74cc98247c54",
    "name": "API error rate",
    "slug": "api-error-rate",
    "description": "Notify the API on-call rotation",
    "url": "/team/acme/alert/api-error-rate"
  }
}

Common errors

  • 400 Bad Request for invalid JSON, invalid alert fields, incompatible payloads, unsupported pagination, or a read-scoped key used for a mutation
  • 401 Unauthorized when the API key is missing or invalid
  • 404 Not Found when an alert identifier does not belong to the API key's team
  • 409 Conflict when the normalized slug already exists for the team
  • 429 Too Many Requests when the API key exceeds the gateway rate limit
  • 500 Internal Server Error when a valid persistence operation fails

Alert definitions can generate email. Use temporary recipients while testing, verify the condition with synthetic data, and disable or delete test alerts after validation. See Alerts for evaluation semantics and Alert delivery and troubleshooting for delivery guidance.

Related product capability

Promote reviewed SQL into an owned threshold and response workflow.

Ownership and technical references

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

Review the editorial standard