Better Stack helps engineering teams monitor application reliability and investigate production incidents. Manage uptime monitors, inspect logs and error traces, query telemetry, track incidents, configure on-call schedules, and analyze service health across applications and infrastructure.
Try asking
Encrypted at rest, isolated from the model
Resolved from an AES-256-GCM vault at the moment of the call and attached to the request — the model never sees the secrets.
Execute a ClickHouse SQL query to retrieve logs, traces/spans, errors, and metrics from telemetry data. Write the query as a template and the server builds the collections for you, as Better Stack's dashboards and AI SRE do: - `FROM {{source}}` reads the source's recent and historical rows of `source_type` together (for metrics, its metrics collection), scoped to the source - `{{start_time}}` and `{{end_time}}` are the `start_time` and `end_time` arguments, e.g. `WHERE dt >= {{start_time}} AND dt < {{end_time}}` - `{{time}}` buckets `dt` to an interval that suits the range (`toStartOfInterval(dt, INTERVAL '<n> second')`); `{{interval_seconds}}` is that interval A template needs `source_type`, `start_time` and `end_time`, and no `table`. A query without `{{...}}` runs exactly as written, needs `table` and must name its `remote(...)` and `s3Cluster(...)` collections itself. - **IMPORTANT**: Use `query_help` to get instructions on how to create the correct query for logs and spans - **IMPORTANT**: Use `errors_query_help` to get instructions on how to create the correct query for errors - **IMPORTANT**: Use `metrics_query_help` to get instructions on how to create the correct query for metrics - Before a query over more than a few hours of logs, spans or other events, call `query_windows` with the same arguments: it says whether the range fits one call and, if not, the time windows that do. A template of either shape below needs no such call: `query` splits its range itself - A template listing the newest rows (`ORDER BY dt DESC LIMIT n`, filtered by `dt >= {{start_time}} AND dt < {{end_time}}`, with no aggregate, `GROUP BY`, `DISTINCT` or subquery) whose range is too wide for one call is read one time window at a time, newest first, until it has its n rows; the result says how far it read and where to continue - A template that only adds up or combines (`count()`, `countIf()`, `sum()`, `sumIf()`, `min()`, `max()`, beside the `GROUP BY` columns it selects; `ORDER BY` and `LIMIT` on those), filtered by `dt >= {{start_time}} AND dt < {{end_time}}`, with no `HAVING`, `DISTINCT` or subquery, whose range is too wide for one call but holds at most 300,000,000 rows is run in time windows, several at once, and the windows' rows merged; the result says how far it read
Execute a ClickHouse SQL query and visualize the result as a chart. Use `chart_type` to choose the visualization: - `line` (default) — trends over time. Alias columns as `time`, `value`, and optional `series`. - `bar` — magnitude over time or across buckets. Uses the same columns as `line`; set `stacked: true` to stack the series. - `pie` — share of a total across categories. Alias a categorical column `AS series` (the slice label) and a numeric column `AS value` (the slice size), e.g. `SELECT service AS series, count() AS value FROM ... GROUP BY service`. Write the query as a template and the server builds the collections for you, as Better Stack's dashboards and AI SRE do: - `FROM {{source}}` reads the source's recent and historical rows of `source_type` together (for metrics, its metrics collection), scoped to the source - `{{start_time}}` and `{{end_time}}` are the `start_time` and `end_time` arguments, e.g. `WHERE dt >= {{start_time}} AND dt < {{end_time}}` - `{{time}}` buckets `dt` to an interval that suits the range (`toStartOfInterval(dt, INTERVAL '<n> second')`); `{{interval_seconds}}` is that interval A template needs `source_type`, `start_time` and `end_time`, and no `table`. A query without `{{...}}` runs exactly as written, needs `table` and must name its `remote(...)` and `s3Cluster(...)` collections itself. Alias result columns as (line/bar): - `... AS time` — the time bucket (e.g., `{{time}} AS time` in a template, or `toStartOfHour(dt) AS time`) - `... AS value` — the numeric metric (e.g., `count() AS value`) - `... AS series` — optional: a grouping column for multiple series (e.g., `service AS series`) **IMPORTANT (line/bar): Always choose a time granularity that produces 50–500 data points** (`{{time}}` picks 30 seconds to 2 hours from the range, which is in that band up to a day but gives more points on longer ranges: bucket by hand there) — too few points make the chart useless, too many make it unreadable. Use these rules based on the query's time range: - Up to 1 hour → `toStartOfMinute(dt) AS time` → ~60 points - 1–24 hours → `toStartOfInterval(dt, INTERVAL 5 MINUTE) AS time` → up to 288 points - 1–7 days → `toStartOfHour(dt) AS time` → up to 168 points - 7–30 days → `toStartOfInterval(dt, INTERVAL 4 HOUR) AS time` → up to 180 points - 30–90 days → `toStartOfInterval(dt, INTERVAL 12 HOUR) AS time` → up to 180 points - 90+ days → `toStartOfDay(dt) AS time` Additional query instructions (data access patterns) are in: - `query_help` — logs and spans - `errors_query_help` — errors - `metrics_query_help` — metrics Before a query over more than a few hours of logs, spans or other events, call `query_windows` with the same arguments: it says whether the range fits one call and, if not, the time windows that do.
Size an S3 query before running it: how many rows its time range holds and, when that is more than one `query` call reads (50,000,000 rows, 50 seconds), the time windows that fit one call each. Pass the same arguments you would pass to `query` (`query`, `source_id`, and `table` for SQL without template variables or `source_type`, `start_time` and `end_time` for a template). Nothing is read from S3; the answer comes from the source's row counts in well under a second. All timestamps are UTC; when `server_timezone` is not UTC, write each bound as toDateTime('<value>', 'UTC') rather than as a bare literal, which would be parsed in the server's timezone. Every answer with a `decision` carries `notes`, what it says to do, written for this query: how to pass and merge its windows, what to replace before splitting a template that uses `{{time}}`, why the range could not be sized. Returns `decision`: - `run` — the range fits one call; run the query as written - `windows` — Run the query once per window, newest first, with `dt >= '<from>' AND dt < '<to>'` in place of its time filter, and merge the results yourself: rows concatenate; `count()` and `sum()` add up; `min()` and `max()` combine; an average needs `sum()` and `count()` per window; `quantile(...)`, `uniq(...)`, `count(DISTINCT ...)` and `topK(...)` do not merge across windows, so keep those to one window or add filters. For a template whose time filter is `{{start_time}}`/`{{end_time}}`, pass each window as `start_time` = `from` and `end_time` = `to` instead. When `query` splits such a template itself (its description names the two shapes), `notes` says to call `query` once instead, and the windows are only a fallback. At most 200 windows are returned; `more_windows` counts the older ones the range still holds. More windows than you will run means the range needs rethinking: narrow the time filter, add filters on `raw` before extracting JSON, or set `sampling=` to `suggested_sampling` to estimate rather than measure. A window marked `oversized` holds more than the budget in a single second: add filters or `sampling=` for it - `unsized` — the query's shape cannot be sized; `explanation` says what to change With `run` or `windows`: - `hot_tail` (never for a `{{source}}` template or a query that reads the hot collection itself with `remote()`) — the newest rows of the range are still in the hot collection, which `s3Cluster` holds only once they leave it: `from` is the oldest row still there (Live tail's first row on disk), `estimated_rows` how many of the range's rows that is, `collection` the hot collection to read them from when the row type names one; the top-level `estimated_rows` and the windows then count S3 rows only. Run the same query against `remote(<collection>)` with the time filter unchanged and no `_row_type` filter, and merge it in as one more window, without cutting the S3 windows at `from`: S3 already holds rows past it. Without `from` the boundary could not be probed and the range merely reaches into the last hours - `hot_rows` (a `{{source}}` template, or a query that reads the hot collection itself, whose range is over the budget with its hot rows counted) — how many rows of the range are still in the hot collection: the query reads them too, but `estimated_rows` and the budget leave them out, since a hot read is usually fast. Without `hot_rows`, `estimated_rows` counts them. If the query still times out on them, the timeout lists windows that split them - `suggested_sampling` — with more windows than you will run, the `sampling=` rate at which the whole range fits one call
Get a single-use URL to upload a file (a log, a screenshot, a trace) to, and attach it with a tool that takes `attachment_upload_ids`, such as `create_incident_comment`. The answer is a `curl` command that uploads the file from a shell, so this needs one: Claude Code, or claude.ai's code execution with this MCP server's domain allowed. Run the command, then pass the upload id to the tool. One file of up to 10 MB per URL, 10 upload URLs and 10 uploads a minute. The URL expires after 15 minutes, the uploaded file after a day, and only you can attach it.
Acknowledge an ongoing incident
Create a comment on an incident. To attach files, upload each one with create_file_upload first and pass the upload_id values it returns in attachment_upload_ids
Rewrite a comment on an incident timeline and edit its copies in Slack, Microsoft Teams and Linear in place; no one is notified again. Only your own comments, or ones no team member wrote such as comments added with an API token, can be changed
Remove a comment from an incident timeline and its copies in Slack, Microsoft Teams and Linear; only your own comments, or ones no team member wrote such as comments added with an API token, can be removed
Create a new incident providing a summary of the issue, requester email, and other optional details. Notification channels (call, sms, email, critical_alert) all default to false, so an incident with none of them set reaches nobody unless an escalation policy handles it.
Escalate an ongoing incident to a user, team, on-call calendar, escalation policy, or the whole organization. A policy brings its own notification channels; every other target needs at least one of call, sms, email, push or critical_alert.
Get comments for an incident
Get detailed information about a specific incident
Get available escalation policies for an incident
Get the timeline of events for an incident
List incidents with filtering and pagination options
Reopen a resolved incident (must be within 24 hours of resolution)
Resolve an ongoing incident
Get detailed information about a specific escalation policy
Add a member to an escalation step of an escalation policy, for example an outgoing webhook (type webhook) so the step fires it, or a user, an on-call calendar or a Slack integration. With new_step, the member goes alone into a new escalation step at step_number instead, and the steps from there on move one later: a webhook in a new step 1 fires first, and next_step_wait_minutes gives it time before the people in the next step are notified. The policy keeps its other steps and members
Remove a member from an escalation step of an escalation policy, for example an outgoing webhook (type webhook). An escalation step needs a member, so removing its only one takes remove_empty_step, which removes the whole step and moves the later steps one earlier, and next_step_wait_minutes sets how long the step that takes its place waits: to undo a new step put in front of the people, pass 0 so they are not notified later than before. The policy keeps its other steps and members
Get detailed information about a specific severity (urgency level)
List all escalation policies with their steps and configuration
List all severities (urgency levels) with their notification settings
Create a new monitor that tracks the availability of a website, host, or service. Provide the `url` to monitor. For ping, TCP, UDP, SMTP, POP, IMAP, and DNS monitors this is the host (e.g. `example.com`) rather than a full URL. The monitor starts checking immediately unless `paused` is set to true.
Get availability (SLA) summary for a specific monitor
Get details of a specific monitor
Get response time metrics for a specific monitor
List monitors with optional filtering and pagination
Change a monitor, or pause or unpause it. Only the parameters you pass change: request_headers replace stored headers of the same name and keep the others, and remove_request_headers drops headers by name
Remove a monitor, which stops its checks and alerts, takes it off every status page it is on, and resolves its ongoing incident with the usual notifications (an incident still unconfirmed is removed instead)
Create a new heartbeat that expects a periodic request from a cron job, worker, or other background task, and alerts when that request stops arriving. Provide a `name` for the heartbeat. The heartbeat reports down once no request is received within `period` seconds plus the `grace` window. Use the returned heartbeat URL as the endpoint your job pings on every successful run.
Get availability summary for a specific heartbeat
Get details of a specific heartbeat
List all heartbeats with filtering and pagination options
Change a heartbeat, or pause or unpause it. Only the parameters you pass change
Remove a heartbeat, which stops it alerting, takes it off every status page it is on, and resolves its ongoing incident with the usual notifications (an incident still unconfirmed is removed instead)
Get detailed information about a specific on-call calendar
Get detailed information about a specific on-call event
Get on-call rotation configuration for a specific calendar
List all on-call schedule events for a specific calendar
List all on-call calendars for the team
Create a new status page report
Create a new status update for an existing status page report
Change a status page report's title or times; only the parameters you pass change. Subscribers are not e-mailed and its status, which comes from its updates, stays. An incident report's Slack and Microsoft Teams posts keep the old title until one of its updates is edited, and on an automatic report the next automatic update overwrites the new one. A maintenance's first update moves to its start and that update's posts are edited; extending a completed maintenance reopens it and removes its "Maintenance ended" update
Change an update on a status page report. Only the parameters you pass change. Its Slack and Microsoft Teams posts are edited in place, and the report's status is recalculated from its updates (an automatic report becomes a manual one). Subscribers who already received the update are not e-mailed again
Remove a status page report with all of its updates, so it disappears from the status page and its history, and delete their Slack and Microsoft Teams posts (e-mails already sent to subscribers stay)
Remove an update from a status page report and delete its Slack and Microsoft Teams posts; the report's status is recalculated from the updates left, and removing its last update removes the whole report
Get details of a specific status page
Get resources (monitors/heartbeats) for a specific status page
Get details of a specific status page report update
List status reports (incidents/maintenance) for a specific status page
List all status pages with filtering and pagination options
List status updates for a specific status report
List the sections (resource groups) of a status page
Update the settings of a status page (company name, contact URL, theme, layout, and more)
Add a resource (monitor, heartbeat, or group) to a status page. Provide the `resource_type` and `resource_id` of the thing to display, plus a `public_name` shown to visitors (usually the resource's own name). Use `status_page_sections` to find the section to place it in; when omitted the resource is added to the status page's first section.
Update a resource on a status page. Change its public name, description, widget type (e.g. show or hide the uptime history), or move it by setting `position` (zero-based) and/or `status_page_section_id`. Use `status_page_resources` to find resource IDs.
Remove a resource from a status page
Create a section (resource group) on a status page to group resources under a heading
Rename a status page section or move it by setting its position
Remove a section from a status page. Resources in the section are removed with it.
List outgoing webhooks: the requests Better Stack sends to a URL when incidents, on-call or monitors change
Get one outgoing webhook: its URL, events and request. Header values and basic authentication credentials are never shown
Create an outgoing webhook: Better Stack sends a request to the URL when incidents change (or on-call, or monitors). An incident webhook fires for incidents without an escalation policy when notify_alongside_primary_responder is on (the default), and for incidents with one only once it is added to a step of that policy in Better Stack. Incident events you do not pass are on. Set a custom request with http_method, headers, body_template and basic authentication. Header values and credentials are stored but never shown back
Change an outgoing webhook. Only the parameters you pass change: headers replace stored headers of the same name and keep the others, and remove_headers drops headers by name
Remove an outgoing webhook. It stops firing and is removed from every escalation policy step that uses it
List the Slack integrations (one per connected channel) with their channel and notification type
Get every setting of a Slack integration: notification type, channel invitees, who Slack users can alert, and the notification options
Change the settings of a Slack integration, as on Integrations › Slack › Configure. Only the parameters you pass change. channel_invitees, alert_options and status_page_update_resources replace the whole list
List the catalog's relations with their primary and added attributes and how many records each holds. ## How the catalog works The catalog adds business context to incidents through lookup tables. - A **relation** is a table. Its **primary attributes** are the key columns, its **added attributes** are the columns it adds, and each **record** is a row. - A primary attribute is named after a metadata key incidents already carry, set by a monitor, an integration, the reporter or another relation. When an incident's value for that key equals a record's primary value exactly, the record's added values are copied onto the incident as metadata, where escalation policies can act on them. - With several primary attributes, the relation's matching decides: `any` matches a record when one of its primary values matches, `all` only when every primary attribute does, an empty primary cell matching anything there. A record with every primary cell empty is the relation's fallback, used only when no other record matches. - A value is text, or a Better Stack object: `Team`, `User` (a team member), `Policy` (an escalation policy), `Schedule` (an on-call calendar), `Endpoint` (a monitor) and more. Reference an object by `name` (a team member by `email`) or by the `item_id` that `catalog_relation` shows, a monitor by `item_id` only; the team IDs other tools take are a different number. - An added attribute named like another relation's primary attribute chains the lookup: the value it adds is looked up in that relation next. - Three added attributes draw the Dependencies map on Integrations → Catalog: - `Dependencies`: the records this one needs, named by their primary value. An incident on any of them marks this record as affected too. - `Connections`: the records this one talks to, drawn without passing incidents on. - `Type`: Database, Storage, Frontend, Backend, Mobile app or Desktop app, which sets the record's icon. Read one relation's records with `catalog_relation`.
Show one catalog relation with its records, one column per attribute, paginated. Objects read as `Platform (Team 123)`, so a record can name them again by `name` or `item_id`. See `catalog_relations` for how the catalog works.
Create a catalog relation with its primary and added attributes in one call. Add records with `upsert_catalog_records` afterwards. See `catalog_relations` for how the catalog works. Propose the relation to the user first (its name, matching, attributes and a few example records) and create it once they agree. For a relation describing services, add `Dependencies`, `Connections` and `Type` as added attributes, so the Dependencies map can draw its records. ## Rules - Name primary attributes after metadata keys incidents really carry. Check recent incidents' metadata with `incidents` and `incident` first: a key no incident carries never matches. - Reference teams, team members, escalation policies and other Better Stack objects as objects, like `{"type": "Team", "name": "Platform"}`, never as their names in text. - Never invent records. Every row comes from a source the user pointed you to (code, Terraform, Kubernetes, a Backstage catalog, the service map) or from the user. - Prefer `upsert_catalog_records` to removing records and adding them again: it keeps every record it doesn't name, while removing them all empties the relation.
Change a catalog relation: rename it, change its description or matching, add attributes or remove them. Only what you pass changes. Removing an attribute removes its values from every record. See `catalog_relations` for how the catalog works. ## Rules - Name primary attributes after metadata keys incidents really carry. Check recent incidents' metadata with `incidents` and `incident` first: a key no incident carries never matches. - Reference teams, team members, escalation policies and other Better Stack objects as objects, like `{"type": "Team", "name": "Platform"}`, never as their names in text. - Never invent records. Every row comes from a source the user pointed you to (code, Terraform, Kubernetes, a Backstage catalog, the service map) or from the user. - Prefer `upsert_catalog_records` to removing records and adding them again: it keeps every record it doesn't name, while removing them all empties the relation.
Remove a catalog relation with all of its attributes and records, so new incidents stop picking up metadata from it (metadata already on incidents stays)
Add or update a catalog relation's records, keyed by their primary values, so running the same sync again updates instead of duplicating. A record whose primary values equal a row's is updated: the added attributes the row names are replaced, the others are left alone. A row no record matches becomes a new record. One invalid row fails the whole call and writes nothing. Up to 100 records per call. See `catalog_relations` for how the catalog works. Name `Dependencies` and `Connections` by the other records' primary values. ## Rules - Name primary attributes after metadata keys incidents really carry. Check recent incidents' metadata with `incidents` and `incident` first: a key no incident carries never matches. - Reference teams, team members, escalation policies and other Better Stack objects as objects, like `{"type": "Team", "name": "Platform"}`, never as their names in text. - Never invent records. Every row comes from a source the user pointed you to (code, Terraform, Kubernetes, a Backstage catalog, the service map) or from the user. - Prefer `upsert_catalog_records` to removing records and adding them again: it keeps every record it doesn't name, while removing them all empties the relation.
Remove catalog records by their primary values, or every record of a relation with `all: true`. A record is removed when its primary values equal a given row's, as `upsert_catalog_records` matches them; a row matching several records removes none of them. See `catalog_relations` for how the catalog works. ## Rules - Name primary attributes after metadata keys incidents really carry. Check recent incidents' metadata with `incidents` and `incident` first: a key no incident carries never matches. - Reference teams, team members, escalation policies and other Better Stack objects as objects, like `{"type": "Team", "name": "Platform"}`, never as their names in text. - Never invent records. Every row comes from a source the user pointed you to (code, Terraform, Kubernetes, a Backstage catalog, the service map) or from the user. - Prefer `upsert_catalog_records` to removing records and adding them again: it keeps every record it doesn't name, while removing them all empties the relation.
Add a new chart to a dashboard. Use the `section` parameter to organize charts into named sections — sections are auto-created if they don't exist, and charts are auto-positioned within them. **REQUIRED**: the `query`'s FROM must reference a source, either `{{source}}` or a source-type variable defined via `set_dashboard_variable` (e.g. `{{redis_source}}`). A query with no source is rejected with guidance to define it first. Dashboard chart queries run against the metrics collection: use `sum(logs_count)` (or the matching `sum(<row_type>_count)` for the source), `avgMerge(value_avg)`, `label('tag')`. Do NOT use `JSONExtract(raw, …)` — that is for live-tail charts and `query` only; dashboards have no `raw` column. Call `metrics_query_help` with the source ID **and `context: 'chart_query'`** for available metrics/labels and chart-query-style examples. **Workflow:** 1. Call `chart_building_help` for chart types, units, and settings. 2. Call `metrics_query_help` with `context: 'chart_query'` to see the source's metrics schema and chart-query examples. 3. Verify the query with `render_chart` (runs it and surfaces errors automatically) or with `query`. Use `{{source}}`, `{{start_time}}`, `{{end_time}}`, `{{time}}` variables for integration with dashboard filters. Queries can also reference custom dashboard variables — `{{var}}` (required) or `[[ AND col = {{var}} ]]` (optional clause, dropped while the variable is empty); define them with `set_dashboard_variable`.
Add a section divider to a dashboard. Sections span the full width and help organize charts into groups. Charts and sections at or below the insertion point are shifted down to make room
Create a new application in Better Stack. Returns the created application details including ID, ingestion token, ingesting host URL, retention settings, and platform-specific integration documentation links with next steps for configuration
Create a new chart alert on an existing chart. The chart must support alerts (line_chart, bar_chart, or tail_chart with time variables; Number charts take no new alerts). Call chart_alert_help for configuration reference. Use chart or dashboard first to find the chart ID
Create a secure cloud connection for direct ClickHouse query access to logs, spans, and metrics data. Returns connection credentials (host, port, username, password), sample queries for each data type, and cURL command examples. Connections expire after 1 hour by default
Create a Better Stack Collector, the agent that collects logs, metrics and traces from a Docker host, a Docker Swarm or a Kubernetes cluster. Returns the collector details, the collector secret needed to install it, the ID of the source its data goes to, and the installation guide for its platform
Create a new dashboard. Optionally use a template to start with pre-configured charts. Call chart_building_help for guidance on dashboard structure and layout. Optionally specify a source_id to preconfigure the dashboard with that source. Returns the new dashboard ID which can be used with add_chart_to_dashboard to add charts
Post a comment on an error so the findings land in the conversation the team reads in Better Stack. The comment is written as the user whose token made the call and marked as posted via MCP, so a team member can tell it apart from one typed in the app; a call made with an API token is attributed to the API instead. When the error is linked to a Linear issue whose integration syncs comments, the comment is posted on that issue too. Use error_comments to read the conversation back.
Create an issue for an error in Linear, or a work item in Jira, through the team's integration and link the two. The issue gets Better Stack's standard body (error type, message, call site, stack trace, occurrence counts and a link back) and the integration's configured defaults for team or space, work type, assignee, labels and priority. Better Stack then posts every change to the error state as a comment on the issue. An error that already has an issue through the integration is reported, not duplicated; use link_error_issue to attach an issue that already exists.
Create a new metric expression (extract-metrics-from-logs rule) on a source. `sql_expression` runs against each log row; log fields live inside the `raw` JSON column — use `JSONExtract(raw, 'path', 'Nullable(Type)')`. The `Nullable(...)` wrapper is required. Nested paths use positional args: `JSONExtract(raw, 'request', 'headers', 'user-agent', 'Nullable(String)')`. Call `source_fields` to see what fields exist. Pass `aggregations` (e.g. `["avg", "count"]`) for an aggregated metric, or omit / pass `[]` for a label (group-by column). Prefer `build_type: new_data` (default). `historical_logs` re-runs the rule over every stored log — expensive; only when the user explicitly asks.
Register a release for an errors application, so it appears the moment it deploys instead of when its first error arrives. Returns the release ID, usable with remove_release
Create a new log source in Better Stack. Returns the created source details including ID, ingestion token, ingesting host URL, retention settings, and platform-specific integration documentation links with next steps for configuration. To set up a Better Stack Collector on Docker, Docker Swarm or Kubernetes, use create_collector instead
Delete a chart alert permanently. This will also clean up any associated incidents and anomaly models. This action cannot be undone. Use chart_alerts or chart_alert first to find the alert ID
Delete a metric expression from a source. This action cannot be undone. Call `metric_expressions` first to get the ID. `build_type: new_data` (default) stops the rule from applying to future logs but leaves already-extracted data. `build_type: historical_logs` also rebuilds the source's metrics without this rule — expensive.
Edit an existing application in Better Stack - rename it, pause or resume ingesting, or set its VRL transformations, including the exception grouping program. Only provide the fields you want to change. Use applications or application first to find the application ID.
Edit an existing chart alert configuration. Only provide the fields you want to change. Call chart_alert_help for configuration reference. Use chart_alerts or chart_alert first to find the alert ID
Edit an existing chart name, query, type, or settings. Only provide the fields you want to change. **If changing the query:** the new query's FROM must reference a source, either `{{source}}` or a source-type variable defined via `set_dashboard_variable` (queries with no source are rejected). Dashboard chart queries run against the metrics collection: use `sum(logs_count)` (or the matching `sum(<row_type>_count)` for the source), `avgMerge(value_avg)`, `label('tag')`. `JSONExtract(raw, …)` only works in live-tail charts — dashboards have no `raw` column. Verify the new query with `render_chart` (runs it and surfaces errors automatically) or with `query` before saving. Queries can also reference custom dashboard variables — `{{var}}` (required) or `[[ AND col = {{var}} ]]` (optional clause, dropped while the variable is empty); define them with `set_dashboard_variable`. Call `chart_building_help` for chart/settings reference and `metrics_query_help` with `context: 'chart_query'` for the source's metrics schema and chart-query-style examples. Use `dashboard` first to find the chart ID.
Edit an existing dashboard section. Only provide the fields you want to change. Use dashboard first to find the section ID
Edit an existing dashboard's name or source eligibility. Only provide the fields you want to change. Use dashboard first to find the dashboard ID.
Export a dashboard configuration as JSON. Returns the complete dashboard data structure including charts, sections, presets, and settings
Link an error to an issue or work item that already exists in Linear or Jira. Better Stack then posts every change to the error state as a comment on the issue. For Linear, resolving the issue also resolves the error when auto-resolve is turned on for that integration; Jira has no status sync back. Create the issue in Linear or Jira first, then pass its identifier, key, or URL here.
Get comprehensive details of a specific application including its configuration, VRL transformations (the exception grouping program among them), retention settings, ingestion details, custom bucket settings (if configured)
Get detailed information about a specific chart alert including its configuration, SQL queries, status, and current incident info. Use chart_alerts first to find the alert ID
Get instructions for creating and configuring chart alerts, including alert types, operators, configuration fields, supported chart types, and common mistakes. Call this before creating or editing chart alerts
Get comprehensive instructions for building charts and dashboards, including chart types, units, axis settings, column mapping, legend placement, layout tips, and common mistakes. Call this before creating or editing charts
Get detailed information about a specific chart including its SQL queries, configuration, and settings. Use dashboard first to find the chart ID
Get detailed information about a specific dashboard including its charts, sections, layout, template variables, and configuration. Use this to understand a dashboard structure before modifying it
Get instructions for writing a ClickHouse query to use inside a Better Stack **Dashboard** chart (or chart alert). The query uses template variables (`{{source}}`, `{{time}}`, `{{start_time}}`, `{{end_time}}`) and runs against the source's metrics collection — it is meant to be saved as a dashboard chart via `add_chart_to_dashboard` / `edit_chart`, NOT run directly. To instead write an ad-hoc query you will run directly via `query` or `render_chart`, use `metrics_query_help`.
Get detailed information about a specific exploration (saved Explore query) including its sources, chart type, template variables, saved time range, and query. Use this to understand an exploration before modifying its source or variables
Get comprehensive details of a specific error including its type, message, call site information, first occurrence, current state (unhandled, unresolved, ignored, resolved, or reoccurred), and linked Linear/Jira issues
Get comprehensive instructions for building SQL ClickHouse queries for error tracking, including both error patterns (metrics) and individual exceptions. Explains when to use each source and provides examples for common use cases
Get instructions for writing a ClickHouse query to use inside the Better Stack **Explore logs** page (and live-tail charts) for log and span data. The query uses template variables (`{{source}}`, `{{time}}`, `{{start_time}}`, `{{end_time}}`) and reads fields from the `raw` JSON column — it is meant to be used in the Explore UI, NOT run directly. To instead write an ad-hoc logs/spans query you will run directly via `query`, use `query_help`.
Get comprehensive details about a specific metric. Returns metric overview (data points, active series, available aggregations), definition (SQL expression or JSON path), example queries for different aggregation functions, and Prometheus tags (for pure metrics). Essential for understanding how to query and use a metric
Get instructions for building SQL ClickHouse queries for metrics (available metrics, aggregations, examples) to run directly via the query tools (`query` / `render_chart`), written as templates (`FROM {{source}}`, `{{start_time}}`, `{{end_time}}`, `{{time}}`) that the tools expand from their `source_type`, `start_time` and `end_time` arguments. To instead write a query for use inside a Dashboard chart, use `dashboard_query_help`. Pass `context: 'chart_query'` when the query will be saved as a dashboard chart or chart alert — the prompt will emphasize `{{source}}` / `{{time}}` / `{{start_time}}` / `{{end_time}}` template variables and chart column aliases. (`context: 'direct_query'` is the default, for ad-hoc queries.)
Get metrics and cardinality for a source. Returns a paginated table of available metrics (user-defined and ingested) ordered by active series (highest cardinality first), with their names, types, storage layout, data points count, and active series count. Use `name_filter` to search by name: `rabbitmq*`, `*queue*`, `*_total`, or an exact name. When more than 250 metrics match and grouping them by name prefix would shrink the list at least 3x, a prefix summary is returned instead of the list; drill in with the `name_filter` it suggests. `page` applies to whichever table is shown
Get instructions for building SQL ClickHouse queries for logs and spans (fields, aggregations, examples) to run directly via the query tools (query / render_chart) against the ClickHouse proxy. To instead write a query for use inside the Explore logs UI, use explore_logs_query_help.
Get comprehensive instructions for building SQL ClickHouse queries for session replays. Explains data structure, provides examples for listing replays, finding replays linked to errors, and filtering by user/environment
Get comprehensive details of a specific source including its configuration, VRL transformations, retention settings, ingestion details, custom bucket settings (if configured)
Get complete field catalog for a logs or spans source. Returns a table of all queryable fields with their paths and data types. Essential for understanding what fields can be queried for building custom queries
Import a dashboard from JSON configuration. Creates a new dashboard with the provided data structure. Pass exactly one of `data` or `url`. Writing a large JSON out in `data` is slow and can exceed your output limit, so when the JSON is over about 50 KB, put it at a URL Better Stack can download without credentials (for example a pre-signed S3 link) and pass `url` instead. Each chart's `settings` follow chart_building_help. A chart with `unit: "percent"` takes a 0-1 ratio and multiplies it by 100 (0.75 renders as 75%), so never multiply by 100 in its query; divide a 0-100 value by 100 instead. The threshold `value`s of its `chart_alerts` are 0-1 too (0.85 for 85%); a `relative` alert's `value` stays a percentage change (50 for 50%). A `stacking: "stack_to_100"` bar chart is the exception: its query and thresholds stay raw values
List all available applications in a paginated table format. Returns application ID, name, platform type, team, status (active/paused), data region, and creation date
List chart alerts with optional filtering by team, chart, or dashboard. Returns alert ID, name, type, chart, dashboard, and status
List all available storage clusters for a specific team. Returns a table with cluster IDs, names, and regions. Used primarily for creating cloud connections to query logs and metrics data directly via ClickHouse
List the dashboard groups dashboards are organized into, with how many dashboards each one holds. Use this to find the exact name of an existing group before calling create_dashboard with resource_group_name, which creates a new group whenever the name does not match one
List all available dashboard templates in a paginated table format. Returns template ID, name, description, and other metadata
List all available dashboards in a paginated table format. Returns dashboard ID, name, creation date, and last updated date. Pass resource_group_id to list only the dashboards in one dashboard group
List saved explorations (saved Explore queries) in a paginated table. Returns exploration ID, name, chart type, sources, group, and last updated date. Use this to discover explorations before viewing or modifying one
List all available data regions and clusters for application and source creation. Returns a table with region IDs (to use when creating applications or sources), display names, types (Region or Cluster), and geographical locations. Includes usage instructions for both standard regions and custom clusters
Read the conversation on an error: comments left in the app, comments posted by an agent over MCP, replies written in the error Slack thread, and comments synced in from a linked Linear issue. Every entry names its author and, when it did not come from the app, how it was posted; a comment left on one occurrence of the error names that event.
List error patterns for an application with occurrence counts, affected users, current state, and links. Defaults to unresolved errors and supports filtering by state. For specialized error analytics or custom SQL, use errors_query_help instead.
List the metric expressions (extract-metrics-from-logs rules) on a source. Returns the rule ID, name, kind (metric vs label), ClickHouse type, SQL expression, and aggregations. IDs use a short prefixed form that feeds straight into update_metric_expression / delete_metric_expression
List all releases for a specific application in a paginated table format. Returns release ID (usable with remove_release), reference, environments, origin, first seen, and last seen timestamps
List all available sources in a paginated table format. Returns source ID, name, platform type, team, status (active/paused), data region, and creation date
List all available teams in Better Stack Logs. Returns a table with team IDs and names, grouped by organization
Move one or more charts to new positions on a dashboard. Validates the final layout for overlaps, allowing swaps and complex rearrangements. All moves are applied atomically - if any move is invalid, none are applied. Grid is 12 columns wide
Remove a chart from its dashboard permanently. This action cannot be undone and will also remove any alerts associated with the chart. Use dashboard first to find the chart ID
Remove a section divider from a dashboard permanently. This action cannot be undone. Charts are not affected - only the section header is removed. Use dashboard first to find the section ID
Remove a dashboard permanently. This action cannot be undone
Remove a dashboard template variable by name. Cannot remove the automatic variables source, start_time, end_time, or time. A chart still referencing a removed variable as a required {{name}} errors until it is redefined (the next chart save auto-creates it again, empty). Use dashboard first to see the defined variables.
Remove a variable from an exploration (saved Explore query) by name. Cannot remove the automatic variables source, start_time, end_time, or time. A query still referencing a removed variable as a required {{name}} errors until it is redefined. Use exploration first to see the defined variables.
Remove a release permanently. A release still receiving error events is re-detected automatically when its next event arrives. Use the releases tool to find release IDs
Create or update a dashboard template variable — a user-facing filter in the dashboard toolbar, referenced in chart SQL as `{{name}}` (required — the chart errors until it resolves to a value) or `[[ AND col = {{name}} ]]` (optional — the whole `[[ ... ]]` clause is dropped while the variable is empty). Saving a chart auto-creates a plain text variable (or a source variable when the name contains 'source') for any undefined required `{{name}}`, but it starts empty — use this tool to give it a typed definition or a default value so the chart resolves without manual input. Fields per type: - `select_value` (shown as "Static list") → the options in `default_values` (first = default selection) - `select_with_sql` (shown as "Dynamic list") → an option-yielding **expression** (NOT a full query) in `sql_definition`, e.g. `label('_host')`; see the `sql_definition` parameter for how it is interpolated - Both list types accept `allow_multiple_values: true` to let users pick several options at once (interpolates as a regex used with `match()`). `multi_select_with_sql` is the legacy always-multiple Dynamic list; prefer `select_with_sql` with `allow_multiple_values: true`. - `select_predefined_sql` (shown as "Query per value") → named SQL filters in `filters` (each a **full query**), default picked via `selected_label` - `string` / `number` / `boolean` → `[default]` in `default_values` - `sql_expression` → `[raw SQL]` in `default_values`, spliced into chart SQL unescaped - `source` → the source IDs in `source_ids`. Name it `source` to set the dashboard's own source (which sources every chart queries by default), or use a custom name for an extra source variable that a chart references in its FROM (e.g. `FROM {{name}}`) to query a different set of sources than the dashboard default. - `date` / `datetime` → an ISO date in `default_values`, e.g. `['2026-07-23']` or `['2026-07-23T14:30:00Z']` SQL-backed types are validated at save time by running their SQL against the dashboard's source — the save is refused when it fails. The names `start_time`, `end_time`, and `time` are reserved (they come from the time picker). `source` is the exception: set it as a source-type variable to change the dashboard's source. Use `dashboard` to list a dashboard's variables and `dashboard_query_help` for how each type is referenced in chart SQL.
Create or update a variable on an exploration (saved Explore query), referenced in its query as `{{name}}`. The most common use is setting the exploration's source: name it `source`, type `source`, and pass the source IDs in `source_ids` — the query then runs against those sources. Fields per type: - `source` → the source IDs in `source_ids`. Name it `source` to set the exploration's own source, or use a custom name for an extra source variable the query references in its FROM (e.g. `FROM {{name}}`). - `select_value` → the options in `default_values` (first = default selection) - `select_with_sql` / `multi_select_with_sql` → an option-yielding expression (NOT a full query) in `sql_definition`, e.g. `label('_host')` - `select_predefined_sql` → named SQL filters in `filters` (each a full query), default picked via `selected_label` - `string` / `number` / `boolean` → `[default]` in `default_values` - `sql_expression` → `[raw SQL]` in `default_values`, spliced into the query unescaped - `date` / `datetime` → an ISO date in `default_values`, e.g. `['2026-07-23']` or `['2026-07-23T14:30:00Z']` SQL-backed types are validated at save time by running their SQL against the exploration's source — the save is refused when it fails. The names `start_time`, `end_time`, and `time` are reserved (they come from the time picker). `source` is the exception: set it as a source-type variable to change the exploration's source. Use `exploration` to list an exploration's variables.
Pause or unpause a chart alert. When paused, the alert will not trigger any incidents
Remove the link between an error and an issue in Linear, Jira, or GitLab. Better Stack stops syncing the two; the issue itself is left untouched.
Update the state of a specific error (mark as resolved, ignored, or unresolved)
Update an existing metric expression. Call `metric_expressions` first to get the ID. At least one of `name`, `sql_expression`, `type`, `aggregations` must be provided — `build_type` alone is not a change and will be rejected. See `create_metric_expression` for `sql_expression` / `type` / `aggregations` guidance. Prefer `build_type: new_data` (default). `historical_logs` re-runs the rule over every stored log — expensive; only when the user explicitly asks.
Search for relevant documentation articles and return their contents
List the members of a Better Stack team, including pending invitations. Returns each member's email, name, role and the mobile app platforms they have signed in on. Supports the same email filter and pagination as the REST team-members API. If the token can reach more than one team, pass `team_id` (or `team_name`) to pick one; call it without a team first to see the list of accessible teams. Useful for auditing who has access — e.g. offboarding.
List the roles defined in a Better Stack organization, including their role_id and system-role identifier (admin, billing_admin, team_lead, responder, member, or "custom"). Use this to discover valid role_id values for interpreting team member roles. If the token can reach more than one team/organization, pass `team_id` (or `team_name`) to choose one.
Invite someone to a Better Stack team by e-mail address. Optionally set their role by system-role name (`role`: responder, member, team_lead, billing_admin) or by `role_id` (use team_roles to look up ids). Defaults to responder. The admin role cannot be assigned via the API. Someone who already belongs to the organization is added to the team directly, with no invitation to accept; the Admin role applies to every team, so an existing Admin keeps it. If the token can reach more than one team, pass `team_id` (or `team_name`).
Change a team member's role. Identify the member by `email` or `user_id` (from team_members) and pass the target `role_id` (from team_roles). The admin role cannot be assigned, and an existing admin's role cannot be changed, via the API. Pending invitations can't have their role changed — cancel and re-invite instead. With an OAuth token you must be on the team and your role there must be at or above the member's current role, as on the team members page. If the token can reach more than one team, pass `team_id` (or `team_name`).
Remove a member from a Better Stack team, or cancel a pending invitation. Identify them by `email` or `user_id` (from team_members). Admins cannot be removed via the API, and the organization's last member cannot be removed. If the token can reach more than one team, pass `team_id` (or `team_name`). With an OAuth token you must be on the team and your role there must be at or above theirs, as on the team members page. This permanently removes the person's access to the team.
One endpoint, the same key, whichever client you use.
~/Library/Application Support/Claude/claude_desktop_config.json (Mac) · %APPDATA%\Claude\claude_desktop_config.json (Windows)
Replace API_KEY with your own key.
Already have an "mcpServers" section in your config? Just add the server entry inside it.
Discovery, routing, credentials, tool scoping and execution logs all happen at the gateway→connections stay ACTIVE with no work from you
Better Stack MCP runs through a gateway that holds the credentials, scopes the access and records every call.
Managed auth, hosted MCP servers, and every Better Stack MCP tool your agent needs.
Free to start.
Stand up a staged Attio pipeline with custom attributes and bulk load 200 seed contacts in a single run.