Early access is open. Free plan during early access. Start free
Logthread

Guide

Connect AI agents to Logthread (MCP)

Logthread runs a Model Context Protocol (MCP) server. Connect an AI agent such as Claude Code, Codex, Cursor or VS Code and it can search your logs, follow one request across services, spot new error patterns and read your traces, metrics and alerts, all from your own workspace data. With a setup scope it can also set Logthread up for you: alert rules and channels, dashboards and saved searches, pipelines, collector configs, ingest keys and LiveKit, each checked on your real data first.

  • Endpoint: https://logthread.com/mcp (MCP Streamable HTTP, stateless, JSON responses)
  • Authentication: sign in with OAuth (Claude.ai, Claude Desktop and ChatGPT connectors: paste the URL, nothing else), or a Logthread API key (ltk_...) sent as Authorization: Bearer ltk_... for every other client
  • Access: read-only by default. With a plain read key, nothing an agent does can change your data or settings. Writes need a scope: annotate (chart annotations), alerts, dashboards, pipelines, fleet or sources (setup tools). Deleting needs the same scope as creating, and every delete or revoke tool first says what would go and changes nothing until it is called again with confirm: true; each one is in the audit log.
  • Protocol versions: 2025-11-25, 2025-06-18 and 2025-03-26

What you can do#

Ask in plain language. The agent picks the tools.

  • "Why did checkout start failing at 14:00?"
  • "Which services logged the most errors in the last hour, and what are the top error messages?"
  • "Show me the full story for request id req_8f2a across services."
  • "A customer sent request id 8f2a-41 and says checkout failed. What happened?"
  • "What new log patterns appeared since yesterday?"
  • "Are any alerts firing right now? What do the logs say about them?"
  • "Find the slowest traces for the payments service and where the time went."
  • "What does checkout depend on, and do the failing services share a dependency?"
  • "Alert us when gateway 5xx is above 2% for 10 minutes and email on-call." (alerts scope)
  • "Build a checkout dashboard with error rate, p95 latency and the top errors." (dashboards scope)
  • "Mask secrets in every log, then parse the nginx access logs." (pipelines scope)
  • "What is still half set up in my workspace?"

Quick start (1 minute)#

  1. Create a key. In the app open Keys → API keys (owners and admins) and choose Create API key. Name it after the person or agent, pick Read (add Read + SQL only if the agent needs custom SQL), and copy the key. It starts with ltk_ and is shown once.
  2. Put it in an environment variable in the shell your agent starts from:
export LOGTHREAD_API_KEY="ltk_..."
  1. Add the server to your client with the snippet for it below. Claude Code, for example:
claude mcp add --scope project --transport http logthread https://logthread.com/mcp \
  --header 'Authorization: Bearer ${LOGTHREAD_API_KEY}'
  1. Try a prompt: "List my Logthread services and tell me which one logged the most errors in the last hour."

API keys are separate from ingest keys: an ingest key (lt_...) can only send data; an API key reads, and with a setup scope sets things up.

Connect a client#

Claude.ai, Claude Desktop and ChatGPT (sign in, no key)#

Hosted connectors sign in with OAuth, so there is no key to copy. You need to be an owner or admin of the workspace, as for creating an API key.

  1. Add a custom connector with the URL https://logthread.com/mcp. There is no client ID, secret or header to enter: the app registers itself.
  2. Claude (Pro and Max, web and Desktop): Settings → Connectors → Add custom connector. Name it Logthread, paste the URL, leave Advanced settings (OAuth client ID and secret) empty, choose Add, then Connect.
  3. Claude (Team and Enterprise): an organisation owner first adds it under Organization settings → Connectors → Add → Custom → Web, with the name, the URL and no OAuth client or headers. Each person then connects it for themselves under Customize → Connectors → Logthread → Connect, so every person signs in to Logthread as themselves.
  4. ChatGPT (Plus, Pro, Business, Enterprise and Edu, on the web): turn on developer mode under Settings → Apps → Advanced settings → Developer mode. On Business, Enterprise and Edu a workspace admin must allow it first under Workspace settings → Permissions & roles (custom MCP connectors). Then Settings → Apps → Create: name Logthread, MCP server URL the URL above, Authentication OAuth, tick that you trust the app, and choose Create.
  5. The app opens Logthread. Sign in if you are not signed in already.
  6. Logthread asks you to confirm:
  7. which app is asking and where it returns to (its address, such as claude.ai; an address on your own computer gets a warning, because any program there could use that name);
  8. which workspace it connects to (your current one; to connect another, switch to it in Logthread first);
  9. what it may do, worded as on the API keys page. Read is always on. Every setup permission the app asked for (SQL, annotations, alerts, dashboards, pipelines, fleet, sources) is ticked; untick what you do not want.
  10. Choose Allow. Back in the app, the connector lists Logthread's tools.
  11. Use it in a chat. In Claude, turn Logthread on from the + (tools) menu of the chat. In ChatGPT, choose Developer mode from the + menu and turn on Logthread. Check it works by asking "Which services sent logs to Logthread in the last hour?": the app calls list_services and answers with your services.

Menu names in Claude and ChatGPT change from time to time. What stays the same: the URL https://logthread.com/mcp, OAuth sign-in, and no client ID or key. ChatGPT asks for the offline_access scope to keep the connection signed in, and Logthread accepts it; connections are renewed automatically either way.

The connection acts as you and never goes beyond your role. It keeps working while you stay an owner or admin of the workspace; if you are removed or your role changes, it stops within a minute.

Disconnect an app under Keys → API keys → Connected apps (owners and admins), or remove the connector in the app. Disconnecting takes effect immediately on the server that handles it and within 60 seconds everywhere.

Any other client that supports MCP OAuth (the 2025-06-18 or 2025-11-25 authorization spec, with dynamic client registration and PKCE) connects the same way with just the URL.

Clients with an API key#

Every snippet below uses LOGTHREAD_API_KEY. Where a client cannot read an environment variable, the snippet says so and the config file holds the key instead: keep that file out of version control. Restart the client after editing its config.

ClientRemote header supportKey from environment
Claude Codeyesyes
Codexyesyes
Cursoryesyes
Windsurfyesyes
Google Antigravityyesno, file holds the key
OpenCodeyesyes
VS Code (GitHub Copilot)yesprompted, kept in secret storage
Gemini CLIyesyes
Zedyesno, file holds the key
Clineyesno, file holds the key
Roo Codeyesno, file holds the key
Ampyesyes
Kiroyesyes
Claude Desktopsign in (above), or a bridgeno key, or file holds the key
Other clients

Claude Code#

Run in your project directory. This writes .mcp.json, which you can commit because it holds only ${LOGTHREAD_API_KEY}:

claude mcp add --scope project --transport http logthread https://logthread.com/mcp \
  --header 'Authorization: Bearer ${LOGTHREAD_API_KEY}'

Keep the single quotes. Claude Code expands the variable when it starts. Or, for every project on your machine (this stores the key itself in ~/.claude.json):

claude mcp add --scope user --transport http logthread https://logthread.com/mcp \
  --header "Authorization: Bearer $LOGTHREAD_API_KEY"

Verify: run claude mcp list, or /mcp inside Claude Code. A project server shows Pending approval until you approve it the first time you start claude in that project. A wrong key shows a failed connection with HTTP 401.

Gotchas: if ${LOGTHREAD_API_KEY} is unset Claude Code warns and sends the literal text, which gives a 401. Do not paste a trailing newline into the variable.

Codex#

Add to ~/.codex/config.toml. The Codex CLI and the IDE extension share it:

[mcp_servers.logthread]
url = "https://logthread.com/mcp"
bearer_token_env_var = "LOGTHREAD_API_KEY"

You can also put it in .codex/config.toml inside a project, but Codex ignores project config in untrusted projects.

Verify: run codex mcp list, or /mcp in the Codex TUI. In the IDE extension, restart the extension after saving.

Cursor#

Add to .cursor/mcp.json (project) or ~/.cursor/mcp.json (global):

{
  "mcpServers": {
    "logthread": {
      "url": "https://logthread.com/mcp",
      "headers": { "Authorization": "Bearer ${env:LOGTHREAD_API_KEY}" }
    }
  }
}

Verify: open Cursor Settings and check that logthread is enabled and lists its tools.

Gotcha: Cursor must see the variable. Start it from a shell where LOGTHREAD_API_KEY is set, or set it system-wide. Variables expand only in command, args, env, url and headers.

Windsurf#

In the Cascade panel, open the MCPs section and choose Open MCP config file, then add:

{
  "mcpServers": {
    "logthread": {
      "serverUrl": "https://logthread.com/mcp",
      "headers": { "Authorization": "Bearer ${env:LOGTHREAD_API_KEY}" }
    }
  }
}

Verify: logthread appears under MCPs with its tools.

Gotchas: an unset variable silently becomes an empty string, so a 401 usually means the variable is not set where Windsurf runs. This applies to the Cascade agent. Cascade can use at most 100 tools across all servers.

Google Antigravity#

Antigravity's IDE, app and CLI share ~/.gemini/config/mcp_config.json; a workspace can override with .agents/mcp_config.json. In the IDE, open the agent panel, MCP Servers, Manage MCP Servers, View raw config.

{
  "mcpServers": {
    "logthread": {
      "serverUrl": "https://logthread.com/mcp",
      "headers": { "Authorization": "Bearer PASTE_LOGTHREAD_API_KEY_HERE" }
    }
  }
}

Replace the placeholder with your key. Antigravity documents no environment-variable expansion, so this file holds the key: use the global file, never one inside a repository.

Verify: the server is listed under Manage MCP Servers with its tools.

Gotcha: the field is serverUrl. The older url and httpUrl fields are not supported.

OpenCode#

Add to opencode.json in your project, or ~/.config/opencode/opencode.json for every project:

{
  "mcp": {
    "logthread": {
      "type": "remote",
      "url": "https://logthread.com/mcp",
      "enabled": true,
      "oauth": false,
      "headers": { "Authorization": "Bearer {env:LOGTHREAD_API_KEY}" }
    }
  }
}

Verify: run opencode mcp debug logthread.

Gotchas: OpenCode substitutes {env:NAME} with no dollar sign, and an unset variable becomes an empty string. "oauth": false stops OpenCode from starting an OAuth flow when it gets a 401.

VS Code (GitHub Copilot)#

Add to .vscode/mcp.json, or run MCP: Open User Configuration for every workspace. VS Code asks for the key the first time the server starts and keeps it in its secret storage:

{
  "servers": {
    "logthread": {
      "type": "http",
      "url": "https://logthread.com/mcp",
      "headers": { "Authorization": "Bearer ${input:logthread-api-key}" }
    }
  },
  "inputs": [
    { "type": "promptString", "id": "logthread-api-key", "description": "Logthread API key", "password": true }
  ]
}

Verify: run MCP: List Servers from the Command Palette, choose logthread, then Show Output.

Gotchas: the top-level key is servers, not mcpServers (that one belongs to .mcp.json). Workspace servers are blocked in Restricted Mode until you trust the workspace.

Gemini CLI#

Add to ~/.gemini/settings.json (user) or .gemini/settings.json (project):

{
  "mcpServers": {
    "logthread": {
      "httpUrl": "https://logthread.com/mcp",
      "headers": { "Authorization": "Bearer ${LOGTHREAD_API_KEY}" }
    }
  }
}

Verify: run /mcp inside Gemini CLI. logthread should show CONNECTED with its tools.

Gotchas: httpUrl is Streamable HTTP; url means SSE. Undefined variables become an empty string. Avoid underscores in the server name.

Zed#

Run zed: open settings file and add:

{
  "context_servers": {
    "logthread": {
      "url": "https://logthread.com/mcp",
      "headers": { "Authorization": "Bearer PASTE_LOGTHREAD_API_KEY_HERE" }
    }
  }
}

Zed documents no environment-variable expansion for remote servers, so this file holds the key. Do not commit it.

Verify: in Settings, AI, MCP Servers, a green dot and "Server is active" appear next to logthread.

Cline#

Open the Cline panel, MCP Servers, Configure, Configure MCP Servers, and add:

{
  "mcpServers": {
    "logthread": {
      "type": "streamableHttp",
      "url": "https://logthread.com/mcp",
      "headers": { "Authorization": "Bearer PASTE_LOGTHREAD_API_KEY_HERE" },
      "disabled": false
    }
  }
}

Set type explicitly: without it Cline assumes legacy SSE. Cline documents no environment-variable expansion for headers, so this file holds the key. The Cline CLI reads ~/.cline/data/settings/cline_mcp_settings.json.

Verify: logthread is switched on in the MCP Servers panel with its tools listed.

Roo Code#

Edit the project file .roo/mcp.json (Edit Project MCP) or the global mcp_settings.json (Edit Global MCP):

{
  "mcpServers": {
    "logthread": {
      "type": "streamable-http",
      "url": "https://logthread.com/mcp",
      "headers": { "Authorization": "Bearer PASTE_LOGTHREAD_API_KEY_HERE" }
    }
  }
}

Roo Code documents no environment-variable expansion for headers, so this file holds the key. Prefer the global file; do not commit .roo/mcp.json with a key in it.

Verify: logthread shows as connected in Roo Code's MCP settings view.

Amp#

Add to ~/.config/amp/settings.json (user) or .amp/settings.json (project):

{
  "amp.mcpServers": {
    "logthread": {
      "url": "https://logthread.com/mcp",
      "headers": { "Authorization": "Bearer ${LOGTHREAD_API_KEY}" }
    }
  }
}

Verify: servers in a project's .amp/settings.json must be approved first: run amp mcp approve logthread.

Kiro#

Add to .kiro/settings/mcp.json (workspace) or ~/.kiro/settings/mcp.json (user):

{
  "mcpServers": {
    "logthread": {
      "url": "https://logthread.com/mcp",
      "headers": { "Authorization": "Bearer ${LOGTHREAD_API_KEY}" }
    }
  }
}

In the Kiro IDE, add LOGTHREAD_API_KEY to the Mcp Approved Env Vars setting, or Kiro shows a security warning and does not expand it.

Verify: logthread appears in Kiro's MCP server list with its tools.

Claude Desktop#

The simplest way is the custom connector with sign-in, above. If you would rather use an API key, a small local bridge, mcp-remote (needs Node.js), forwards the header instead. Edit claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json, Windows: %APPDATA%\Claude\claude_desktop_config.json):

{
  "mcpServers": {
    "logthread": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote", "https://logthread.com/mcp",
        "--header", "Authorization:${LOGTHREAD_AUTH_HEADER}"
      ],
      "env": { "LOGTHREAD_AUTH_HEADER": "Bearer PASTE_LOGTHREAD_API_KEY_HERE" }
    }
  }
}

The value sits in env and the argument has no space after the colon on purpose: some clients mangle spaces in args. The config file holds your key. Restart Claude Desktop; logthread appears in the tools menu.

The same bridge works for any client that only supports local (stdio) servers. Claude.ai and ChatGPT connectors do not need it: they sign in.

Other clients#

Any client that supports Streamable HTTP and custom headers works with the endpoint https://logthread.com/mcp and the header Authorization: Bearer <API key>. If a client cannot set Authorization, use X-Logthread-Key: <API key> instead.

Not verified against the vendors' own documentation, so use the bridge from the Claude Desktop section if a native setup does not work:

  • JetBrains AI Assistant and Junie: the documented remote server format has only a url field and no headers.
  • Goose: the documented remote extension fields cover OAuth, not custom headers.
  • Continue: the documented remote server fields do not cover custom headers.

To test any client by hand, or to write your own, send three raw requests. The server is stateless, so no session id is involved:

# 1. initialize
curl -s https://logthread.com/mcp \
  -H "Authorization: Bearer $LOGTHREAD_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'

# 2. list the tools
curl -s https://logthread.com/mcp \
  -H "Authorization: Bearer $LOGTHREAD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

# 3. call one
curl -s https://logthread.com/mcp \
  -H "Authorization: Bearer $LOGTHREAD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_services","arguments":{"from":"1h"}}}'

GET and DELETE answer 405, which is normal: only POST is accepted.

Set Logthread up from an agent#

Give the key one or more setup scopes when you create it (Keys → API keys, "Optional: let the agent set things up too"), and the agent can create what your team would otherwise click through in the app.

  • It acts as you. A setup tool runs exactly what the app runs when you do it yourself, with the same checks and limits, as the person who created the key and within that person's current role. If you later lose the role (for example from admin to member) the tool says so; if you leave the workspace, the key can still read but no longer change anything. Ask an admin for a new key.
  • Checked on your data first. Panels and saved searches report how many records their query matched in the last 24 hours, and which fields no record has (with similar names that do). New alert rules report what they would have matched and whether anyone is notified.
  • Nothing half set up. A new pipeline is not active until activate_pipeline; without confirm that tool only shows the plan, the records it matches and a before/after preview on real records. A new collector config is rolled out with activate_fleet_config, and set_redaction and activate_fleet_config work the same way: without confirm they only show the plan (what changes, how many agents match, how many recent records are covered). get_setup_checks lists everything that is saved but not working yet.
  • Deleting needs confirm. Each delete or revoke tool first says what would go and changes nothing until it is called again with confirm: true.
  • Audited twice. Each call is an mcp.tool_call, and the change itself is in the audit log under your name with via: mcp and the key.

The role each tool needs from the key's creator, next to what the app asks for the same action, is the matrix on Roles and permissions. In short: dashboards, alert rules, SLOs, saved searches, annotations, service owners, ingest keys and SQL need Member; alert channels, pipelines, collector configs and integrations need Admin; reading pipelines, collectors and integrations needs Member. Inviting, removing and changing the role of people is not available through MCP (list_team_members only reads).

A typical session: "Set up alerting for the gateway." The agent runs list_services and list_fields, preview_alert_rule for "service:gateway gw.status>=500", create_alert_channel for your on-call email, test_alert_channel, create_alert_rule with that channel, and get_setup_checks to confirm nothing is left.

Add Logthread to an application#

Point your coding agent at the app's repository with this MCP server connected and a key with the sources setup scope, and ask "Add Logthread to this app". The set_up_ingest prompt walks it through:

  1. Keep the logging library. It reads the code and adds the Logthread integration for what the app already uses: Serilog, NLog, log4net or ILogger in .NET; pino, winston, bunyan, log4js or NestJS in Node.js; logging, loguru or structlog in Python. Other languages use OpenTelemetry.
  2. Name things once, in configuration. LOGTHREAD_SERVICE (or the service option) and LOGTHREAD_ENVIRONMENT. The same service running in several places keeps one name and a different environment.
  3. Correlation ids. The SDK's middleware reuses an incoming X-Correlation-Id, X-Request-Id or traceparent, or generates one, puts it on every log of the request and on outgoing calls, so one request reads as one story across services.
  4. A key, kept secret. create_ingest_key makes one; the agent hands it to you as LOGTHREAD_INGEST_KEY and never writes it into code.
  5. Checked on real traffic. check_instrumentation reports, per service, what arrives (records, hosts, environment, the share of records with correlation and trace ids, the SDK) and the fix for each gap.

Resources and prompts#

Besides tools, the server offers MCP resources (read-only context a client can attach, without a tool call) and prompts (ready-made workflows a client shows as slash commands). Both use the same API key, belong to the key's workspace only, and need no scope beyond read.

Resources (resources/list, resources/read, resources/templates/list):

URIWhat it is
logthread://docs/<page>One documentation page as markdown, for every page in these docs. Long pages are cut at 6,000 characters and say which sections to read next.
logthread://docs/{page}#{section}One section of a page (a template: the section ids are listed when a page is cut).
logthread://workspace/fieldsThe field names your logs carry (service, severity, attr.*, res.*) over the last day, as list_fields returns them.
logthread://workspace/servicesThe services that sent logs in the last day, with event, error and warning counts, as list_services returns them.
logthread://workspace/setup-checksWhat is saved but not working yet, as get_setup_checks returns it.
logthread://ltql/grammarThe Logthread Query Language, the same text as ltql_grammar.

An unknown page or section is a JSON-RPC error -32002 (resource not found). A resource read counts toward the same 120 calls per minute as a tool call, and is audited as mcp.resource_read with a hash of the URI, never the URI itself.

Prompts (prompts/list, prompts/get) each return one message that tells the agent which tools to call in which order. Every one checks a query with validate_query before search_logs runs it, and a prompt that could change something waits for your agreement and falls back to a plan when the key lacks the scope.

PromptArgumentsWhat it does
investigate_incidentservice, time_range (both optional)Finds when errors started, the new patterns, one failing request across services and the likely cause.
explain_queryltql (required)Validates an LTQL query, explains it piece by piece and reports how many records it matches.
build_alertgoal (required)Turns a goal into a checked rule: validated query, a channel, a 24 hour preview, then creation if you agree.
build_dashboardservice (optional)Plans panels from your real fields and metrics, checks each query and creates the dashboard.
set_up_ingestplatform (optional)Finds the right docs page, creates or reuses an ingest key and confirms the first logs arrived.
reduce_costnoneFinds the noisiest services and patterns and proposes safe cuts, previewed on real records.

Time arguments#

Tools that read data take from and to:

  • ISO 8601 in UTC, for example 2026-10-18T14:00:00Z; or
  • relative to now: 15m, 2h, 7d (to: "5m" means five minutes ago).

The default is the last hour. search_logs and count_logs take any range within your workspace's retention: search_logs reads it newest first in 7-day slices and hands back next_cursor (with scanned, how far back the page looked) to keep going, and count_logs is a chart over the whole range. The other tools read at most 7 days. If your range is longer than a tool takes, Logthread uses the most recent part and says so: every result contains time_range with clamped and notes. It never silently widens a range.

Tools reference#

There are 88 tools. Each write tool needs its scope: a key without the sql scope does not see run_sql, a key without the annotate scope does not see create_annotation, and so on for the setup tools below, so a plain read key sees 51. Three of them, search_docs, get_doc and list_docs, answer questions about Logthread itself from its documentation and never read your workspace data. Every result that names a page (a dashboard, alert rule, incident, pipeline, collector config, channel, ingest key, saved search or Explore search), in lists too, gives its link in the app and its full url. Every log tool that takes a service name says when a named service sent nothing in the range, with the close names that did.

All tools return structuredContent (JSON) and a compact text rendering, one line per log: timestamp severity service message {attributes}. Log messages are cut at 2,000 characters (the row then has "truncated": true), and lists default to 50 rows and never exceed 200. Results include stats (read_bytes, elapsed_ms) and a next_cursor when more is available.

list_services#

Services that sent logs in the range, with event, error and warning counts. Start here.

ArgumentMeaning
from, toTime range.
limitMost services (1 to 200, default 50).

Example: {"from": "1h"}

check_instrumentation#

What arrives from one service after the app was instrumented, and the fix for each gap: records and hosts, service.name, deployment.environment, the share of records with a correlation id and with a trace id, and the SDK or integration that sent them. Each failing check has a fix with its docs link.

ArgumentMeaning
serviceThe service name the app sends as.
from, toTime range (default the last hour).

Example: {"service": "orders-api", "from": "15m"}

search_logs#

Find log events, newest first.

ArgumentMeaning
from, toTime range.
serviceList of service names.
severityList of fatal, error, warn, info, debug, trace, unknown (records with no severity).
textCase-insensitive text in the message or an attribute value.
filtersList of {field, op, value}. Fields: service, severity, body, trace_id, span_id, attr.<key>, res.<key>. Operators: eq, neq, in, contains, exists, not_exists.
queryA Logthread query-language string: a selector, optionally followed by pipe stages (json, where, fields, stats and so on). Use it instead of service, severity, text and filters, not with them. A stats pipeline returns a table (kind: "table"). LogQL is accepted too: it is translated to LTQL, and the answer starts with the LTQL it ran (translated).
limit1 to 200, default 50.
cursornext_cursor from the previous page.
orderdesc (default) or asc. asc lists the returned page oldest first; paging still moves back in time.
fieldsOnly these fields in each row (plus ts and event_id), for example ["service", "body", "attr.http.status"]. Keeps large pages small.
compacttrue for short rows: the message cut to 300 characters, no resource attributes, at most 10 attributes.

Example: {"from": "30m", "service": ["checkout"], "severity": ["error"], "filters": [{"field": "attr.http.status", "op": "eq", "value": "500"}]}

Each row has event_id, ts and, when present, trace_id. A query search also answers with url, the same search in Explore. When a named service sent nothing in the range, the answer says so and suggests the close names that did (gatewy → gateway).

Records from the Logthread Agent carry the machine's res.host.name and res.host.ip (one address, the main interface's), so {"field": "res.host.ip", "op": "eq", "value": "10.0.0.4"} or the query res.host.ip:10.0.0.4 shows what one server sent, even from behind NAT.

Parsed fields work in query as plain names. If a pipeline parsed gw.organization and gw.status into attributes, this counts one organization's requests by status:

{"query": "service:gateway gw.organization:abc | stats count() by gw.status", "from": "6h"}

In SQL (run_sql) the same fields are map entries, for example LogAttributes['gw.status'].

count_logs#

Counts over time by severity, plus a total.

Arguments: the same filters as search_logs (service, severity, text, filters), from, to, and interval (auto, or a width such as 30s, 5m, 1h; 10 to 240 buckets). When the width you ask for does not fit the range, the answer says which width it used (interval_note). Counts are by fatal, error, warn, info, debug, trace and unknown. compact: true returns only the totals and the busiest bucket, for an overview of a long range.

Example: {"from": "6h", "severity": ["error"], "interval": "15m"}

top_values#

The most frequent values of fields among matching logs.

Arguments: the same filters as search_logs, fields (up to 5 of service, severity, trace_id, attr.<key>, res.<key>; default service and severity), top (1 to 20, default 10).

Example: {"from": "1h", "fields": ["attr.http.route"], "severity": ["error"], "top": 5}

list_fields#

Attribute (attr.*) and resource (res.*) keys seen in matching logs, with counts from a sample. Use it before filtering on a field.

Arguments: the same filters as search_logs.

get_log_context#

Events immediately before and after one event.

ArgumentMeaning
event_id, tsFrom a log row, exactly as returned.
before, after0 to 50 each (default 10).
same_serviceOnly events of the same service.

get_story#

Everything that belongs to one request or flow across services, in time order, with a one-line narrative, per-service counts and the first error. Pass a correlation id, a request id or a trace id.

ArgumentMeaning
idThe id to follow.
from, toTime range (default: the last 24 hours).
max_rowsMost events per story (1 to 500, default 200).
window_sSeconds around the matched events in which related events are collected (default 900).

Example: {"id": "4bf92f3577b34da6a3ce929d0e0e4736"}

find_request#

The thread of an id: every log event that mentions it, across all services, oldest first, collapsed into steps, plus the traces found and the connected values. The id can be a request, order, user or session id, or a trace id: it matches an attribute value, a trace or span id, or a whole word in the message. Use it when you are given an id and do not know which field holds it; then call get_trace or get_log_context.

ArgumentMeaning
idThe id: 1 to 200 characters of letters, digits and . _ : / + = -.
alsoUp to 4 more ids to pull into the same thread, usually link values from connected.
from, toTime range (default: the last 24 hours; at most 7 days).

Returns:

  • logs: up to 200 events.
  • summary: events, services in order, errors, duration and the first error.
  • steps: consecutive events of one service and message pattern.
  • connected: other ids on the same lines, with field, value, events, services and kind. A link is specific enough to follow, and extends: true means it reaches lines the thread does not have yet. A context value, such as a host or region, is shared by many lines.
  • traces: trace_id, service, name, duration_ms, status, start.
  • truncated.

Example: {"id": "order-A1", "also": ["pay-77"]}

get_flows#

What is happening right now, and how requests move between log messages. It returns:

  • changes, listed first:
  • error steps that are growing against the previous period of the same length, such as a login failure or a payment timeout climbing;
  • healthy steps that are falling, such as logins succeeding less.
  • transitions: every move between message patterns, within and across services, with its count, its share of the step it leaves, whether it is new and whether it leads to an error.

Flows are learned from your logs that carry request, correlation or trace ids. Start an investigation here. Then call explain_step on a growing error to see who or what is behind it, compare_failing for what failing requests have in common, and find_request on an example id.

Arguments: service (only transitions into or out of it), query (only requests with a line matching this Explore query, for example username:user042; no | stages), limit (1 to 200, default 40), from, to (default: the last 24 hours; at most 7 days over MCP).

Example: {"service": "auth", "query": "tenant:acme", "from": "1h"}

explain_step#

Who and what is on one step's log lines. A step is a service and a message pattern from get_flows. It returns:

  • fields: each field's top values and their share of the lines. A field is marked concentrated when one value is on 20% or more of the lines, for example one username on a third of the failed logins.
  • links: unusual links between fields. One value of a field is tied to at least 5 distinct values of another, and to at least 3 times what is typical for that pair of fields. For example, one client IP behind 70 usernames points to credential stuffing; one tenant on 9 versions points to a half-finished rollout. Ordinary fan-out, such as a load-balanced host serving many users, is left out.
  • id_fields: fields with a different value on almost every line (request ids). They are ids, not entities, so they are left out.

Arguments: service, pattern_id, from, to (default: the last 24 hours; lines over at most 7 days).

Example: {"service": "auth", "pattern_id": "4f1c2a9e0b7d3c55"}

compare_failing#

What failing requests have in common. A request is a correlation, request or trace id; it is failing when any of its lines is an error. The tool returns:

  • failing and healthy request counts;
  • values: attribute values such as a version, host, route or tenant;
  • paths: moves between log messages.

Each value and path is much more common in failing requests and lists failing_pct, healthy_pct, lift and a failing example id to pass to find_request. When healthy requests never have it, only_in_failing is true and the text says "only in failing requests" instead of a lift.

Arguments: service (only requests with a line in it), from, to (default: the last 24 hours; at most 7 days).

Example: {"service": "payments", "from": "6h"}

who_is_failing#

From raw lines: who has failed attempts, or with value the full picture of one user, address, key or account.

get_service_owners#

The team, contact and runbook of one service, or of every owned service.

set_service_owner (alerts scope) records the owner from an agent: service, team, and optionally contact and runbook_url. The service must have sent logs in the last 7 days; a misspelled name is refused with the close names that did.

investigate_incident#

Runs the fixed investigation of one incident and returns leads with their evidence, confidence and next check.

list_linking_fields#

Attribute fields whose values tie log lines together, found automatically from the newest 50,000 lines in the range. They are usually order, user, job or request ids, including fields that pipelines extract. Each field lists how many of its values appear in several services (or says it links lines within one service) or on several lines, and sample values to pass to find_request. File and object paths never count as ids.

Arguments: from, to (default: the last 24 hours; at most 7 days).

Example: {"from": "6h"}

list_patterns#

Recurring log message templates with counts, change versus the previous period and whether they are new.

Arguments: from, to, service, severity, text (part of the template), sort (count, new, rising, errors, daily), limit (1 to 50, default 20). daily reads the last 7 days whatever the range and lists errors and warnings that come back at the same time of day, with daily.days, daily.start_min and daily.end_min (UTC minutes of the day).

Example: {"from": "24h", "sort": "new"}

search_traces#

Find traces.

Arguments: from, to, service, operation, status (error or ok), min_duration_ms, max_duration_ms, attributes (list of {field: "attr.<key>" or "res.<key>", value}), limit (1 to 50, default 20), cursor.

Example: {"service": ["payments"], "min_duration_ms": 1000, "status": "error"}

get_trace#

One trace as a span tree (parents before children) with start offsets, durations, status and key attributes. At most 300 spans are returned; truncated says when there are more.

Argument: trace_id (32 hexadecimal characters).

list_metrics#

Metric names with type and unit. logthread.flow.transitions is listed too, marked made_by_logthread: Logthread makes it from your logs (Threads → Flows) and does not charge for it.

Arguments: from, to, service, search (part of the name), limit (1 to 200, default 50).

query_metric#

A metric time series, with a summary (min, max, average, last) per series. Grouped series are named by their labels (service=api route=/pay); an ungrouped one is <metric> (all series).

ArgumentMeaning
metricMetric name.
typegauge, sum, histogram or summary; only needed if a name exists with several types.
aggregationauto, avg, min, max, sum, rate, increase, p50, p90, p95, p99, last.
serviceList of services.
filtersList of {key, op: "=" or "!=", value} label conditions.
group_byUp to 2 label keys, or service.
interval_sBucket width in seconds.
limit_series1 to 10, default 5.

list_alerts#

Alert rules with their current state (firing or ok), their conditions, and recent incidents.

Argument: recent_incidents (0 to 50, default 10).

topology_downstream, topology_upstream#

What a service calls, directly or through other services, or what calls it, from the workspace's topology graph (see Service map). Each result is a service, database or external host with its hop count. In the app the same answers are in the Service map's node panel (Relationships), the Relationships card under the map, and an incident's Related services.

ArgumentMeaning
serviceA service name.
hops1 to 5, default 3.

topology_shared_dependencies#

Dependencies that two or more failing services have in common, a likely shared cause. Arguments: services (default: services with firing incidents, else those with errors in the latest hour) and hops (1 to 4, default 2).

topology_downstream_patterns#

Error pattern ids recurring in services downstream of a service in the last 24 hours, with the service, hop count and error count. Arguments: service, hops. Read a pattern with list_patterns.

topology_diff and topology_incident_clusters#

topology_diff lists calls that appeared or disappeared between two times (before, after, ISO 8601 within the last 30 days). topology_incident_clusters groups the incidents of the last days (1 to 30, default 7) by the dependency their services share.

validate_query#

Checks a query without running it. Arguments: language (ltql default, kql or sql), query, and for LTQL count_matches (also count the records it matches in from/to, default the last 24 hours, and name fields no record has).

An invalid query returns valid: false, the compiler's error and, for LTQL, the line and column of the mistake. A valid one returns valid: true and its pipeline stages. Counting returns matches and missing_fields (counts and names only). KQL and SQL are checked against the same rules as the SQL page and then parsed by the query engine without reading any data: an invalid one returns the engine's message (with line and column when it points into your text), a valid one returns its result columns (name and type). run_sql runs them, with the sql scope.

Example: {"language": "ltql", "query": "service:checkout severity:error", "count_matches": true}

ltql_grammar#

The Logthread Query Language: selector filters, | stages (json, logfmt, regexp, where, stats, sort, limit), functions and operators, plus the SQL tables and the KQL dialect that run_sql takes. Argument: topic (overview default, selector, stages, functions, operators, sql, kql). Read it before writing a query.

get_alert_rule, get_incident, get_channel#

One alert rule (rule_id), incident (incident_id) or channel (channel_id), with ids from list_alerts and list_alert_channels. A rule shows its type, the config it watches, severity, channels, state and whether anyone is notified; an incident its value against the threshold and the notifications sent; a channel its recent deliveries, never its secrets. They run as the key's creator, like the other setup reads.

get_usage_summary, list_team_members#

get_usage_summary (optional month, YYYY-MM): what the workspace sent against its plan (sizes in decimal units: 1 GB is 10^9 bytes, as billed), the share of the included volume (or that the plan has no included allowance), the month-end projection for a month still running, the estimated overage and the AI credits. list_team_members: members with their roles and pending invitations. Both read this workspace only.

list_saved_searches#

Searches your team saved and shared in Explore, with their query. Private searches are never listed.

search_docs#

Search the Logthread documentation. Use it for "how do I..." and "what does Logthread support" questions, such as creating an ingest key, sending logs from a collector or writing a query. Each result has the page title, section, URL and a snippet; the agent should cite the URL.

ArgumentMeaning
queryWhat you want to know, in plain words.
limitResults returned, 1 to 10, default 5.

get_doc#

The full text of one documentation page or section (cut at 6,000 characters), from a URL returned by search_docs or list_docs. Pass section (the id after # in the URL) to read one section.

list_docs#

Every documentation page with its URL and group. Takes no arguments.

list_annotations#

Deploys, reported changes, alert incidents and custom events in a time range, newest first. Works with any key.

ArgumentMeaning
from, toTime range (default: the last 24 hours; at most 90 days).
kindsAny of deploy, change, incident, custom (default: all).
servicesOnly these services. Markers without a service are always included.

Each item has id, kind, time, end (ranges and incidents), title, text, service, tags, link and state (incidents). The result carries truncated when more than 500 match.

what_changed#

Changes in the hour before a problem, scored against the service that broke (see What changed before an incident). Works with any key. Each result is a hypothesis to check, never a conclusion.

ArgumentMeaning
serviceThe service with the problem.
atWhen it started: ISO 8601 UTC or a relative age such as 20m. Default: now.

Each item has title, change_type, service, version, url, minutes_before, score, confidence, relation, errors_before, errors_after and reasons. graph is false when dependencies could not be read, so only direct changes are listed.

create_annotation#

Mark an event, such as a deploy, a config change or an experiment, on your charts. Requires an API key with the annotate scope; keys without it do not see this tool, and calling it returns "This key cannot create annotations".

ArgumentMeaning
titleRequired, 1 to 200 characters.
textOptional note, up to 2,000 characters.
timeWhen it happened (ISO 8601 or relative such as 10m; default now; within the last 30 days).
endMakes it a range; at most 7 days after time.
serviceOptional service name.
tagsUp to 10 tags of letters, digits and _ . : / -.

The annotation is recorded with the key as its author. See Chart annotations.

create_alert_rule#

Create an alert rule that watches a search or a computed value. Requires an API key with the alerts scope; keys without it do not see this tool, and calling it returns "This key cannot create alert rules". It goes through the same checks as the app: a search that cannot run is refused with the same message Explore gives. Check the search first with validate_query (it reports a mistake with its position, and with count_matches how many records the search matches and which fields no record has), or run it with search_logs and a short from; preview_alert_rule shows what the rule would have done in the last 24 hours.

ArgumentMeaning
nameRequired, 1 to 200 characters.
typescheduled_query (count the records a search matches) or query_value (one aggregate such as p95, or a percentage of two searches).
configAs in POST /api/alerts/rules. A search: {"filter": {"query": "service:gateway gw.status>=500"}, "window_min": 10, "every_min": 5, "condition": {"op": ">=", "value": 20}}. A percentage: {"query": "service:gateway gw.status>=500", "ratio_of": "service:gateway", "op": ">", "threshold": 2, "min_count": 50}.
severityinfo, warning (default) or critical.
channelsChannel ids to notify (from the Alerts page); none means incidents only.

The rule's author is the person who created the key, so the key's creator must still be an active member with at least the member role (a removed creator's key creates nothing); the creation is audited with that person and the key. The reply says what the rule would have matched in the last 24 hours and, when it has no channels, that it notifies no one. Change, mute or attach channels with update_alert_rule. See Alerts.

When search_logs text is found in a field rather than in messages, the reply adds text_found_in (each word, its message count, and the fields and values that hold it) and a sentence such as "bulbul is in no message, only in model (435 lines): filter with model:bulbul:v4-flash." Use that exact filter for later searches, counts and alerts.

When a search_logs query finds nothing, the reply lists missing_fields: fields the query filters on that no record in the range carries, with similar names that exist. Fix the query rather than concluding there is no such data.

get_setup_checks#

Everything saved but not working yet, each with its next step: pipelines no route uses, routes on an older version, alert rules that notify no one, failing channels, ingest keys that never sent data, and services that stopped sending on every host: quiet for over an hour and for over 3 times their own longest gap in the last 7 days, so a service that logs only at startup is not flagged (a host that went quiet while its service still sends from other hosts was replaced, and is not listed; a service quiet for more than three days is treated as retired and stays only on the Sources page). No arguments.

list_dashboards, list_pipelines, list_fleet, list_sources, list_alert_channels#

What exists, with ids for the setup tools. list_pipelines gives each pipeline's state (active, not active, outdated), the active routes, whether redaction runs on every record, and the Library presets. list_fleet gives collector configs with their latest and active versions, and templates. list_sources gives ingest keys (never their secrets) and connectors. list_alert_channels never returns channel secrets.

create_dashboard and add_dashboard_panel#

create_dashboard: name, optional template (from list_dashboards, such as logs-overview), description, visibility (shared, the default, or private), refresh (live, 10s … 15m, or off). Then add_dashboard_panel with dashboard_id, title, type (timeseries, bar, stat, gauge, bargauge, state_timeline, heatmap, table, pie, logs or text), and an LTQL query: service:checkout level:error charts matches over time, service:gateway | stats p95(gw.duration_ms) by service charts a computed value; group_by gives the top values of a field; text is Markdown for a text panel; width (of 12) and height. The reply says how many records the query matched in the last 24 hours.

Panels also take options, as the panel editor sets them: unit, decimals, min, max, reduce, thresholds ([{value, color}]), threshold_base (the colour below every threshold), mappings ([{type: value|range|null|regex, value|from|to|pattern, text, color}], such as 1 as ONLINE in green), stat_series (each for one tile per series), color_mode (value, background, background_solid, none), graph_mode, text_mode, bar_display (gradient, basic, lcd), orientation, show_unfilled, show_values and cell_display. Colours are ok, warn, err or #rrggbb. repeat ({variable, direction, max_per_row}) draws the panel once per value of a dashboard variable. update_dashboard_panel merges options into the current ones (a null value removes one).

set_dashboard_refresh: dashboard_id and refresh (live keeps the time axis moving and new data streaming in). import_grafana_dashboard: a Grafana dashboard JSON as dashboard, optional name and visibility; PromQL, LogQL and TraceQL become Logthread queries checked on your data, and the reply lists what each panel shows and which need a query.

get_dashboard and update_dashboard_panel#

get_dashboard: dashboard_id; returns the variables and each panel's id, title, type, size and, for a logs panel, its LTQL query (and group_by). update_dashboard_panel: dashboard_id, panel_id and any of title, type, query, group_by, text, width, height; the rest of the panel and the dashboard stays as it is. A new query is counted over the last 24 hours, as add_dashboard_panel does. Metrics, traces and SQL panels change their title and size here and their queries in the app. Use bin(ts, auto) so a chart fits every range. Removing a panel stays in the app.

set_dashboard_variables#

dashboard_id and variables, which replaces the dashboard's variables (send [] to remove them). Each variable is {name, label?, type, ...}: query with query: {source: "services"}, {source: "log_field", field: "attr.gw.operation", query?} or {source: "metric_label", metric, key}; custom or interval with values; or textbox. multi, include_all and default work as in the dashboard editor. Panels use a variable as $name, for example service:$service | stats count() by bin(ts, 5m). The reply says how many real values each query variable has over the last 24 hours, with a sample, and names any variable no panel uses yet.

name, LTQL query, range (how far back Explore opens it: 15m, 1h, 24h, 7d) and visibility. The reply includes the 24-hour match count.

list_slos, create_slo#

list_slos returns each SLO with its SLI against target, the error budget left, the burn rate in the last hour and whether burn alerts exist. create_slo (alerts scope) takes name, total and bad (LTQL selectors), target (90 to 99.999) and window_days (7, 28 or 30), and answers with the numbers on your data. Its answer says what the bad records are ("67 of 76 bad have code=..."). With burn_alerts: true, optional channels and min_count (default 50 requests per window), it also adds the fast and slow burn-rate rules. update_slo (alerts scope) changes any of name, selectors, target and window, and the burn rules follow. delete_slo (alerts scope, confirm) removes an SLO, and with delete_burn_alerts: true its burn rules too.

Delete and revoke#

delete_dashboard, delete_dashboard_panel, delete_saved_search (dashboards scope), delete_alert_rule, delete_alert_channel, delete_slo (alerts), delete_pipeline (pipelines), delete_fleet_config (fleet), revoke_ingest_key (sources) and delete_annotation (annotate) each take the object's id and confirm. Without confirm they change nothing and say exactly what would go; ask the user, then call again with confirm: true. They use the app's own rules: a pipeline the active routes use, a collector config with an active version and automatic deploy markers are refused, and a removed panel stays in the dashboard's history. Every delete and revoke is in the audit log.

preview_alert_rule, update_alert_rule, create_alert_channel, test_alert_channel#

preview_alert_rule takes type and config as create_alert_rule does and says what the rule would have done over the last 24 hours (an error spike: the last 3 hours), with last_fired_at. A count rule replays every evaluation; a computed value also says whether it crosses the limit now (would_fire) and gives fired_24h with a history_note on how it was checked. For a ratio rule it also gives ratio_24h: the numerator and denominator counts, their ratio, and the highest ratio of any window with its time and service. update_alert_rule takes rule_id and any of name, enabled, severity, channels (replaces the list), config, mute_until. create_alert_channel takes type (email, slack, teams, discord, pagerduty, opsgenie, firehydrant, webhook, apprise), name and config (email: {"to": ["oncall@example.com"]}; Slack, Teams, Discord: {"webhook_url": "..."}; webhook: {"url", "secret"}; PagerDuty: {"routing_key"}). test_alert_channel sends a test.

create_pipeline, preview_pipeline, activate_pipeline, set_redaction#

create_pipeline: name and one of preset (a Library preset id), query (an LTQL query whose parse and where stages become the pipeline) or config ({"functions": [...]} as the editor saves it); none makes an empty pipeline. preview_pipeline: pipeline_id and optional services; runs it on up to 100 of the latest real records from the last 24 hours and says how many would change. activate_pipeline: pipeline_id, where (every: after any route on every record, for redaction; match: a new route for services, named route_name; replace: switch route_ids to this pipeline) and confirm. Without confirm it changes nothing and returns the planned routes, the matching records and the preview; with confirm: true it activates them in one step, exactly as the app's Activate wizard does. get_pipeline: pipeline_id and optional version; returns that version's config (its functions), the version history and the state. update_pipeline: pipeline_id, the whole new config (start from get_pipeline's, so no step is lost) and an optional note; saves it as a new version and previews it on recent real records. Routes keep running their version until you roll it out: the reply names the routes and the exact activate_pipeline call (where: "replace" with their route_ids). set_redaction turns Redact secrets in every route on or off: enabled and confirm; without confirm it shows the plan (routes covered, records in the last 24 hours) and changes nothing.

create_fleet_config, activate_fleet_config#

create_fleet_config: name, template (host, kubernetes, livekit, relay) or yaml, selector (agent labels such as {"env": "prod"}; empty for every agent) and priority. It is validated as in the app and is not rolled out until activate_fleet_config (config_id, optional version, default the latest, and confirm). Without confirm it shows the plan: the version, what runs now and how many agents match the selector (zero means nothing would receive it); with confirm: true it rolls the version out.

create_ingest_key, connect_livekit#

create_ingest_key takes a name and returns the key once, with the endpoints to send to; store it in a secret. connect_livekit takes the fields of Sources → LiveKit (name, deployment cloud or self_hosted, api_key, api_secret, optional project_id, analytics, mask_phone) and returns the remaining setup steps.

list_integrations, add_integration, get_integration#

Ready-made integrations for software you run (SQL Server, IIS, PostgreSQL, MySQL, Redis, MongoDB, Elasticsearch, Kafka, RabbitMQ, NGINX, Apache, Linux hosts, Docker and Podman, Kubernetes, Pipecat) and for cloud services (AWS CloudWatch Logs through Amazon Data Firehose, Cloudflare Logpush, Vercel and Heroku log drains, Google Cloud Logging through Pub/Sub, Azure Monitor through an Event Hub) and audit logs Logthread reads with a token (Okta System Log, GitHub organization audit log). Each has a public guide at https://logthread.com/sdk/docs/integrations/<id>.html, which search_docs and get_doc also read.

  • list_integrations (optional search, such as postgres or windows) returns each one's questions (answers fields, with defaults) and how it works. Agent integrations install the Logthread Agent in your own environment. It reads the software locally and sends only outbound HTTPS to ingest.logthread.com, so nothing is exposed to the internet.
  • add_integration takes kind, an optional name and answers. It creates the integration with its own ingest key and returns the install command or app settings with the key filled in. For a cloud service it returns the Logthread address and key to paste into that service (a Firehose destination, a Logpush job, a log drain); nothing is installed. The key is shown only in this reply.
  • get_integration takes the integration's id (from list_sources). It returns how many records arrived in the last 24 hours and the setup steps again, with YOUR_INGEST_KEY in place of the key.

A typical session: "Monitor our orders Postgres." The agent:

  1. runs list_integrations with postgres;
  2. asks you for the server;
  3. runs add_integration with kind postgresql and answers {"endpoint": "orders-db:5432"};
  4. gives you the monitoring-user SQL and the one install command to run on that server;
  5. runs get_integration until records arrive, then create_dashboard with the postgresql template.

run_sql#

Read-only SQL or KQL over your logs, traces and metrics. Requires an API key with the sql scope; keys without it do not see this tool, and calling it returns an error.

ArgumentMeaning
sqlOne SELECT over obs.logs, obs.spans, obs.trace_ids and obs.metrics_*, or one KQL query when dialect is kql. The time range is always applied: in SQL each table holds only that range (binding {from:DateTime64(3)} and {to:DateTime64(3)} as well gives the same answer); in KQL it is applied after the first table.
dialectsql (default) or kql.
paramsNamed parameters used as {name:Type}.
from, toTime range (default: the last hour; may reach back as far as your retention).
limitRows returned, 1 to 200, default 50.

Example: {"sql": "SELECT ServiceName, count() AS n FROM obs.logs WHERE Timestamp >= {from:DateTime64(3)} AND Timestamp < {to:DateTime64(3)} GROUP BY ServiceName ORDER BY n DESC LIMIT 10", "from": "6h"}

KQL example: {"dialect": "kql", "sql": "logs | where SeverityNumber >= 17 | summarize errors = count() by ServiceName | order by errors desc", "from": "6h"}

The same rules apply as in the SQL page of the app.

Keys, scopes and security#

  • Read-only unless you add a scope. Every tool only reads, except create_annotation (annotate scope) and the setup tools (alerts, dashboards, pipelines, fleet and sources scopes), which act as the key's creator within that person's role. The delete and revoke tools need the creating scope and confirm: true, and API keys cannot send data (that takes an ingest key).
  • Workspace-scoped. A key sees only its own workspace. Tool arguments never name a workspace.
  • Scopes. read is always granted. Add sql only for agents that need run_sql, annotate only for agents or pipelines that should mark events on charts, and a setup scope only for what the agent should set up; without the scope the tool is not even listed.
  • Signed-in apps (OAuth) get short-lived access (one hour, renewed automatically for up to 30 days of inactivity) for one workspace, with the permissions you allowed. Logthread stores only a hash of each token. A renewal token used twice ends the connection, because only a copy would do that. Each tool call in the audit log names the app (oauth_client), and connecting or disconnecting is recorded as oauth.app_connected and oauth.app_revoked.
  • Create and revoke keys on the Keys → API keys tab (owners and admins). Revoking takes effect immediately on the server that handles it and within 60 seconds everywhere. Optionally set an expiry of 30 days, 90 days or 1 year.
  • One key per person or agent, named after them, so the audit log tells you who asked what. Rotate by creating a new key, switching the client, then revoking the old one.
  • Audit log. Every tool call is recorded as mcp.tool_call with the key, the tool, a hash of the arguments, the duration, the status and the rows and bytes returned. The arguments themselves are never stored.
  • Masking applies. Values masked by your pipelines are masked for agents too.
  • Treat log content as untrusted. An agent reads whatever your services logged. Do not give it other tools that can act on your systems without your approval.
  • Keep keys out of Git. Prefer clients that read the environment variable. Where the config file holds the key, keep that file in your home directory, not in a repository.

Limits#

Rate limit120 tool calls per minute per API key. Over the limit the call returns an error result that says how many seconds to wait.
Failed sign-ins20 failed authentications per IP address per 10 minutes; then 429 until the window passes. Every failure gets the same 401, so a wrong key reveals nothing.
Time rangeAt most 7 days per query and never beyond your retention (run_sql may reach back as far as your retention).
Rows50 by default, at most 200 per call. Use next_cursor for more.
Log messagesCut at 2,000 characters; the row then has "truncated": true.
TracesAt most 300 spans per trace.
QueriesThe same per-workspace concurrency limits and short result cache as the app.

Troubleshooting#

You seeMeaning and fix
The connector says sign-in failed, or Logthread shows "Only workspace owners and admins can connect apps"Ask an owner or admin of the workspace to connect it, or to create an API key for you. "Redirect address not allowed" means the app returned to an address it did not register: remove the connector and add it again.
401 "Invalid or expired access token."A signed-in app's access ended: it was disconnected, its renewal failed, or the person who allowed it is no longer an owner or admin. Connect it again.
401 "Invalid or missing API key."The message is the same for every key problem, on purpose. When the token is not even shaped like an API key (an unexpanded ${LOGTHREAD_API_KEY}, an ingest key lt_..., a missing ltk_ prefix, the wrong length) the JSON-RPC error.data.hint says which; a plausible key that is unknown, revoked or expired gets no hint. Check, in order: the key starts with ltk_ (an ingest key, lt_..., can only send data); the header is Authorization: Bearer ltk_...; the environment variable is set where the client starts (a literal ${LOGTHREAD_API_KEY} means the client did not substitute it); the key was not revoked or expired under Keys → API keys.
429 "Too many failed attempts."More than 20 failed authentications from your IP address in 10 minutes. Requests are refused until the window passes (see Retry-After), even with a valid key. Fix the key, then wait.
403The workspace is suspended or still being set up, or the request carried an untrusted Origin header (browser-based clients).
405 on a GET or DELETENormal. The server is stateless and accepts only POST. If a client treats it as fatal, it is not using Streamable HTTP.
Client says the connection failed but curl worksThe client config is wrong: wrong key name (serverUrl, httpUrl, url, servers against mcpServers), an unexpanded variable, or the client was not restarted.
isError with "Rate limit exceeded"More than 120 tool calls in a minute. Wait for the number of seconds given.
"tool not found: run_sql"The key lacks the sql scope. Create a key with Read + SQL access.
isError with query_too_expensive or a timeoutNarrow the range or add filters (service, severity, text).
isError with "Invalid arguments"The message lists the offending argument, what the tool takes and one valid example call. Times must be ISO 8601 or relative (15m).
isError with "The query was rejected"The message carries the compiler's own reason (and the position of a mistake in LTQL). Run validate_query on it.
A result says clamped: trueYour range was longer than 7 days or your retention; the notes say what was used.
Tools list is empty or staleRestart the client. Run the tools/list request above to see what the server returns.
Works at home, not at workA corporate proxy may block or rewrite the connection, strip the Authorization header or inspect TLS. Ask IT to allow logthread.com, or try X-Logthread-Key if the header is being stripped.

FAQ#

Can I use Claude.ai on the web or ChatGPT? Yes. Add a custom connector with the URL https://logthread.com/mcp and sign in (how). Claude Code, Codex, Cursor, VS Code and the other clients here use an API key and need no bridge.

Local or project config? Project config (Claude Code .mcp.json, .cursor/mcp.json, .vscode/mcp.json) can be shared in Git when it references an environment variable or prompts for the key, because the key itself is not in the file; each teammate sets their own key. Use user or global config for personal setups, and always when the file would hold the key itself.

I have several workspaces. One API key belongs to one workspace. Add one server entry per key with distinct names, such as logthread-prod and logthread-staging, each with its own environment variable (LOGTHREAD_PROD_KEY, LOGTHREAD_STAGING_KEY).

Can agents change anything? Only with a scope you give the key: then they can create and activate things (never delete), as you and within your role. A plain read key changes nothing.

Does the agent send my logs to a third party? Results are passed to the AI model your client uses, under that vendor's terms. Choose your client and model accordingly.

Next: meet Pari, the assistant in the app.