# Firetiger Documentation > Firetiger is a REST API platform (JSON-over-POST, AIP conventions, Basic auth) for configuring autonomous agents that monitor software systems using telemetry data and connected tools. ## Table of Contents - **Concepts**: Agents, Services, Integrations, Providers, Telemetry, Deployments, Issues, Knowledge - **Guides**: OpenTelemetry Integration, MCP Server, Agent Webhooks, Create a Custom Slack Handle for an Agent, BigQuery Integration, Datadog Alerts, incident.io Workflows, Change Monitor, Fixing issues with coding agents, Impact Reports, Connect to a Private Database with Tailscale, GCP Cloud Build, Investigate GCP Error Reporting, Custom Agent Skills - **Integrations**: PostgreSQL, MySQL, ClickHouse, Iceberg, Trino, Elasticsearch, Databricks, AWS, GCP, AWS CloudWatch Logs, AWS ECS Events, CloudFront Kinesis Integration, Cloudflare Workers Integration, Cloudflare Logpush Integration, Convex Log Streams, Vercel, Fastly, GitHub, GitHub Webhooks, Incident.io, Cursor, Pylon, WorkOS, Inspect, Linear, Clerk, Vanta, Tembo, Devin, Slack, SendGrid Event Webhooks, Google Postmaster Tools, Vector, Datadog Agent, DataDog Log Forwarding, GCP Cloud Monitoring, PromQL, PagerDuty, HTTP, Send Email via Webhook, MCP Servers, OpenAPI, gRPC, GraphQL, Web Search, AWS VPC Peering, Tailscale, SOCKS5 - **Deployment Options**: SaaS, BYOC: AWS, BYOC: GCP - **Account Management**: Google Workspace, API Keys - **API Reference**: Agents, Connections, Customers, Deployments, Investigations, Issues, Monitoring Plans, Coding Agents, Notes, Tags, Runbooks, Triggers, Agent SLOs, Auth, Notifications, Agent, Session, Connection, Customer, Deployment, Investigation, Issue, DeploymentEnvironment, Monitoring Plan, Note, Runbook, Tag, Trigger, Activity, Slack Handle, User Identity And Change Monitor Notifications, Role, SkillsBundle, Network Profiles, Slack Handles, Indicators, Flows, Objectives, Providers, Services, Billing, Network Transports, Autofix, Slack, Skills Bundles, Roles, Impact Report Notifications --- # Concepts Core concepts and terminology in Firetiger. ## Agents ## What are Agents? Firetiger Agents are autonomous LLM-driven workers that you can configure to manage your software systems. They are defined by a **plan**, run in response to **triggers**, and use a configured set of **tools**. You can see your agents at [/agents]({{ site.ui_url }}/agents). You can create as many Agents as you want. Treat agents as lightweight operators. It's common to make agents for ad-hoc tasks, not just persistent operational roles. ## What can Agents do? Agents can be granted access to [your telemetry](./telemetry.txt) and [connected tools](./integrations.txt). They run in an isolated sandbox with shell scripting and Python. By connecting a [HTTP Integration](../integrations/custom/http.txt), you can give them the ability to access specific addresses over the network, too. Typical uses of Agents include: - Writing a weekly analytics report - Investigating root causes of issues and posting updates on ticketing systems - Running playbooks of remediation commands in response to issues - Detecting and notifying code owners of issues - Running post-deploy tasks ## How to make an Agent Firetiger Agents are created conversationally in a planning session. To make one, go to [the Agent creation page]({{ site.ui_url }}/agents/new), and describe what you'd like the agent to do in high-level terms. The agent is initially in a draft state as you refine its goals and objectives. The planner that you're conversing with is able to use tools, and grant access to tools the actual realized agent instance it's creating with you. You can tell the planner to try executing the agent as much as you like to verify that it does what you expect, and then tell it to "enable" the agent to put it into live service. The planner will take care of all the details of configuring your agent, including writing a detailed prompt, setting up the triggers that control when it runs, and granting it access to only the tools it needs. Once it's ready, it'll summarize its results for you: You can always come back to the same planner by going to the 'plan' tab under your configured agent: ## When do Agents run? Agents are run in response to **Triggers**. There are several trigger types. ### Webhook Triggers All agents are automatically configured with a trigger ID that can be used to invoke them with a webhook. This invocation can include a message that can be passed to the agent which can have any text content. The generated trigger ID is visible on the Agent Details page: The webhook invocations need to be authenticated. See [API keys](../account-management/api_keys.txt) for details on getting an API key. For more details on running webhook triggers, see [Running Agents With Webhooks](../guides/agent-webhooks.txt). ### Scheduled Triggers Agents can be configured to run on a repeated schedule. This is expressed as a [`cron`](https://en.wikipedia.org/wiki/Cron) schedule. This is capable of expressing common intervals, like running every 15 minutes, every hour, or at the start of each week. ### Post-Deploy Triggers Agents can be configured to run after a particular bit of code is shipped in a [Deployment](./deployments.txt). Post-Deploy triggers require a [GitHub Integration](../integrations/developer-tools/github.txt). They are configured to watch for a particular Git commit (or any of its descendants), in a particular repo, for a particular environment. When that commit-repo-environment triplet is first seen, the trigger will fire, with an optional delay. This can be used to set up sophisticated post-deploy orchestration workflows, like poking a new API and watching the chain of logs in a real production service. ### Data Triggers Agents can be configured to fire whenever a new event matching a SQL predicate arrives in a Firetiger table. Instead of polling on a schedule, the agent wakes up the instant a matching signal is detected — making this the preferred trigger type for alerting and reactive workflows. You specify which table to watch and a predicate expression (for example, `severity = 'ERROR' AND body LIKE '%payment%'`). The predicate is validated against the table schema when the trigger is created, so mistyped column names are caught immediately. A cooldown setting prevents the agent from firing too frequently when many matching events arrive in quick succession. Data triggers are generally preferable to cron triggers for monitoring use cases because they react immediately rather than waiting for the next scheduled run. Use cron when the agent needs to synthesize or summarize data over a time window rather than react to individual events. ### Slack Triggers Agents can be configured to run from Slack. **Slack @mention** triggers fire when a custom [Slack Handle](../guides/agent-slack-handles.txt) is mentioned in an allowed channel. Use this when you want a handle such as `@checkout-oncall` to start one specific agent. **Slack channel message** triggers fire when messages are posted to selected Slack channels. Use this when the agent should monitor channel traffic without requiring an explicit mention. ## Viewing Agent runs You can see the individual executions of your agent under the Sessions tab: These sessions can finish in one of three ways: - "Done" sessions are ones that completed their mission successfully. Hooray! - "Issue Found" sessions are ones that detected and reported an operational issue. The agent still completed its job, but it created one or more [Issues](./issues.txt) scoped to that agent for tracking and triage. - "Aborted" sessions are ones that, for some reason, were unable to do their job. This could be a data access issue or broken connection, and these usually indicate that some human intervention is needed to fix up the agent that ran the session. In any case, you can click into a session to see the chain of actions and thoughts that the worker took, including any queries and tool uses. This can be helpful for observing what the agents are actually doing or debugging their behavior. ## Agent memory Each unique agent has its own "notebook" of memories. It can use this to remember things across execution runs. It will automatically use it to identify issues with queries, learn, and adapt to how your systems work. Sometimes it can be useful to encourage the agents to use their notebook more, particularly if you find they are repeatedly hitting an issue. Do this by chatting with the planner agent - tell it about the problem, and tell it to steer the executor agents to use their notebook more often. ## Services > **Services and Objectives are frozen.** They are no longer discovered, > evaluated, or changed, and their Experts no longer run. Everything you built > stays readable so you can review and export it — see > [Exporting your catalog](../api-reference/services.txt#exporting-your-catalog) > and [Exporting your Objectives](../api-reference/objectives.txt#exporting-your-objectives). > This page describes the model behind what you are reading. A **Service** is how Firetiger represents a component of your system — an API server, a worker, a checkout flow — and monitors whether it is healthy. Services are the system map Firetiger's [Agents](./agents.txt) reason about: who owns what, where a problem's blast radius is, and which parts of your system are involved in a given investigation. Firetiger measures a Service's health through a small chain of concepts that build on each other: - A **Service** is broken down along one or more **Dimensions** (environment, region, cloud, …). - An **Indicator** turns your [telemetry](./telemetry.txt) into a measurement over time. - An **Objective** sets a target on that measurement — a plain-English health promise. - Each combination of Dimension values is a **Service Cell**, evaluated independently. - The result of evaluating a Cell against its Objective is its **Cell Health**. The rest of this page walks through each concept and how they fit together. For the API and field-level detail, see the [Services](../api-reference/services.txt), [Indicators](../api-reference/indicators.txt), and [Objectives](../api-reference/objectives.txt) API references. ## What is a Service A Service is a conceptual software component that Firetiger observes and explains — for example, your `checkout` API or your `billing-worker`. It is the thing a developer recognizes and talks about ("the checkout service is slow"). A Service is deliberately *not* tied to a single running instance, container, or replica. The same Service is often deployed in many places — multiple environments, regions, or clouds — each with different failure boundaries. So the Service itself is not necessarily the unit whose health moves together: one environment can be perfectly healthy while another is degraded. Firetiger captures that nuance with Dimensions and Service Cells, below. Each Service carries a short human description and an agent-maintained deep-dive (`context`) that records what the Service does, where its telemetry lives, and what "normal" looks like — knowledge agents draw on when they investigate it. ## Dimensions A **Dimension** is an axis along which a Service's health is meaningfully broken down — its deployment shape. Common Dimensions are **environment** (`production`, `staging`), **region** (`us-west-2`, `eu-central-1`), **cloud**, or **cell**. Dimensions are catalog metadata: they describe *how* a Service splits up, so health can be reported per environment or per region instead of as a single blurry number for the whole Service. A Service can have several Dimensions at once — for example, both `environment` and `region` — and their combinations define the units Firetiger evaluates independently (see [Service Cells](#service-cells)). ## Indicators An **Indicator** is the measurement behind a Service's health: a query that turns your telemetry into a number over time — a latency, an error rate, a success ratio. It is what an Objective watches. An Indicator can bind to one or more of a Service's Dimensions. When it does, each row the Indicator returns carries its Dimension values (such as `environment=production`, `region=us-west-2`), and those values are what identify the Service Cells an Objective evaluates separately. An Indicator with no bound Dimensions produces a single, Service-wide measurement. ## Objectives An **Objective** is a health promise for a Service, expressed as a target over one backing Indicator. It pairs a plain-English statement of intent (for example, "checkout stays fast and reliable for our users") with a machine-checkable target, and Firetiger evaluates it continuously. There are two kinds of target: - **Ratio** — a share of events must stay good, for example "99.9% of requests succeed." - **Gauge** — a measured value must stay within a threshold, for example "p99 latency stays under 300 ms." An Objective is a single resource even when its backing Indicator is dimensional and it ends up evaluating many Service Cells. That matters for how problems are reported: one Objective produces one health story, with affected Cells called out as scope rather than as many separate Objectives. An Objective does not flip to unhealthy the instant a single data point grazes its target. Each Objective carries an alerting **trigger** that decides when a breach is serious enough to count: the measurement must stay past the target by a configurable **multiplier**, and stay there across both a long and a short evaluation window. A multiplier of 1× means "right at the target"; a higher multiplier adds margin so only meaningful, sustained breaches trip the alert. For example, a "99.9% success" Objective (a 0.1% failure target) with a 14.4× multiplier is flagged when the failure rate stays above ~1.44% — not at the first request over 0.1%. New Objectives also **calibrate** against your recent telemetry to suggest a confident target before they begin alerting. See the [Objectives API reference](../api-reference/objectives.txt) for trigger, multiplier, and calibration detail. ## Service Cells A **Service Cell** is one blast-radius unit of a Service: a concrete part of its deployment whose health can be monitored and measured independently. A Service Cell is identified by a set of Dimension values — for example `environment=production` **and** `region=us-west-2`. Each distinct combination the backing Indicator returns is its own Cell. When an Objective's Indicator binds Dimensions, Firetiger evaluates the Objective **once per Service Cell**. This is what lets Firetiger say "checkout is unhealthy in `us-west-2` production, but fine everywhere else" instead of collapsing every deployment into a single pass/fail. After first mention, these are usually just called **Cells**. A Cell is a health concept, not a chart artifact. It is distinct from a time-series "series" (how data may be drawn on a graph): a Cell is the unit Firetiger monitors and reasons about independently. ## Cell Health **Cell Health** is the result of evaluating one Service Cell against its Objective. Each Cell resolves to one of three states: | State | Meaning | | :--- | :--- | | **Healthy** | The Cell has not stayed past the Objective's trigger threshold across both windows — it can sit somewhat past the raw target and still be healthy until it crosses that threshold. | | **Unhealthy** | The Cell stayed past the Objective's trigger threshold — its target adjusted by the trigger multiplier — across both the long and short evaluation windows, so a brief blip doesn't flip it. | | **No data** | There isn't enough telemetry in the window to judge the Cell. | An Objective rolls its Cells up into an overall health: it is **unhealthy** if any evaluated Cell is unhealthy, **healthy** when Cells have data and none are unhealthy, and **no data** when nothing could be evaluated. In the Objective view, the per-Cell breakdown is shown as the **Cell health** panel. An **unhealthy Cell** describes the *affected scope* of a problem — which environment or region is hurting — inside a single Objective's health story. Unhealthy Cells are evidence an Agent uses to scope and explain an issue; they are not separate Objectives or separate alerts. ## How it fits together Putting the chain together with an example: > The checkout Service looks unhealthy. Does that mean every deployment of checkout is broken? Not necessarily. Health is evaluated per Service Cell whenever the Objective's Indicator binds Dimensions. If the Indicator returns `environment=production` and `region=us-west-2`, that combination is one Cell. If only that Cell stays past the Objective's trigger threshold, it becomes an unhealthy Cell — the affected scope — while the rest of checkout stays healthy. The Objective remains a single Objective; the unhealthy Cell is the blast radius and the evidence an Agent investigates from. That is the whole point of the model: a **Service** is the component you recognize, **Dimensions** describe how it is deployed, an **Indicator** measures it, an **Objective** sets the promise, and **Service Cells** with their **Cell Health** tell you exactly *where* that promise is or isn't being kept. To configure these resources via the API, see the [Services](../api-reference/services.txt), [Indicators](../api-reference/indicators.txt), and [Objectives](../api-reference/objectives.txt) references. ## Integrations ## What are Integrations? Integrations are how Firetiger connects to your existing infrastructure, tools, and data sources. They serve two purposes: they **extend the capabilities** of your [Agents](./agents.txt) by granting them access to external tools, and they **provide data sources** that feed telemetry into Firetiger for analysis. You can manage your integrations at [/integrations]({{ site.ui_url }}/integrations). ## Connections Connections are integrations that give Firetiger ongoing access to an external service. When you set up a connection, you provide credentials and configuration, and Firetiger maintains the link on your behalf. Connections are used in two ways: - **As agent tools** — Agents can use connections to interact with external systems. For example, a [GitHub](../integrations/developer-tools/github.txt) connection lets agents read repositories and create issues, while a [Slack](../integrations/communications/slack.txt) connection lets them post messages to channels. - **As data sources** — Some connections pull data into Firetiger on an ongoing basis. A [PostgreSQL](../integrations/databases/postgres.txt) connection, for instance, lets agents query your database directly. For a full list of available connections, see the [Integrations](../integrations/) section of the docs. ## Ingest Sources Ingest sources are integrations that push telemetry data — logs, traces, and metrics — into Firetiger. These are typically configured on the sending side, pointed at your Firetiger ingest endpoint. You can see the health and latency of your active ingest sources at [/integrations/ingest]({{ site.ui_url }}/integrations/ingest): This page shows which sources are actively sending data, their recent latency, and any delivery issues. It's a good first place to check if you suspect telemetry isn't arriving. For setup guides on specific ingest sources, see the [Infrastructure](../integrations/infrastructure/) and [Observability](../integrations/observability/) integration docs. ## Credential Security When you configure an integration, any secret values — API keys, database passwords, OAuth tokens — are handled with care. They are **never visible to LLMs** and are **never stored at rest** outside of encrypted cloud secrets managers. Here's how it works: when an agent runs, its sandboxed environment receives dummy placeholder credentials. An isolated proxy sits between the sandbox and the external service. The proxy intercepts outgoing requests and rewrites the dummy credentials with the real values, which only it can access. The agent's LLM never sees or handles actual secrets — it only ever works with the placeholders. This means that even if an agent's behavior were compromised, the real credentials remain inaccessible to the model. ## Providers A **Provider** is how Firetiger represents external infrastructure or a dependency that a customer's system relies on: a cloud account, database, data store, SaaS API, AI API, or similar system. Providers sit beside [Services](./services.txt). A Service is software the customer owns; a Provider is something that software depends on. Providers help Firetiger answer questions like: - Which Services depend on AWS, GCP, Postgres, or ClickHouse? - Do we have the right Connection to observe this dependency? - Are there Provider-owned Objectives that monitor the dependency directly? - When a Service is unhealthy, could an upstream Provider explain the scope? For field-level API detail, see the [Providers API reference](../api-reference/providers.txt). ## Providers and Services A **Service** is a customer-owned component such as `checkout-api` or `billing-worker`. A **Provider** is an external system those Services depend on, such as `aws`, `postgres`, or `openai`. The relationship is intentionally loose. Provider dependencies are recorded as architecture context, usually in the Provider's `context` field or in a Service's context, rather than as a hard schema edge. That lets agents capture the real architecture even when the dependency evidence comes from traces, configuration, logs, code, or operator notes. ## Provider types Each Provider may have a `provider_type`, such as `PROVIDER_TYPE_AWS`, `PROVIDER_TYPE_GCP`, `PROVIDER_TYPE_POSTGRES`, or `PROVIDER_TYPE_CLICKHOUSE`. The type drives UI branding and lets Firetiger recommend the Connection kinds needed to observe the Provider. Some Provider types are **connection-backed** today because Firetiger knows which Connection can measure them. Others are **context-only**: they are still recommended as useful architecture and investigation context, but Firetiger may not yet offer a direct monitoring path for them. ## Connections A **Connection** is the credentialed integration Firetiger uses to inspect data: cloud monitoring, databases, telemetry stores, APIs, MCP servers, and similar systems. Providers and Connections are related but not the same thing: - A Provider is the dependency being represented, such as "production Postgres". - A Connection is how Firetiger can query or call into something, such as a Postgres connection or a cloud monitoring integration. `GetProviderRecommendations` returns recommended Connection types for a Provider. It does not create or mutate Connections. ## Provider Objectives An **Objective** may be owned by a Provider by setting `owner_resource` to the Provider name, for example `providers/postgres`. Provider-owned Objectives are used when Firetiger monitors the dependency itself rather than one customer Service that uses it. Provider-owned Objectives follow the same Objective and Indicator model as Service-owned Objectives: - The Indicator queries telemetry or provider data. - The Objective sets the health promise. - The Objective's trigger decides when a sustained breach is serious enough to become unhealthy and drive an Investigation. Provider-owned monitoring is useful for dependency health signals such as database availability, connection saturation, replication lag, cloud API errors, or queue backlog. If the signal really describes a customer-owned component's user experience, model it as a Service Objective instead. ## Lifecycle Providers flow through the same recommendation lifecycle as Services: | State | Meaning | | :--- | :--- | | **Recommended** | An agent detected the Provider and is asking the customer to accept or dismiss it. | | **Active** | The Provider is accepted or user-created and participates in architecture reasoning. | | **Archived** | The Provider recommendation was dismissed or hidden. | Archived Providers are retained for history but skipped by active Provider surfaces and expert sweeps. ## Provider Expert Each active or recommended Provider may have a `provider-expert` session in its `expert_session` field. The Provider Expert is a long-lived Resource Expert for that one Provider. It answers questions from the Provider detail page and is woken periodically to refresh its understanding of dependencies, Connections, existing Provider-owned Objectives, and telemetry evidence. The session can be replaced over time; the Provider's `expert_session` points to the current session while older sessions remain in history. The Provider itself, not any one session transcript, is the durable source of truth. ## Modeling guidance Use a Provider when the concept is an external dependency. Use a Service when the concept is customer-owned software or a customer-owned workflow. When in doubt, ask: "Can the team deploy, roll back, or change this component directly?" If yes, it is usually a Service. If not, it is usually a Provider. Provider context should capture: - What the Provider is and what type it is. - Which Services or workflows depend on it. - Which Connections can observe it. - Where relevant telemetry lives. - Known gotchas, limits, maintenance windows, or failure modes. That context is useful to humans in the UI and to Firetiger agents during investigation and monitoring design. ## Telemetry Firetiger ingests and stores telemetry data from your software systems. This is the data that [Agents](./agents.txt) use to understand what's happening in your infrastructure. We use the [OpenTelemetry](https://opentelemetry.io/) standard for data ingestion. OpenTelemetry defines three signal types: - **Logs** — textual records of events, like HTTP access logs, application errors, or audit trails. - **Traces** — records of request execution across services, showing the path a request takes and where time is spent. - **Metrics** — numeric measurements over time, like request rates, error counts, or CPU usage. You can send data from any OpenTelemetry-compatible source. See [Sending OpenTelemetry Data](../guides/opentelemetry.txt) for setup instructions, or browse [Integrations](../integrations/) for source-specific guides. ## How data is organized When Firetiger receives telemetry, it organizes it by **service name** and **time**. The service name comes from the OpenTelemetry `service.name` resource attribute. This is the standard way to identify which component of your system produced a piece of telemetry. If no `service.name` is set, data goes into a `default` table. Each distinct service name gets its own table. For example, if you have services named `api-gateway`, `billing-worker`, and `web-frontend`, your logs will be stored in three separate tables. This keeps queries fast — when an Agent investigates an issue with your billing system, it only needs to scan the `billing-worker` table, not your entire log volume. Service names are normalized when they're received: `api-gateway` becomes `api_gateway`, `MyService` becomes `my_service`, and so on. ### Sub-table routing for events and metrics When log records include the `__type__` and `__name__` attributes, Firetiger routes them into dedicated sub-tables under the service's base table. This keeps high-volume, metric-like datapoints separate from regular logs and makes them faster to query. Recognized values for `__type__`: | `__type__` | Sub-table path | | :--- | :--- | | `event` | `opentelemetry/logs/{service.name}/events/{__name__}` | | `counter` | `opentelemetry/logs/{service.name}/counters/{__name__}` | | `gauge` | `opentelemetry/logs/{service.name}/gauges/{__name__}` | | `histogram` | `opentelemetry/logs/{service.name}/histograms/{__name__}` | | `metric` | `opentelemetry/logs/{service.name}/metrics/{__name__}` | Records with an unrecognized or missing `__type__`, or with a `__name__` that contains characters outside `[a-zA-Z0-9.\-_/]`, fall back to the base table. All sub-tables share the same schema as the base logs table. ## Structured logs The more structure your logs have, the more useful they are to Firetiger's agents. Plain text logs like `"user u_123 logged in"` work, but structured logs with typed fields are much better — they let agents write precise queries instead of parsing strings. A good structured log record uses OpenTelemetry attributes to capture discrete facts about an event. Here's what that looks like in practice: **An HTTP request log:** | Attribute | Value | | :--- | :--- | | `http.method` | `GET` | | `http.route` | `/api/v1/users` | | `http.status_code` | `200` | | `http.duration_ms` | `42` | | `user.id` | `u_123` | | `request.id` | `req_abc` | **A deployment event:** | Attribute | Value | | :--- | :--- | | `deploy.service` | `billing-worker` | | `deploy.sha` | `a1b2c3d` | | `deploy.environment` | `production` | | `deploy.trigger` | `merge` | **A background job completion:** | Attribute | Value | | :--- | :--- | | `job.name` | `sync_invoices` | | `job.duration_ms` | `12340` | | `job.status` | `success` | | `job.records_processed` | `847` | The key principle is: if you'd want to filter, group, or aggregate on a value, make it a separate attribute rather than embedding it in a message string. ### Sending structured logs with OpenTelemetry SDKs OpenTelemetry provides SDKs for most languages. Here are a few examples of emitting structured logs: **Python** ([opentelemetry-python](https://opentelemetry.io/docs/languages/python/)): ```python from opentelemetry._logs import SeverityNumber from opentelemetry.sdk._logs import LoggerProvider, LogRecord from opentelemetry.sdk._logs.export import BatchLogRecordProcessor from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter provider = LoggerProvider() provider.add_log_record_processor( BatchLogRecordProcessor(OTLPLogExporter(endpoint="https://ingest.example.com/v1/logs")) ) logger = provider.get_logger("my-service") logger.emit(LogRecord( severity_number=SeverityNumber.INFO, body="invoice sync completed", attributes={ "job.name": "sync_invoices", "job.duration_ms": 12340, "job.status": "success", "job.records_processed": 847, }, )) ``` **Go** ([opentelemetry-go](https://opentelemetry.io/docs/languages/go/)): ```go import "go.opentelemetry.io/otel/log" logger := loggerProvider.Logger("my-service") record := log.Record{} record.SetBody(log.StringValue("invoice sync completed")) record.SetSeverity(log.SeverityInfo) record.AddAttributes( log.String("job.name", "sync_invoices"), log.Int("job.duration_ms", 12340), log.String("job.status", "success"), log.Int("job.records_processed", 847), ) logger.Emit(ctx, record) ``` **Node.js** ([opentelemetry-js](https://opentelemetry.io/docs/languages/js/)): ```javascript import { logs, SeverityNumber } from "@opentelemetry/api-logs" const logger = logs.getLogger("my-service") logger.emit({ severityNumber: SeverityNumber.INFO, body: "invoice sync completed", attributes: { "job.name": "sync_invoices", "job.duration_ms": 12340, "job.status": "success", "job.records_processed": 847, }, }) ``` See [Sending OpenTelemetry Data](../guides/opentelemetry.txt) for full setup instructions including configuring exporters and the `service.name` resource attribute. ## Schema inference When Firetiger receives log data, it automatically infers and evolves the schema of your tables based on the data it sees. You don't need to define schemas up front. If your application emits logs with structured attributes (like the examples above), Firetiger will detect the types and create typed columns for each one. `job.duration_ms` becomes an integer column, `job.status` becomes a string column, and so on. As new fields appear in your data, the schema expands to accommodate them. This also works for JSON bodies. If your logs contain a JSON string as the body, Firetiger will detect and unpack it into typed columns automatically. Attribute names are normalized to `snake_case` during ingestion — `invocationId` becomes `invocation_id`, `InstanceID` becomes `instance_id`, etc. ## Querying your data The primary way to query telemetry in Firetiger is through [Agents](./agents.txt). When an agent investigates an issue, it writes and executes SQL queries against your telemetry tables on your behalf. You describe what you're looking for in natural language, and the agent figures out the right tables, columns, and filters. You can also query your data directly as an [MCP](https://modelcontextprotocol.io/) client. This is useful for integrating Firetiger into coding agents, AI assistants, or any tool that speaks the MCP protocol. See [Using Firetiger with MCP](../guides/mcp-server.txt) for setup instructions. ## Volume and pricing Firetiger is built to accept very high volumes of telemetry data. We don't charge based on cardinality — you won't be penalized for having many unique label values, high-dimensional attributes, or a large number of distinct services. Send what you need, and query what matters. ## Deployments Firetiger tracks deployment events from your CI/CD systems. A deployment represents a specific Git commit being shipped to a specific environment — for example, commit `abc123` going live in `production`. You can view your deployment history at [/deployments]({{ site.ui_url }}/deployments). This shows each deployment event with its repository, environment, Git SHA, and status. Deployments are a building block for several Firetiger features: - [Change Monitor](../guides/change-monitor.txt) — proactive, per-PR monitoring that activates when code goes live - [Post-deploy triggers](./agents.txt#post-deploy-triggers) — agent triggers that fire after a specific commit is deployed ## How deployments are registered There are two ways to register deployments with Firetiger. ### GitHub Deployments (automatic) If your CI/CD pipeline uses [GitHub Deployments](https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments), Firetiger picks up deployment events automatically through your [GitHub integration](../integrations/developer-tools/github.txt). No extra configuration is needed once the connection is installed. This auto-creation can be disabled per-connection if your GitHub Deployment events don't map to meaningful production deploys (e.g., if you use them for CI build gates or preview environments). ### Deployment API (explicit) For other CI/CD systems, or when you want explicit programmatic control, you can register deployments directly via the API: ```bash curl -X POST {{ site.api_url }}/deployments \ -u "$FT_DEPLOY_USERNAME:$FT_DEPLOY_PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "repository": "owner/repo", "environment": "production", "sha": "abc123def456...", "ref": "main", "deployer": "octocat", "deploy_time": "2025-01-15T10:00:00Z" }' ``` | Field | Required | Description | |---|---|---| | `repository` | yes | Repository in `owner/repo` format | | `environment` | yes | Deployment environment (e.g. `production`, `staging`) | | `sha` | yes | Git commit SHA that was deployed | | `ref` | no | Git branch or tag that was deployed | | `deployer` | no | User or service that triggered the deploy | | `deploy_time` | no | RFC 3339 timestamp; defaults to the time of the request | The optional `ref` and `deployer` fields mirror what Firetiger captures from GitHub deployments, so manually-registered deployments show the same branch and deployer context in the [Deployments page]({{ site.ui_url }}/deployments). Deploy credentials are available on the [Deployments page]({{ site.ui_url }}/deployments#create) under the **Create a deployment** tab. ## What happens after a deployment Once a deployment is registered, Firetiger checks it against any active monitoring plans and post-deploy triggers using **Git ancestry**. ### Git ancestry matching Firetiger doesn't require the exact commit from a PR to be deployed. Instead, it checks whether the deployed commit is a *descendant* of the commit it's watching. This matters because in practice, the commit that actually gets deployed is rarely the exact merge commit of a single PR — it's usually a later commit that includes several merged PRs. For example, suppose you set up Change Monitor on PR #42, which merges as commit `B`: ``` PR #42 │ A ── B ── C ── D ── E (main) │ │ merge of deployed PR #42 to prod ``` When commit `E` is deployed, Firetiger checks: is `B` an ancestor of `E`? It is — `E` contains all the changes from `B` — so the monitoring plan for PR #42 activates. This works regardless of how many other commits landed between `B` and `E`. ### Monitoring plans When a deployment's SHA is a descendant of a PR's merge commit, Firetiger marks that PR as deployed and begins running the monitoring plan that was set up for it. Checks run at 10 minutes, 1 hour, and 72 hours after deploy. See [Change Monitor](../guides/change-monitor.txt) for the full guide on setting this up. ### Post-deploy triggers Post-deploy triggers use the same ancestry logic. Each trigger watches for a specific commit SHA (or any descendant of it) landing in a particular repository and environment. When a match is found, the trigger fires the associated agent after a configurable delay. See [Post-deploy triggers](./agents.txt#post-deploy-triggers) for details. ## Environments Firetiger doesn't prescribe a fixed set of environment names. Whatever string you pass as `environment` — whether that's `production`, `staging`, `us-east-1`, or `canary` — is used as-is. This means you can model your deployment topology however it actually works, without mapping it into a predefined set of stages. When a monitoring plan is tracking multiple environments, it handles them in a single pass. A new deployment to any environment resets the monitoring schedule for the entire plan, since a fresh deploy is a new risk event worth checking against all environments. ## Issues Issues are recurring problems, errors, or system degradation that Firetiger agents have identified. Each issue is scoped to the [agent](./agents.txt) that discovered it, giving you a clear view of what each agent is observing across your systems. You can see your issues at [/issues]({{ site.ui_url }}/issues). ## How issues get created Issues are created by your Firetiger Agents. When an agent runs and discovers a problem according to its Plan, it will escalate that problem to be classified as follows: - If the problem **matches an existing Issue** for that agent, the existing issue's observation count is incremented and timestamps are updated --- no duplicate is created. - If the problem is **new**, a new Issue is created with an auto-assigned call sign (e.g. FT-1, FT-2) and an **Issue Expert Agent** that is responsible for triaging the Issue, and managing its lifecycle moving forward. - If the problem matches a **previously dismissed Issue**, the recurrence is suppressed. This means Issues are automatically deduplicated at the per-agent level. The same underlying problem reported across multiple agent runs gets consolidated into a single Issue to be triaged and fixed. ## Workflow states Each issue progresses through a defined lifecycle: | State | Meaning | | ----------------- | ----------------------------------------------------------------------------------------------- | | **Investigating** | Root cause analysis is in progress. This is the initial state. | | **Actionable** | The issue has been triaged and warrants attention. Root cause may or may not be identified yet. | | **Verifying Fix** | A fix has been applied and Firetiger is confirming whether it worked. | | **Closed** | Terminal state. See the closure reason for why. | Issues can move backward through the workflow when hypotheses are invalidated. For example, from Verifying Fix back to Investigating if a fix didn't work, or from Actionable back to Investigating if the initial root cause turns out to be incorrect. ## Issue Closure Issues can be closed by you, the user, or the Issue Expert Agent. When an issue is closed, you can record why: | Reason | Meaning | | ------------------ | ----------------------------------------------------------------------------------- | | **Resolved** | The issue was fixed and verified with evidence. | | **Accepted Risk** | A real issue, but intentionally dismissed or won't fix. | | **False Positive** | Not a real issue. | | **Duplicate** | Closed in favor of another canonical issue. | | **Not Useful** | The signal may be real, but the issue isn't actionable or relevant enough to track. | Closed issues aren't deleted: they remain queryable and visible. If a closed issue recurs, the Agent Issue Manager may choose to reopen it if there is material evidence to justify doing so. ## Observation tracking Each issue tracks how often it's been observed: - **First/last observation time** --- when the problem was first and most recently seen. - **Observation count** --- how many times agents have reported this problem. - **Source sessions** --- which agent sessions reported it. This gives you a clear picture of whether an issue is a one-off or a persistent pattern, without creating duplicate issue records. ## The Issues List The issues list at [/issues]({{ site.ui_url }}/issues) shows all your issues, filterable by workflow state. By default it shows actionable issues --- the ones that have been triaged and are ready for attention. ## Issue Details Clicking into an issue shows: - **Title** --- a concise description of the root cause. - **Description** --- impact-first summary of the problem - **Details** --- investigation evidence including code references, log lines, metric queries, and trace IDs. - **Tags** --- categorization labels. - **Links** --- external references like GitHub PRs, Linear tickets, or Slack threads. Additionally, you can chat with the Issue's Expert Agent via the chat panel. The Issue Expert owns that specific issue and can gather data, verify fixes, or check whether error patterns are still occurring. ## Fixing issues with coding agents The issue detail page carries a `Fix ▾` button that lists every coding-agent connection you've configured — [Cursor](../integrations/developer-tools/cursor.txt), [Devin](../integrations/developer-tools/devin.txt), [Tembo](../integrations/developer-tools/tembo.txt), Replicas, Coder{% if site.deployment_name == "ft-ramp" %}, and on this deployment [Inspect](../integrations/developer-tools/inspect.txt){% endif %}. Clicking an entry opens a new session on the provider, pre-seeded with the issue's description and investigation details. The session typically ends in a pull request you review like any other, and Change Monitor closes the loop when the PR merges. See [Fixing issues with coding agents](../guides/fixing-issues-with-coding-agents.txt) for the end-to-end walkthrough and the per-provider connection setup pages. ## Notifications By default, issues don't notify anyone. To set up notifications, go to [/issues/notifications]({{ site.ui_url }}/issues/notifications). The notifications page has a chat interface where you describe your notification policy conversationally. You tell it which [connections](../integrations/) should receive notifications --- for example, a Slack channel --- and the planner agent configures the routing rules. Notifications get sent when an Issue transitions to **Actionable** (a new problem needs attention) or **Closed** (a problem was resolved or dismissed). ## Knowledge Knowledge is Firetiger's accumulated understanding of your systems. As agents investigate, query, and interact with your infrastructure, they build up a structured body of knowledge that makes every subsequent interaction smarter. This knowledge is shared across all agents in your organization. You don't need to seed knowledge manually — it's built automatically from your telemetry. But you can supplement it with human-authored context via [Firetiger.md](#firetigermd). ## Services A service represents a component in your system — an API server, a background worker, a database, a message queue. You can browse your discovered services at [/knowledge/services]({{ site.ui_url }}/knowledge/services). Firetiger discovers services from your telemetry and maintains a profile of each one, including: - A description of what the service does and how it fits into your architecture - The telemetry patterns it emits — log attributes, metric names, trace spans - Correlation IDs used to track requests through or across the service (e.g., `request_id`, `trace_id`, `batch_id`) - Dependencies on other services - Known error patterns and expected anomalies (so agents don't raise false alarms on, say, expected timeout errors) - Tags for environment, region, version, and other metadata When an agent investigates an issue, it uses service definitions to understand the architecture around the problem — what services are involved, how they communicate, and what telemetry to look for. ## Flows A flow describes an end-to-end process that spans multiple services — something like "user authentication," "order processing," or "data ingestion pipeline." You can browse your flows at [/knowledge/flows]({{ site.ui_url }}/knowledge/flows). Where services describe individual components, flows describe how those components work together to accomplish something. Each flow contains: - A set of **key identifiers** — the correlation IDs that tie telemetry together across the flow (e.g., `user_id`, `order_id`, `session_id`) - A sequence of **steps**, each documenting which services are involved, what telemetry signals to expect, and how to query for the relevant data - **Operational notes** — deployment quirks, timing variations, seasonal behaviors, and other real-world context Flows are what let agents reason about cross-service behavior. When investigating a latency spike in your checkout process, an agent can look up the "checkout" flow to find the services involved, the correlation IDs to thread through, and example queries that have been useful in the past. Firetiger maintains a curated set of flows (typically a few dozen) and actively consolidates or removes stale ones to keep the set focused and useful. ## Customers If your system serves multiple customers or tenants, Firetiger can track them as first-class knowledge. You can browse your customers at [/knowledge/customers]({{ site.ui_url }}/knowledge/customers). A customer profile includes: - How to identify the customer in telemetry — which fields, which tables, what extraction patterns - A workload summary describing the customer's usage patterns and volume - An operational runbook with customer-specific investigation guidance This lets agents quickly scope queries to a specific customer when investigating issues, and understand whether a customer's behavior is normal for them. ## Notes Notes are Firetiger's general-purpose memory, organized by domain. You can browse them at [/knowledge/notes]({{ site.ui_url }}/knowledge/notes). They accumulate insights that don't fit neatly into services, flows, or customer profiles — things like common debugging techniques, system-wide operational patterns, or recurring themes across investigations. Notes are the primary way knowledge persists between agent sessions. When an agent discovers something useful during an investigation, it records the finding. Background agents periodically review these findings and incorporate the durable ones into the shared notes. ## Issues Issues are recurring problems that Firetiger agents have identified. Each issue is scoped to the agent that discovered it and progresses through workflow states: Investigating, Actionable, Verifying Fix, and Closed. Issues are automatically deduplicated --- when an agent reports a problem that matches an existing issue, the observation count is updated rather than creating a duplicate. When an agent detects a pattern matching an existing issue, it can reference it rather than re-diagnosing from scratch. See [Issues](./issues.txt) for more detail. ## How knowledge is built Firetiger runs a set of background researcher agents that periodically discover and maintain knowledge. These don't count against your agent usage. - A **service researcher** investigates telemetry to discover and document services, their dependencies, and their telemetry patterns - A **flow researcher** discovers end-to-end processes and maintains the curated set of flows - A **customer researcher** identifies top customers and their workload characteristics - A **synthesis agent** reviews all recent agent sessions to extract durable insights — user corrections, consensus patterns across multiple investigations, and repeated mistakes that indicate a knowledge gap Knowledge also grows organically during regular investigations. When an agent discovers something new about your systems, it records the finding. The synthesis agent picks it up on its next pass and incorporates it into the shared knowledge base. User corrections always take precedence over automatically discovered patterns. If you correct an agent during an investigation — "that's not an error, those timeout logs are expected" — that correction is preserved and propagated so no agent makes the same mistake again. ## Firetiger.md Firetiger.md is a human-authored markdown document where you can provide foundational context about your organization that doesn't need to be discovered from telemetry. Every agent reads it at the start of every session. This is a good place to put things like: - What your company does ("We're an authentication provider") - High-level architecture ("We run three main services: API, Worker, and Scheduler") - Business context ("Our peak traffic is during US business hours") - Naming conventions ("We call our internal customers 'tenants'") - Operational preferences ("Always check the audit log before escalating access issues") You can edit Firetiger.md at [/knowledge/firetiger]({{ site.ui_url }}/knowledge/firetiger). It's intentionally simple — just a markdown file — so it's easy to keep up to date. ## Knowledge and privacy Knowledge is scoped to your organization. It's never shared across organizations, and agents in one organization cannot see another organization's knowledge. Automatically discovered knowledge is derived solely from your own telemetry and agent interactions. Human-authored content in Firetiger.md and user corrections during investigations are marked as such and given priority over automatic discoveries, so they're never silently overwritten by an agent. --- # Guides Step-by-step guides for setting up and using Firetiger. ## OpenTelemetry Integration # How to Send OpenTelemetry to Firetiger This guide will walk you through configuring your application to export OpenTelemetry-compliant logs, metrics, and traces to Firetiger. ## Prerequisites Before you begin, ensure you have: - An active Firetiger deployment - An application that you want to monitor - OpenTelemetry instrumentation set up in your application ## Step 1: Get your Firetiger ingest credentials 1. Log in to your Firetiger account 2. Navigate to the **Integrations** page 3. Find your **Ingest Basic Auth Credentials** 4. **Important**: Copy these credentials securely - you'll need them to authenticate your telemetry exports Your credentials will include: - **Deployment Name**: Your unique Firetiger deployment identifier - **Password**: Your ingest authentication password - **Endpoint**: Your Firetiger OTLP endpoint (typically `{{ site.ingest_url | remove: "https://" }}:443`) You can find these credentials on the Firetiger Settings page: `{{ site.ui_url }}/settings` ## Step 2: Configure your OpenTelemetry collector or SDK Firetiger accepts OpenTelemetry data via the OTLP or HTTP protocol. You can send data directly from your application using OpenTelemetry SDKs, or route it through an OpenTelemetry Collector. ### Option A: Using the OpenTelemetry Collector Create or update your `config.yaml` file: ```yaml receivers: otlp: protocols: grpc: endpoint: "0.0.0.0:4317" http: endpoint: "0.0.0.0:4318" processors: batch: exporters: otlphttp/firetiger: endpoint: {{ site.ingest_url }}:443 tls: insecure: false headers: "Authorization": "Basic " service: pipelines: traces: receivers: [otlp] processors: [batch] exporters: [otlphttp/firetiger] logs: receivers: [otlp] processors: [batch] exporters: [otlphttp/firetiger] metrics: receivers: [otlp] processors: [batch] exporters: [otlphttp/firetiger] ``` **To create your Base64-encoded credentials:** ```bash echo -n "{{ site.deployment_name }}:{password}" | base64 ``` ### Option B: Direct from your application Configure your OpenTelemetry SDK to export directly to Firetiger: **Python example:** ```python from opentelemetry import trace from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor import base64 # Create base64-encoded credentials deployment_name = "{{ site.deployment_name }}" password = "{password}" auth = base64.b64encode(f"{% raw %}{deployment_name}:{password}{% endraw %}".encode()).decode() # Configure the exporter exporter = OTLPSpanExporter( endpoint="{{ site.ingest_url }}:443/v1/traces", headers={"Authorization": f"Basic {auth}"} ) # Set up the tracer provider = TracerProvider() provider.add_span_processor(BatchSpanProcessor(exporter)) trace.set_tracer_provider(provider) ``` **Node.js example:** ```jsx const { NodeSDK } = require("@opentelemetry/sdk-node"); const { OTLPTraceExporter, } = require("@opentelemetry/exporter-trace-otlp-http"); const deploymentName = "{{ site.deployment_name }}"; const password = "{password}"; const auth = Buffer.from(`{% raw %}${deploymentName}:${password}{% endraw %}`).toString("base64"); const traceExporter = new OTLPTraceExporter({ url: "{{ site.ingest_url }}:443/v1/traces", headers: { Authorization: `Basic ${auth}`, }, }); const sdk = new NodeSDK({ traceExporter, }); sdk.start(); ``` ## Step 3: Deploy and verify After configuring your telemetry export: 1. Deploy your updated application or collector configuration 2. Generate some traffic to your application 3. Log in to your Firetiger account and navigate to your traces or logs view 4. You should see telemetry data flowing in within a few moments ## Troubleshooting **No data appearing in Firetiger?** - Verify your credentials are correct (deployment name and password) - Check that your endpoint URL matches the format: `{{ site.ingest_url | remove: "https://" }}:443` - Ensure your Base64-encoded credentials are formatted correctly - Check your application or collector logs for connection errors - Verify that your application is generating telemetry data **Authentication errors?** - Double-check your credentials from the Integrations page - Ensure there are no extra spaces or characters in your Base64-encoded string - Verify the Authorization header format: `Basic ` ## Additional resources - [OpenTelemetry Documentation](https://opentelemetry.io/docs/) - [Configure the OpenTelemetry Collector](https://www.notion.so/Configure-the-OpenTelemetry-Collector-19070c7133b480d4a4fdc4c18960d2d3?pvs=21) For more help, contact Firetiger support or check our documentation. ## MCP Server # Firetiger MCP Server Firetiger exposes an MCP (Model Context Protocol) server that allows AI assistants like Claude, Cursor, and other MCP-compatible clients to interact with your Firetiger data. The server uses HTTP streaming transport. Interactive clients authenticate via OAuth; scripts can use an API key instead. See [Authentication](#authentication) below. **Quick start:** Visit your deployment's setup page at `/mcp` (e.g., `{{ site.ui_url }}/mcp`) for copy-pastable setup commands. ## Endpoint Your MCP endpoint is: ``` {{ site.api_url }}/mcp/v1 ``` ## Authentication ### OAuth (interactive clients) Default for Claude Desktop, Cursor, and Claude Code. On first connection the client runs the OAuth flow, you sign in with your Firetiger account, and it stores the bearer token — no header config required. ### API key (scripts) Create an API key at [`{{ site.ui_url }}/settings/api-keys`]({{ site.ui_url }}/settings/api-keys). Pick the **read-write** access level — `read-only` keys cannot reach `/mcp/v1`. Copy the `Authorization` header from the reveal dialog; the password is only shown once. ```bash export FT_AUTH='Basic ' curl -i -H "Authorization: $FT_AUTH" \ -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"}}}' \ {{ site.api_url }}/mcp/v1 ``` With an MCP client that supports custom headers: ```bash claude mcp add firetiger --transport http \ --header "Authorization: $FT_AUTH" \ {{ site.api_url }}/mcp/v1 ``` ## Client Configuration ### Claude Desktop Add to your Claude Desktop configuration file (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS): ```json {% raw %}{ "mcpServers": { "firetiger": { "url": "{% endraw %}{{ site.api_url }}/mcp/v1{% raw %}" } } }{% endraw %} ``` ### Cursor In Cursor settings, add the MCP server configuration: ```json {% raw %}{ "mcpServers": { "firetiger": { "url": "{% endraw %}{{ site.api_url }}/mcp/v1{% raw %}" } } }{% endraw %} ``` ### Claude Code Add the Firetiger MCP server using the CLI: ```bash claude mcp add firetiger --transport http {{ site.api_url }}/mcp/v1 ``` Then authenticate by running `/mcp` in Claude Code and following the prompts. ## Available Tools ### Resource Tools The MCP server exposes generic CRUD tools that work across all resource collections. Use `schema` to discover fields and types before creating or updating resources. | Tool | Description | |------|-------------| | `list` | List resources in a collection. Supports filtering, ordering, and pagination. | | `get` | Get full details of a specific resource by name. | | `schema` | Get the schema for a collection — field names, types, required/optional, enum values. | | `create` | Create a new resource in a collection. | | `update` | Partially update an existing resource. Only fields included in the request body are modified. | | `delete` | Soft-delete a resource by name. | ### Resource Collections | Collection | Operations | Description | |------------|-----------|-------------| | `issues` | List, Get, Create, Update, Delete | Track recurring problems for automatic correlation with future incidents | | `notes` | List, Get, Create, Update, Delete | Knowledge base entries and documentation | | `connections` | List, Get, Create, Update, Delete | External service connections (Postgres, HTTP, etc.) | | `customers` | List, Get, Create, Update, Delete | Customer records and metadata | | `investigations` | List, Get, Create, Update | Automated investigation runs | | `runbooks` | List, Get, Create, Update, Delete | Operational runbooks | | `triggers` | List, Get, Create, Update, Delete | Alert and automation triggers | | `scheduled-agent-runs` | List, Get, Create, Update, Delete | Scheduled recurring agent tasks | | `agents` | List, Get, Create, Update, Delete | Agent configurations | | `sessions` | List, Get, Create, Delete | Agent sessions (nested under agents) | | `issue-notification-policies` | List, Get, Update | Notification policy configuration (singleton) | ### Agent Tools | Tool | Description | |------|-------------| | `send_agent_message` | Send a message to an agent session and wait for a response. | | `read_agent_messages` | Read the message history from an agent session. | ### Query Tool | Tool | Description | |------|-------------| | `query` | Execute DuckDB SQL against Firetiger's Iceberg data warehouse. Supports standard SQL including JOINs, CTEs, and aggregate functions. | ### Credentials Tool | Tool | Description | |------|-------------| | `get_ingest_credentials` | Get OTLP ingest endpoint URL and authentication credentials. | ### Onboarding Tools These tools appear only when the corresponding integration hasn't been connected yet. Once you connect a provider, its onboarding tool is automatically hidden. | Tool | Description | |------|-------------| | `onboard_github` | Connect your GitHub account. Returns an authorization URL to open in your browser. | | `onboard_slack` | Connect your Slack workspace. Returns an authorization URL to open in your browser. | | `onboard_linear` | Connect your Linear workspace. Returns an authorization URL to open in your browser. | ## Example Workflows ### Investigate issues Ask Claude to query your logs and investigate problems: > "Figure out the reason the api server is crash looping in staging" Claude will query your logs, analyze error patterns, and identify the root cause. ### Fix an issue Ask Claude to work on an issue tracked in Firetiger: > "Get the issue 'memory leak in worker pool', verify it's still happening, find the root cause, and fix it" Claude will fetch the issue details, query recent logs and metrics to confirm the problem, analyze the patterns, and implement a fix. ### Create and manage resources Ask Claude to create or update Firetiger resources directly: > "Create an issue for the Redis connection timeouts we've been seeing under high load" Claude will call `schema` to learn the fields, then `create` to make the issue. > "Resolve the Redis timeout issue and add a note explaining the fix" Claude will `update` the issue status and `create` a note with the details. ### Set up telemetry integration The `integrate-firetiger` prompt provides a step-by-step guide for adding OpenTelemetry instrumentation to send data to Firetiger. It walks through fetching credentials, exploring your codebase, installing the SDK, and configuring the exporter. In Claude Code, invoke it with the slash command: ``` /firetiger:integrate-firetiger ``` Or ask Claude directly: > "Help me integrate OpenTelemetry into this project to send data to Firetiger" ## Related Documentation - [MCP Server Connections](../integrations/custom/mcp.txt) - Connect Firetiger to external MCP servers - [OpenTelemetry Integration](./opentelemetry.txt) - Send telemetry data to Firetiger ## Agent Webhooks # Running Agents With Webhooks > **Discontinued.** Custom agents can no longer be created and their triggers (including webhooks) no longer run. Existing agents and session history remain viewable. The guide below is retained for reference. Every Firetiger agent has a **manual trigger** with a stable **webhook URL**. Posting to that URL fires the trigger — the HTTP body and headers become the agent's opening message. This lets you kick off an agent run from a CI pipeline, an alerting system like PagerDuty or incident.io, a cron job, or any other tool that can make an HTTP request. No API key is required. The opaque token in the URL is the credential; treat it as a secret. ## Finding the Webhook URL Open the agent's detail page in the Firetiger UI and look at the **Triggers** section. You'll see a **Webhook URL** with a copy button — something like: ``` {{ site.api_url }}/webhooks/dGhpc0lzQVRlc3RUb2tlbjEyMzQ1Njc4OTA ``` Copy that URL; you'll use it directly in your HTTP request. ## Making the Request Invoking a webhook trigger is a single POST to the webhook URL. The body can be any format — Firetiger passes it through to the agent as-is. ```bash curl -X POST \ {{ site.api_url }}/webhooks/dGhpc0lzQVRlc3RUb2tlbjEyMzQ1Njc4OTA \ -H "Content-Type: application/json" \ -d '{ "event": "incident.triggered", "incident": {"id": "Q1A2B3C4", "title": "High error rate on checkout"} }' ``` The agent receives a structured message containing the timestamp, request headers, and body. A few headers are omitted (like `X-Forwarded-For`). The body is included as-is, truncated to 50 kB if larger. No `Content-Type` requirement — the body is passed through verbatim. Including `Content-Type` is recommended so the agent knows how to interpret the payload. ## Response A successful invocation returns the created session name: ```json { "session": "agents/uvxtwa1yoyrx/sessions/sess456" } ``` The `session` field is the name of the agent session that was started. You can use it to poll for results or read the agent's output via the agent sessions API. ## Using Multiple Sources With One Trigger You can multiplex multiple webhook sources to the same trigger by including a custom header (e.g., `X-Alert-Team: platform`). The agent receives all headers and can use them for routing or interpretation without requiring a separate trigger per source. ## Create a Custom Slack Handle for an Agent > **Discontinued.** Custom agents can no longer be created and their triggers (including Slack mentions) no longer run. The guide below is retained for reference. A custom Slack handle lets teammates start a specific Firetiger agent by mentioning a workspace handle such as `@checkout-oncall`. The handle is a Slack user group: Firetiger creates a new one if the handle is unused, or binds to an existing workspace group if the handle already exists. The trigger decides which agent responds and which channels are in scope. Use this when you want a memorable Slack mention for a purpose-built agent, instead of routing every Slack request through the default Firetiger mention. ## Prerequisites - A Firetiger agent. - A Slack connection with the Firetiger app installed. See [Slack](../integrations/communications/slack.txt). - The Slack connection must include `usergroups:read` to import existing handles. It also needs `usergroups:write` to create a new Slack user group when the handle is unused. If the connection was installed before custom handles were added, reconnect the Slack app from the connection page. - **A Slack workspace with user groups.** Custom handles are implemented as Slack [user groups](https://slack.com/help/articles/212906697-Create-and-edit-user-groups), which require **Slack Business+** or **Enterprise Grid**. To create a new handle from Firetiger, workspace policy must allow user-group management. To use an existing handle, the user group can be created and managed in Slack by an admin. - The Firetiger app must be a member of any channel you want to pick. If a channel is missing from the picker, invite the app to it first (in Slack, type `/invite @Firetiger` in the channel) — the picker lists only the channels the bot has been invited to. ### Slack workspace requirements Firetiger creates user groups on your behalf using the `usergroups:read` and `usergroups:write` scopes when the handle is unused. If your workspace blocks user-group management and the requested handle does not already exist, **handle creation will fail with**: > *Slack workspace blocks user group management* This is a workspace policy, not a Firetiger permission. To unblock: - Confirm your workspace is on **Slack Business+** or **Enterprise Grid** — user groups are not available on Free or Pro plans. - Ask a Slack workspace admin to create the user group in Slack, then enter that existing handle in Firetiger. - Alternatively, ask a Slack workspace admin to allow user-group creation. Slack admins can review the setting in [Manage user groups from the admin dashboard](https://slack.com/help/articles/115004952926-Manage-user-groups-from-the-admin-dashboard). After your admin creates the group or enables user-group management, you can retry adding the handle in Firetiger without reconnecting. ## Create the trigger 1. In Firetiger, go to **Agents** and open the agent that should respond to the Slack handle. 2. Open the agent's **Plan** page. 3. In the triggers area, click the **+** button to create a trigger. 4. Select **Slack @mention**. 5. Choose the **Slack connection** for the workspace where the handle should exist. 6. Under **SlackHandle**, select an existing Firetiger handle or click **Add**. 7. Enter the handle name, then click **Add**. If the Slack user group already exists, Firetiger imports it without changing its membership. If it does not exist and Slack allows Firetiger to create user groups, Firetiger creates it. Use the handle text without the leading `@`; Firetiger normalizes it to lowercase. Handles must be at least four characters. 8. Under **Channels**, either select the channels where this agent should respond, or leave the field empty to respond in any channel the Firetiger app is a member of. 9. Click **Create trigger**. After the trigger is created, mention the handle in Slack: ```text @checkout-oncall investigate the elevated checkout error rate ``` Firetiger starts a new session for the selected agent and includes the Slack mention context, including the channel, user, message text, and permalink. ## Channel scope Channel eligibility comes from the Firetiger app's **membership**: the bot only receives mentions in channels it has been invited to, so those are the channels eligible for triggers. There is no separate allowlist to configure — invite the app to a channel to make it eligible (`/invite @Firetiger`). The trigger's **Channels** field optionally narrows this specific agent to a subset of those channels. If it's empty, the agent can respond anywhere the app is a member of and receives matching mentions. If you select channels on the trigger, mentions in other channels are ignored. ## Handle behavior A `SlackHandle` is separate from the trigger that routes mentions. The handle reserves the Slack `@` name in the workspace, while the trigger points that handle at an agent. You can reuse an existing handle when creating another trigger. This is useful when the same Slack handle should route to another agent, or when you are recreating a trigger after changing channel scope. If the workspace already has a user group with the handle you enter, Firetiger binds to it instead of creating a duplicate. Firetiger does not change that group's membership. Deleting a `SlackHandle` removes the Firetiger record but leaves the underlying Slack user group in place — including its members. Other people in your workspace may rely on that group, so Firetiger never disables or deletes it on your behalf. If you want the group itself removed, a Slack workspace admin can do that from Slack. If a Slack message mentions both `@firetiger` and a custom handle, Firetiger runs only the custom handle trigger to avoid duplicate responses. ## Troubleshooting **No Slack connection appears.** Install the Firetiger Slack app from **Integrations** first. **The handle cannot be created because scopes are missing.** Reconnect the Slack app so Firetiger can request `usergroups:read` and `usergroups:write`. Existing handle import requires `usergroups:read`; creating a new Slack user group requires `usergroups:write`. **The Slack user group is disabled (`slack user group is disabled`).** A workspace user group with this handle exists but has been disabled. Firetiger does not automatically re-enable groups it doesn't own end-to-end — ask a Slack workspace admin to re-enable it, then retry handle creation. **A channel is missing from the picker.** The Firetiger app isn't a member of that channel yet. In Slack, invite it to the channel (`/invite @Firetiger`), then return to the agent's trigger form — the picker lists only channels the app belongs to. **Slack user groups are restricted (`Slack workspace blocks user group management`).** Your workspace plan or policy doesn't allow Firetiger to create user groups, and the handle you entered was not found. See [*Slack workspace requirements*](#slack-workspace-requirements) above — a Slack admin can create the group in Slack first, enable user-group creation, or upgrade the workspace to Business+ or Enterprise Grid. ## Related - [Agents](../concepts/agents.txt) - [Slack](../integrations/communications/slack.txt) - [Slack Handles API](../api-reference/slack-handles.txt) - [Triggers API](../api-reference/triggers.txt) ## BigQuery Integration # Query Firetiger with BigQuery > **Note:** Customers whose Firetiger deployment lives on GCP already have BigQuery configured. This guide is for Firetiger on AWS only. Firetiger Iceberg tables can be queried directly from BigQuery without ETL. This process requires frequent updates, as Google hasn't added support for external Iceberg REST catalog services. In our example, a typical workstation environment is assumed; production deployments will be different. ## Credentials Establish both AWS and GCP credentials. The `firetiger` command uses the common SDKs to do this. For GCP, run a command like `gcloud auth login --update-adc`. Learn more: [Set up Application Default Credentials](https://docs.cloud.google.com/docs/authentication/provide-credentials-adc). For AWS, we've run a command like `aws sso login`. Learn more: [Authentication and access using AWS SKDs and tools](https://docs.aws.amazon.com/sdkref/latest/guide/access.txt). ## GCP Configuration Use this information to configure the example env vars `GOOGLE_CLOUD_PROJECT`, `FT_BIGQUERY_LOCATION`, `FT_BIGQUERY_CONNECTION`. Identify the GCP project where BigQuery lives. Specifically, a "PROJECT_ID" found in the left column returned by `gcloud projects list`. Identify the BigQuery location, which is similar to a region. Specifically, a "region name" matching your Firetiger AWS region in [the list of "BigQuery Omni locations"](https://docs.cloud.google.com/bigquery/docs/locations#omni-loc). Create a BigQuery Connection in the GCP web console. Navigate to the [BigQuery Studio](https://console.cloud.google.com/bigquery). Under the "Explorer" tab (the icon looks like a compass), click "Connections", then "Create connection". - Connection type: "Vertex AI remote models..." - Connection ID: You decide, something like `firetiger` is fine. - Location type: "Multi-region" - Multi-region: Whichever option matches the BigQuery location from earlier. ## AWS Configuration Use this information to configure the example env vars `AWS_PROFILE`, `FT_CATALOG`, `FT_NAMESPACE`. Identify the AWS profile. Specifically, one of the alternatives returned by `aws configure list-profiles`. Identify the Iceberg catalog URI. If you don't know, then use `glue://`. Identify the Iceberg namespace where your Firetiger tables live. This is the Firetiger "deployment name" provided by Firetiger (e.g., `{{ site.deployment_name }}`). ## Other Configuration `FT_MAX_CONCURRENCY` limits table sync concurrency For example, `10` allows 10 tables to be synced concurrently. `FT_TIMEOUT` causes the process to exit after this duration. The value is a [Golang `time.Duration` strings](https://pkg.go.dev/time#ParseDuration), such as `30s` or `5m`. `OTEL_*_EXPORTER` are OpenTelemetry SDK environment variables. Learn more: [SDK Environment Variables](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#exporter-selection) `FT_LOG_LEVEL` is a [Golang `slog`](https://pkg.go.dev/log/slog) level, one of `ERROR`, `WARN`, `INFO`, `DEBUG`. ```bash docker run \ --rm \ --name firetiger-bigquery-sync \ -v $HOME/.config/gcloud/application_default_credentials.json:/root/.config/gcloud/application_default_credentials.json \ -v $HOME/.aws/:/root/.aws \ -e GOOGLE_CLOUD_PROJECT="$GOOGLE_CLOUD_PROJECT" \ -e FT_BIGQUERY_LOCATION="$FT_BIGQUERY_LOCATION" \ -e FT_BIGQUERY_CONNECTION="$FT_BIGQUERY_CONNECTION" \ -e AWS_PROFILE="$AWS_PROFILE" \ -e FT_CATALOG="$FT_CATALOG" \ -e FT_NAMESPACE="$FT_NAMESPACE" \ -e FT_MAX_CONCURRENCY=10 \ -e FT_TIMEOUT=1m \ -e OTEL_LOGS_EXPORTER=none \ -e OTEL_METRICS_EXPORTER=none \ -e OTEL_TRACES_EXPORTER=none \ -e FT_LOG_LEVEL="INFO" \ 975050257559.dkr.ecr.us-west-2.amazonaws.com/firetiger/firetiger \ gcp bigquery sync ``` ## Datadog Alerts # Triggering Firetiger from Datadog Alerts When a [Datadog monitor](https://docs.datadoghq.com/monitors/) alerts, you can have Firetiger automatically start working on it. Datadog posts the alert to a Firetiger HTTP endpoint, and Firetiger turns the alert into either an **investigation** or a **custom agent run** — depending on which endpoint you point the Datadog webhook at. Both use Datadog's built-in [Webhooks integration](https://docs.datadoghq.com/integrations/webhooks/), so they work with any monitor type (metric, log, APM, anomaly, etc.) and require no code on your side. ## Investigation vs. agent: which one do you want? These are two different things and they work differently. Most teams connecting Datadog alerts want **investigations**. | | **Fire an investigation** | **Fire a custom agent** | |---|---|---| | What it creates | A first-class **Investigation** — a scoped diagnostic run that turns your telemetry into an explanation and, when warranted, opens a durable [Issue](../concepts/issues.txt) | A plain agent **session** for an agent you configured | | Best for | "Something alerted — figure out what happened and why." Generic, no per-agent setup. | A purpose-built agent you've already set up with specific instructions, connections, or a runbook | | Endpoint | `POST …/InvestigationService/CreateInvestigation` (the API) | `POST {{ site.api_url }}/webhooks/{token}` (the agent's webhook) | | Auth | **API key** (HTTP Basic) | The **opaque token in the URL** — no API key | | Request body | Structured JSON: an `investigation` plus an `initial_activities` message that carries the prompt | Any payload — passed through to the agent verbatim | | Shows up in | The **Investigations** surface in the UI; can escalate to an Issue | The agent's page (Sessions) | If you're not sure, start with an **investigation** — it's the diagnostic primitive and needs no agent setup. The two paths are documented separately below; you can wire up either or both. --- ## Path A: Fire an investigation (recommended) An investigation is created through the Firetiger API. You pass the alert's details as an **initial activity** — a seed message — which is what the agent reads as its opening prompt, so it immediately starts diagnosing: pulling the relevant logs, correlating traces and metrics, and identifying what changed. (The `description` field is investigation metadata only — it does not reach the agent, so the prompt must go in `initial_activities`.) ### 1. Create an API key The investigation endpoint authenticates with an API key (HTTP Basic auth). In Firetiger, go to **Settings → API keys**, create a **Read-write** key (creating an investigation is a write), and copy the credentials. See [API Keys](../account-management/api_keys.txt) for details. The dialog gives you a username, a password, and a pre-computed `Authorization: Basic ` header — you'll paste that header into Datadog. ### 2. Create the webhook in Datadog 1. In Datadog, open the [Webhooks integration tile](https://app.datadoghq.com/integrations/webhooks) (**Integrations → Webhooks**) and click **New**. 2. Fill in: | Field | Value | |-------|-------| | **Name** | `firetiger-investigation` (becomes the `@webhook-firetiger-investigation` handle) | | **URL** | `{{ site.api_url }}/firetiger.investigations.v1.InvestigationService/CreateInvestigation` | 3. Under **Custom Headers**, add your API-key Basic auth header (Datadog takes headers as JSON): ```json { "Authorization": "Basic ", "Content-Type": "application/json" } ``` 4. Enable **Custom Payload** and provide the investigation body. Datadog substitutes its `$`-prefixed [template variables](https://docs.datadoghq.com/integrations/webhooks/#variables) before sending. The agent's prompt is the `content` string inside `initial_activities[].user.text`. You don't need to set `display_name` — when the seed message is present the server generates the investigation's title from it. ```json { "investigation": {}, "initial_activities": [ { "user": { "text": { "content": "A Datadog monitor alerted ($ALERT_TYPE). Investigate the root cause and affected scope.\n\nMonitor ID: $ALERT_ID\nStatus: $ALERT_STATUS\nHost: $HOSTNAME\nTags: $TAGS\nDatadog link: $LINK\n\nDetails:\n$EVENT_MSG", "role": "USER" } } } ] } ``` 5. Click **Save**. The richer the seed message, the faster the agent orients itself. Embedding `$LINK` lets it (and you) jump back to the Datadog event. ### 3. Reference the webhook in your monitor See [Wiring the webhook into a monitor](#wiring-the-webhook-into-a-monitor) below — add `@webhook-firetiger-investigation` to the monitor message. ### 4. What you get back `CreateInvestigation` returns the created investigation, including its name and status: ```json { "investigation": { "name": "investigations/inv_abc123", "displayName": "Elevated error rate in payments service", "status": "INVESTIGATION_STATUS_EXECUTING", "createTime": "2024-06-15T14:30:00Z" } } ``` The investigation shows up in the **Investigations** surface in the UI, where you can watch it run. You can also poll it programmatically with `GetInvestigation` — see the [Investigations API reference](../api-reference/investigations.txt). --- ## Path B: Fire a custom agent Use this when you've already built an agent for a specific job (with its own instructions, connections, or runbook) and want a Datadog alert to kick it off. Every Firetiger agent has a **manual trigger** with a stable webhook URL; POSTing to it starts a session with the alert payload as the opening message. See [Agent Webhooks](agent-webhooks.txt) for the full reference. ### 1. Get the agent's webhook URL Open the agent in Firetiger, look at the **Triggers** section, and copy the **Webhook URL**: ``` {{ site.api_url }}/webhooks/dGhpc0lzQVRlc3RUb2tlbjEyMzQ1Njc4OTA ``` The opaque token in the URL is the credential — no API key or `Authorization` header is required. Treat the URL as a secret. ### 2. Create the webhook in Datadog 1. In Datadog, open **Integrations → Webhooks → New**. 2. Fill in: | Field | Value | |-------|-------| | **Name** | `firetiger-agent` (becomes the `@webhook-firetiger-agent` handle) | | **URL** | The agent webhook URL you copied above | 3. Enable **Custom Payload**. The body is passed to the agent as-is, so include whatever context is useful: ```json { "alert_title": "$EVENT_TITLE", "alert_type": "$ALERT_TYPE", "alert_status": "$ALERT_STATUS", "monitor_id": "$ALERT_ID", "host": "$HOSTNAME", "tags": "$TAGS", "link": "$LINK", "body": "$EVENT_MSG", "message": "A Datadog monitor has alerted. Investigate the cause: check the relevant logs, traces, and metrics around the time of this alert and identify what changed." } ``` 4. Click **Save**. No `Authorization` header is needed — the URL token authenticates the request. ### 3. Reference the webhook in your monitor Add `@webhook-firetiger-agent` to the monitor message (see [below](#wiring-the-webhook-into-a-monitor)). ### 4. What you get back A successful invocation returns the created session name: ```json { "session": "agents/uvxtwa1yoyrx/sessions/sess456" } ``` The agent starts working immediately. It receives a structured message containing the timestamp, the request headers (a few, like `X-Forwarded-For`, are omitted), and the payload body (truncated to 50 kB if larger). You can watch the session on the agent's page in the UI. --- ## Wiring the webhook into a monitor This step is the same for both paths. A webhook only fires when a monitor's notification message mentions its handle. Edit the monitor (or create one) and add the handle to the **notification message**: ``` {% raw %}{{#is_alert}}{% endraw %} @webhook-firetiger-investigation Error rate on checkout exceeded threshold. {% raw %}{{/is_alert}}{% endraw %} ``` The handle (`@webhook-firetiger-investigation` or `@webhook-firetiger-agent`) matches the **Name** you gave the webhook. Wrapping it in a matched `{% raw %}{{#is_alert}}{% endraw %}` … `{% raw %}{{/is_alert}}{% endraw %}` block (and optionally a separate `{% raw %}{{#is_warning}}{% endraw %}` … `{% raw %}{{/is_warning}}{% endraw %}` block) means Firetiger is only invoked on the transitions you care about — not on recovery or no-data, unless you want that too. Datadog requires these conditional section tags to be opened and closed in matching pairs. Save the monitor. Use **Test Notifications** in the monitor editor to fire a sample alert and confirm an investigation (or session) shows up in Firetiger. ## Tips - **One webhook, many monitors.** A single handle can be referenced from any number of monitors. Include `$ALERT_ID` and `$TAGS` in the payload so the run can tell which monitor and service fired. - **Route alert classes differently.** Create separate webhooks (e.g. `firetiger-db` pointed at a DB-specialist agent) and reference whichever handle fits the monitor. - **Scope the work** by writing an explicit seed message — the `initial_activities` text (Path A) or the `message` field (Path B) — naming the service, the suspected subsystem, or a runbook to follow. - **Use `$TAGS[key]`** to pull a single tag value, e.g. `$TAGS[service]`. ## Related Documentation - [Investigations API reference](../api-reference/investigations.txt) — Full `CreateInvestigation` / `GetInvestigation` reference - [Agent Webhooks](agent-webhooks.txt) — Full reference for agent webhook trigger URLs - [API Keys](../account-management/api_keys.txt) — Create and use API keys for the investigation endpoint - [Triggering Agents from incident.io Workflows](incident-io-workflows.txt) — The agent-webhook pattern, driven by incident.io - [Forward Traces and Metrics from a Datadog Agent](../integrations/observability/datadog.txt) — Send the underlying telemetry to Firetiger so the run has data to investigate - [Datadog Webhooks integration](https://docs.datadoghq.com/integrations/webhooks/) — Datadog's reference for webhook payloads and variables ## incident.io Workflows # Triggering Agents from incident.io Workflows You can configure an incident.io workflow to call a Firetiger agent automatically when an incident is created (or at any other workflow trigger point). The agent receives details about the incident as its opening prompt, so it can immediately start investigating, pulling relevant logs, or running a runbook. ## Prerequisites You'll need the **webhook URL** for the agent you want to invoke. Open the agent in Firetiger, look at the **Triggers** section, and copy the **Webhook URL**. It'll look like `{{ site.api_url }}/webhooks/dGhpc0lzQVRlc3RUb2tlbjEyMzQ1Njc4OTA`. See [Agent Webhooks](agent-webhooks.txt) for more detail on how webhook invocation works. You'll also need a Firetiger API key to authenticate the request. See [API Keys](../account-management/api_keys.txt) for instructions on creating one. ## Creating the Workflow In incident.io, go to **Workflows** and create a new workflow. Set the trigger to **Incident created** (or whichever lifecycle event you want to fire on). Add a **Send a webhook** step and fill it in as follows. ### URL Paste the webhook URL you copied from the Firetiger UI: ``` {{ site.api_url }}/webhooks/dGhpc0lzQVRlc3RUb2tlbjEyMzQ1Njc4OTA ``` ### Method `POST` ### Headers | Key | Value | |-----|-------| | `Authorization` | `Bearer {your-api-key}` | ### Body The body is passed through to the agent as-is. Use incident.io's template syntax (`{% raw %}{{variable}}{% endraw %}`) to include incident details so the agent has context to work with. ```json { "event": "incident.created", "incident_id": "{% raw %}{{incident.id}}{% endraw %}", "incident_name": "{% raw %}{{incident.name}}{% endraw %}", "message": "Investigate this incident. Check for relevant errors, anomalies, or spikes in the data from around the time it was declared." } ``` The agent receives the full body alongside the request headers, so include whatever context is useful. The more detail you provide, the better the agent can orient itself. ## What Happens Next When the workflow fires, Firetiger creates a new agent session and returns a session ID. The agent starts working immediately on the webhook payload. You can see the session on the agent's page in the Firetiger UI, or poll the sessions API to read its output. ## Related Documentation - [Agent Webhooks](agent-webhooks.txt) — Full reference for webhook trigger URLs ## Change Monitor > **Discontinued.** Change monitors can no longer be created or run. Existing monitors remain viewable in the [Change Monitor]({{ site.ui_url }}/change-monitor) UI. The guide below is retained for reference. Change Monitor watches a PR through to production. When the PR merges and rolls out, an agent runs a plan tailored to that change — checking that the intended effect lands and that nothing else regresses — then posts updates on the PR and (optionally) DMs you on Slack. Use this when you want to ship faster without doing the manual "stare at dashboards for an hour after deploy" step. ## Prerequisites - Install the [GitHub Connection](../integrations/developer-tools/github.txt). - Register deployments — either via [GitHub Deployments](#registering-deployments) (automatic) or the [Deployments API](#registering-deployments-via-api) (for other CI/CD systems). ## Starting monitoring on a PR Pick whichever fits your workflow: - **GitHub comment** — write `@firetiger` on the PR. You can add extra context in the same comment (what to watch for, what to ignore); it's passed to the planning agent. - **Firetiger UI** — paste a PR URL into the [Change Monitor]({{ site.ui_url }}/change-monitor) and click **Start Monitoring**. - **MCP** — call `monitor_pr(pr_url=...)` from the [Firetiger MCP server](./mcp-server.txt). Pass `initial_message` to steer focus. - **Automatic** — flip on **Auto-Monitor Opened Pull Requests** on the GitHub connection. Every opened PR gets a monitoring plan; a natural-language filter lets you restrict to PRs that touch production-meaningful code. See [GitHub connection settings](../integrations/developer-tools/github.txt#connection-settings). You'll get a quick "👀" reaction confirming the trigger fired, then a PR comment with a link to the plan. Once planning finishes, the full plan is posted as another comment: ## What happens after merge When a deployment is registered whose SHA is a descendant of the PR's merge commit, Firetiger marks the PR as deployed and starts the plan. Checks run at **10 minutes, 1 hour, and 72 hours** after each deploy. If the monitoring plan is not ready at 10 minutes, the first check runs as soon as it is ready. Each check looks at both intended effects and regressions. If the same PR ships to another environment, the schedule resets for that environment. Plans expire 14 days after first deployment. You'll see updates on the PR — including when the intended effect is confirmed: ## Slack DM updates Change Monitor can DM you the same updates it posts on the PR — useful if you don't live in GitHub notifications. Each PR gets its own DM thread, so updates land as replies instead of spamming your inbox. **One-time setup** at [Change Monitor settings]({{ site.ui_url }}/change-monitor/settings): 1. Install the Firetiger Slack app in your workspace. 2. Link your GitHub and Slack identities. 3. Click **Send test DM** to verify delivery and enable notifications. After that, any PR you author gets a Change Monitor DM thread for plan publishes and status updates. You can toggle delivery off at the same settings page; the per-PR thread is reused if you re-enable later. ## When monitoring finds something If a check turns up a problem, Firetiger: - Posts a detailed comment on the PR (and a DM, if enabled) describing what's out of expectations. - Opens an FT-N issue in your Firetiger project, linked to the PR. Feeding that issue into Claude Code via the [MCP server](./mcp-server.txt) gives the agent the full investigation context to propose a fix. When the resulting PR merges and re-deploys cleanly, the issue closes automatically. - Optionally pings a Slack channel (configure in [Change Monitor settings]({{ site.ui_url }}/change-monitor/settings)). You can also browse everything in the UI: open the [Change Monitor list]({{ site.ui_url }}/change-monitor) for the plan, PR updates, deployment results, and per-environment status. ## Weekly Impact Reports Change Monitor results also feed [Impact Reports](impact-reports.txt): a weekly, per-engineer summary of what your shipped changes did in production — the lead win, the week's numbers, and anything that regressed — delivered as a Slack DM. On by default for every engineer with a change monitor, with a per-user opt-out in [settings]({{ site.ui_url }}/change-monitor/settings?tab=notifications). See the [Impact Reports guide](impact-reports.txt). ## Registering deployments If your CI/CD uses [GitHub Deployments](https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments), Firetiger picks up deployment events automatically via the GitHub Connection — no extra config. ### Registering deployments via API For other CI/CD systems, POST directly: ```bash curl -X POST {{ site.api_url }}/deployments \ -u "$FT_DEPLOY_USERNAME:$FT_DEPLOY_PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "repository": "owner/repo", "environment": "production", "sha": "abc123def456...", "deploy_time": "2024-01-15T10:00:00Z" }' ``` | Field | Required | Description | |---|---|---| | `repository` | yes | Repository in `owner/repo` format | | `environment` | yes | Deployment environment (e.g. `production`, `staging`) | | `sha` | yes | Git commit SHA that was deployed | | `deploy_time` | no | RFC 3339 timestamp; defaults to request time | **Auth:** deploy credentials (Basic auth) from the Firetiger UI on the [Deployments page]({{ site.ui_url }}/deployments/create) under **Create a deployment**. **Response:** `200 OK` with the created deployment's resource name: ```json {"name": "deployments/abc123"} ``` > For a step-by-step CI/CD example, see the [GCP Cloud Build guide](gcp-cloud-build.txt). ## Fixing issues with coding agents Firetiger can hand off an [issue](../concepts/issues.txt) directly to an external coding agent — Cursor, Devin, Tembo, Replicas, Coder, or any other provider you've configured — with a single click from the issue detail page. The agent reads the issue's description and investigation details, runs a session against your repository, and typically opens a pull request you can review like any other. This guide covers the end-to-end flow: from configuring a connection to the issue closing after the fix lands. ## Prerequisites - A configured coding-agent connection. Today, Firetiger supports: - [Cursor](../integrations/developer-tools/cursor.txt) - [Devin](../integrations/developer-tools/devin.txt) - [Tembo](../integrations/developer-tools/tembo.txt) - [Replicas](https://tryreplicas.com) — sandboxed background agents; connect with a Replicas API key and environment ID - [Coder](https://coder.com) — self-hosted Coder Tasks; connect with your deployment URL, session token, and template version{% if site.deployment_name == "ft-ramp" %} - [Inspect](../integrations/developer-tools/inspect.txt) (Ramp-internal){% endif %} All of them are added the same way: **Integrations → Connections → + Connection**, under the **Coding Agents** category. You can configure more than one connection per provider (for example, one Cursor key per team) — each shows up as a distinct entry in the `Fix ▾` dropdown. - Optionally, a [GitHub connection](../integrations/developer-tools/github.txt) on the same repository the agent will push to. Firetiger doesn't manage the agent's PR directly, but having the GitHub connection means webhook events (PR opened, merged, deploy-monitored) close the loop automatically — see *[What happens next](#what-happens-next)* below. ## The `Fix ▾` dropdown On any issue detail page (e.g. `{{ site.ui_url }}/issues/FT-42`) the header carries a `Fix ▾` button. Clicking it opens a dropdown that lists every coding-agent connection you've configured, plus a trailing **+ Coding Agent** row that deep-links to the connection setup flow. Pick an agent to launch a new session. Firetiger opens a brief transition page while it calls the provider: Once the provider accepts the request, the page redirects to the agent's own session UI in the same tab — `cursor.com/agents/...` for Cursor, `app.devin.ai/sessions/...` for Devin, `app.tembo.io/tasks/...` for Tembo, and so on for the other providers. The same `Fix ▾` control is also available on issue cards in list views (Issues list, Home, Agent views), so you can launch a session without drilling into the issue first. ## What happens next A launched session is an independent artifact on the provider. Firetiger records the session URL on the originating issue as a link so you can jump back to the agent's log later, but it does not drive the agent's behavior beyond the initial context handoff. From here the flow is: 1. **Agent runs.** The session works through the issue on the provider's infrastructure, typically opening a pull request against your repo when it has a candidate fix. 2. **PR merges.** You review and merge the PR in GitHub. If you have [Change Monitor](change-monitor.txt) wired up, a monitoring plan is created automatically and the issue transitions to **Verifying Fix**. Firetiger ties the PR to the issue when the PR description names it with a GitHub-style closing keyword — `Fixes FT-42`, `Closes FT-42`, or `Resolves FT-42` (any of `fix`/`close`/`resolve` and their `-s`/`-d` forms, case-insensitive; the issue may also be a markdown link or a full issue URL). A bare mention like `Related to FT-42` does **not** count as a fix. Coding-agent sessions launched from `Fix ▾` add this line for you, but you can write or edit it by hand on any PR. Firetiger re-reads the link every time the PR description changes and reconciles it against deployments that already happened — so adding `Fixes FT-42` to an already-merged, already-deployed PR still moves the issue to **Verifying Fix**. 3. **Fix verified or rejected.** If the monitoring plan confirms the underlying problem stopped recurring, the [Issue Expert Agent](../concepts/issues.txt#issue-details) closes the issue with `Resolved`. If the problem recurs, the issue moves back to **Actionable** for another attempt. Firetiger does not poll the provider for session state — session lifecycle is owned by the agent. The issue-page session link simply lets you jump back to the agent's own UI on demand. ## Multiple attempts and multiple providers Nothing prevents you from launching more than one session on the same issue. Common patterns: - **Two providers in parallel.** Launch Cursor and Devin on the same issue and see which opens the more convincing PR. - **Retry with a different key.** If a session stalls, launch a fresh one from `Fix ▾` — the new session is independent. - **Reviewer hand-off.** After a session opens a PR, a reviewer can launch their own session to iterate on the PR comments. Each launch records a new session link on the issue without overwriting the previous ones. ## Related - [Issues](../concepts/issues.txt) — issue lifecycle and the states a Fix can drive - [Cursor](../integrations/developer-tools/cursor.txt) — Cursor connection setup - [Devin](../integrations/developer-tools/devin.txt) — Devin connection setup - [Tembo](../integrations/developer-tools/tembo.txt) — Tembo connection setup{% if site.deployment_name == "ft-ramp" %} - [Inspect](../integrations/developer-tools/inspect.txt) — Inspect connection setup{% endif %} - [Change Monitor](change-monitor.txt) — how PR-based monitoring closes the Verifying-Fix loop - [Coding Agents API](../api-reference/coding-agents.txt) — programmatic access to the same launch / list / get endpoints ## Impact Reports An Impact Report is a weekly, personal account of what your shipped work did in production, harvested from your [Change Monitor](change-monitor.txt) results. Each week Firetiger reviews the changes you shipped — what landed safely, what's still being watched, what surfaced an issue — leads with the most meaningful win, and delivers the result to you as a Slack DM. Use this when you want a lightweight record of production impact — for yourself, for standups, or for the "what did I actually ship?" question — without assembling it by hand. ## Who gets one Impact Reports are **on by default for every engineer with a change monitor**. Concretely, a report is generated for you when: - you authored a PR that Change Monitor watched through to production (the PR merged and its monitoring plan deployed), and - your GitHub identity is linked to your Firetiger account — that's how Firetiger matches PRs to you. Link it at [Change Monitor settings]({{ site.ui_url }}/change-monitor/settings?tab=notifications). There's no setup beyond [Change Monitor](change-monitor.txt) itself: if you shipped monitored changes this week, you get a report. ## What's in a report - **The lead win** — the change with the clearest production impact, with a before/after one-liner. - **The week's numbers** — changes monitored, landed safely, still being watched, and issues detected. - **Supporting wins** beyond the lead. - A link to the full report in Firetiger. Reports are composed by an agent from your actual Change Monitor results — deployment checks, confirmed effects, and detected issues — not from commit counts or diff sizes. ## Where to find them - **Slack DM** — generated every Friday covering the past week; the DM lands once the report is ready, provided your Change Monitor Slack DMs are verified. - **Firetiger UI** — the [Impact Reports tab]({{ site.ui_url }}/impact-reports) under Change Monitor lists every report generated for you. ## Turning the Slack DMs off The weekly report DM is opt-out, per user: - Open [Change Monitor settings → Notifications]({{ site.ui_url }}/change-monitor/settings?tab=notifications) and switch **Send me a weekly Impact Report by Slack DM** off. Only affects your account. Opting out stops Firetiger delivering your weekly report; it is still generated and visible in the Impact Reports tab. Switch delivery back on any time; DMs resume the following Friday. To opt out programmatically, set `slack_enabled: false` via [UpdateMyImpactReportNotificationPreference](../api-reference/impact-report-notifications.txt#updatemyimpactreportnotificationpreference). > Opting out only affects your weekly Impact Report delivery. It doesn't change Change Monitor coverage of your PRs or your Change Monitor Slack DM notification settings. ## Connect to a Private Database with Tailscale # Connect Firetiger to Your Private Network Using Tailscale This guide walks through connecting Firetiger to an AWS RDS PostgreSQL database that is not publicly accessible, using Tailscale as the network transport. By the end, Firetiger agents will be able to query your private RDS instance through a secure Tailscale tunnel. ## Prerequisites - A [Tailscale](https://tailscale.com) account with admin access - Your RDS database must be reachable from your tailnet (via a [subnet router](https://tailscale.com/kb/1019/subnets) in the same VPC) ## Step 1: Configure Tailscale ACLs Open the [Access Controls](https://login.tailscale.com/admin/acls) page in the Tailscale admin console. Add a `tag:firetiger` tag and grant it access to your database port: ```json "tagOwners": { "tag:firetiger": ["autogroup:admin"] }, "grants": [ { "src": ["tag:firetiger"], "dst": ["*"], "ip": ["5432"] } ] ``` > Restrict `dst` to specific machines or tags for tighter security (e.g., `["tag:databases"]` instead of `["*"]`). ## Step 2: Create a Tailscale OAuth Client 1. Go to [Settings > OAuth clients](https://login.tailscale.com/admin/settings/oauth) 2. Click **Generate OAuth client** 3. Set the description to something like `firetiger` 4. Under **Tags**, select `tag:firetiger` 5. Under **Scopes**, ensure `auth_keys` Write is included (this allows the client to generate auth keys with the selected tags) 6. Click **Generate** 7. Copy the **Client ID** and **Client Secret** > The client secret is only shown once. Save it securely before closing the dialog. > The OAuth client must have the `tag:firetiger` tag selected. Without it, the proxy cannot generate tagged auth keys and will fail with "requested tags are invalid or not permitted". ## Step 3: Find Your Tailnet Name You'll need your tailnet name for the next step. Find it at [Settings > General](https://login.tailscale.com/admin/settings/general), or run: ```bash tailscale status --json | jq -r .MagicDNSSuffix ``` It looks like `example.ts.net` or `tail1234.ts.net`. ## Step 4: Create the Network Transport 1. Navigate to <{{ site.ui_url }}/integrations/network-transports> 2. Click **Create Network Transport** > **Tailscale** 3. Enter a display name (e.g., "Tailscale") 4. Enter your Tailscale OAuth Client ID and Client Secret from Step 2 5. Enter your tailnet name from Step 3 6. Optionally set a hostname for the proxy node (default: auto-generated) 7. Click **Create Network Transport** ## Step 5: Create the Database Connection 1. Navigate to <{{ site.ui_url }}/integrations/connections/new> 2. Select **PostgreSQL** 3. Enter the connection details: - **Host**: Use the **private IP or DNS name** of your RDS instance (the one reachable from within the VPC, not a public endpoint — e.g., `mydb.abc123.us-east-1.rds.amazonaws.com`) - **Port**: `5432` - **Database**: your database name - **Username / Password**: your database credentials - **SSL Mode**: `require` - **Read Only**: enabled (recommended for production) 4. Under **Network Transport**, select your Tailscale network transport 5. Click **Save** ## Step 6: Test the Connection On the connection page, click **Save + Test**. If successful, agents can now query your private RDS database through the Tailscale tunnel. ## Troubleshooting | Error | Cause | Fix | | --------------------------------------------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------- | | "requested tags are invalid or not permitted" | OAuth client doesn't have `tag:firetiger` | Recreate the OAuth client with the tag selected | | "tailnet not found" | Wrong tailnet name | Check `tailscale status --json \| jq -r .MagicDNSSuffix` | | "tailnet-owned auth key must have tags set" | Network transport missing `tags` field | Update the transport to include `"tags": ["tag:firetiger"]` | | Connection times out | Database not reachable from tailnet | Verify your subnet router is running and the RDS security group allows traffic from the subnet router | ## How It Works ``` Agent ──► Firetiger Proxy ──► Tailscale Tunnel ──► Subnet Router ──► RDS (ephemeral (in your node with VPC) tag:firetiger) ``` The proxy joins your tailnet as an ephemeral node, dials the RDS endpoint through the Tailscale mesh network, and forwards the database traffic. The node is automatically removed when the connection closes. ## Related Documentation - [Network Transports](/integrations/networking/network-transports.txt) — Overview of network transports - [Tailscale Transport Reference](/integrations/networking/tailscale.txt) — Full configuration reference - [PostgreSQL Connections](/integrations/databases/postgres.txt) — PostgreSQL connection parameters - [AWS VPC Peering](/integrations/networking/aws-vpc-peering.txt) — Alternative: direct VPC peering for BYOC deployments ## GCP Cloud Build Register your GCP Cloud Build deployments with Firetiger so that [Change Monitor](change-monitor.txt) can track changes, correlate them with observability data, and catch deployment-specific issues. ## Prerequisites - A Firetiger account with the [GitHub Connection](../integrations/developer-tools/github.txt) installed - A GCP project with Cloud Build enabled - Deploy credentials from the [Deployments page]({{ site.ui_url }}/deployments/create) — copy the **Authorization header** value (a `Basic` token encoding your username and password) ## Store the credential in Secret Manager Create a secret containing the authorization header value from the Deployments page: ```bash echo -n "Basic WTNKb..." | gcloud secrets create firetiger-deploy-token \ --data-file=- \ --replication-policy=automatic ``` Grant your Cloud Build service account access to the secret. Replace `` with the service account your builds run as — this is the [legacy Cloud Build service account](https://cloud.google.com/build/docs/cloud-build-service-account), the Compute Engine default SA, or a [user-specified service account](https://cloud.google.com/build/docs/securing-builds/configure-user-specified-service-accounts) depending on your project's configuration: ```bash gcloud secrets add-iam-policy-binding firetiger-deploy-token \ --member="serviceAccount:" \ --role="roles/secretmanager.secretAccessor" ``` ## Add a registration step to `cloudbuild.yaml` Add the following step to the end of your `cloudbuild.yaml`, after your deploy step: ```yaml steps: # ... your existing build and deploy steps ... - id: register-firetiger-deployment name: curlimages/curl entrypoint: sh args: - -c - | curl -sf -X POST {{ site.api_url }}/deployments \ -H "Authorization: $$FT_DEPLOY_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "repository": "$_REPOSITORY", "environment": "$_ENVIRONMENT", "sha": "$COMMIT_SHA" }' secretEnv: - FT_DEPLOY_TOKEN availableSecrets: secretManager: - versionName: projects/$PROJECT_ID/secrets/firetiger-deploy-token/versions/latest env: FT_DEPLOY_TOKEN substitutions: _REPOSITORY: "your-org/your-repo" # owner/repo format _ENVIRONMENT: "production" ``` > **Failed deploys:** Because the registration step runs after your deploy step, Cloud Build will skip it if the deploy fails. This means only successful deployments are registered with Firetiger. > **Variable escaping:** `$$FT_DEPLOY_TOKEN` uses a double `$$` because it references a `secretEnv` variable — Cloud Build requires the double-dollar prefix to distinguish secret environment variables from built-in substitutions like `$COMMIT_SHA`. This is a common gotcha. > **Repository format:** The `repository` field requires `owner/repo` format (e.g. `acme-corp/api-server`). Cloud Build's built-in `$REPO_NAME` only provides the repo name without the owner, so the example uses a user-defined `_REPOSITORY` substitution instead. ## Verify 1. Trigger a build and check the **register-firetiger-deployment** step in Cloud Build logs — you should see a `200` response with a JSON body like `{"name": "deployments/..."}`. 2. Confirm the deployment appears on the [Deployments page]({{ site.ui_url }}/deployments) in the Firetiger UI. ## Next steps - [Start Change Monitor on a PR](change-monitor.txt#starting-monitoring-on-a-pr) so Firetiger develops a targeted plan for your changes - Review the [Change Monitor guide](change-monitor.txt) for the full workflow: PR tagging, monitoring plans, and the check schedule ## Investigate GCP Error Reporting # Triggering Investigations from GCP Error Reporting You can wire [GCP Error Reporting](https://cloud.google.com/error-reporting) notifications to a Firetiger agent so that a new investigation starts automatically the moment an error group fires. Error Reporting POSTs the error details — exception type and message, the affected service and version, the request that triggered it, and a link back to the Error Reporting console — and the agent receives all of it as its opening prompt, ready to pull relevant logs and traces from around the time of the error. This works because every Firetiger agent exposes a stable **webhook URL**, and GCP can deliver Error Reporting notifications to any webhook endpoint. ## Prerequisites You'll need the **webhook URL** for the agent you want to invoke. Open the agent in Firetiger, look at the **Triggers** section, and copy the **Webhook URL**. It'll look like `{{ site.api_url }}/webhooks/dGhpc0lzQVRlc3RUb2tlbjEyMzQ1Njc4OTA`. See [Agent Webhooks](agent-webhooks.txt) for more detail on how webhook invocation works. No API key is required — the opaque token in the URL is the credential, so treat the URL as a secret. ## Create a webhook notification channel GCP delivers Error Reporting notifications through a Cloud Monitoring **notification channel**, so you create the webhook channel there first. 1. In the Google Cloud console, go to **Monitoring → Alerting**. 2. Click **Edit notification channels**. 3. In the **Webhook** section, click **Add new**. 4. Paste the Firetiger webhook URL as the **Endpoint URL**. Leave authentication set to **None** — the token in the URL is the credential. 5. Click **Test Connection** to confirm GCP can reach the endpoint, then **Save**. > **Test Connection fires the trigger.** GCP's **Test Connection** sends a real POST to the URL, which starts an agent session. That's expected — you can ignore or delete the resulting test session in the Firetiger UI. ## Attach the channel to Error Reporting 1. Go to the **Error Reporting** page in the Google Cloud console. 2. Click **Configure notifications**. 3. In the **Notification channels** drop-down, select the webhook channel you just created. 4. Click **Save**. Error Reporting will now POST to your Firetiger webhook whenever it detects a new error group (or a resolved error reoccurring). ## What the agent receives When a notification fires, GCP sends a JSON body that Firetiger passes through to the agent verbatim, alongside the request headers. The payload includes: | Field | Description | |-------|-------------| | `subject` | A short description of the error group | | `group_info.project_id` | The GCP project where the error occurred | | `group_info.detail_link` | A link to the error group in the Error Reporting console | | `exception_info.type` | The exception class or error type | | `exception_info.message` | The error message | | `event_info.service` / `event_info.version` | The reporting service and version | | `event_info.request_method` / `request_url` / `response_status` | The request that triggered the error | | `event_info.log_message`, `referrer`, `user_agent` | Additional request context | That's enough for the agent to orient itself and begin correlating the error with logs, traces, and metrics around the time it occurred. ## What happens next When the webhook fires, Firetiger creates a new agent session and the agent starts working immediately on the Error Reporting payload. You can watch the session on the agent's page in the Firetiger UI, or poll the sessions API to read its output. ## Related documentation - [Agent Webhooks](agent-webhooks.txt) — Full reference for webhook trigger URLs - [GCP Cloud Monitoring](../integrations/observability/gcp-cloud-monitoring.txt) — Give the agent live access to Cloud Monitoring metrics so it can correlate errors with infrastructure signals ## Custom Agent Skills Firetiger's agents ship with a built-in library of skills — focused, reusable instructions an agent reads on demand (how to query a table, how to write up an issue). With a **Skills Bundle** you add your own: point Firetiger at a directory of [agentskills.io](https://agentskills.io)-format skills in one of your GitHub repositories, and every agent in your organization can use them. Use this when you have org-specific runbooks, conventions, or procedures you want your agents to follow — your deploy process, your escalation policy, the quirks of a particular service — authored as version-controlled Markdown rather than pasted into prompts. ## How skills are structured A skill is a directory containing a `SKILL.md`: YAML frontmatter (a `name` and a one-line `description`) followed by the instructions. Your repository holds one directory of these, with each immediate subdirectory being a single skill: ``` skills/ deploy-production/ SKILL.md rotate-credentials/ SKILL.md scripts/ rotate.sh ``` ```markdown --- name: deploy-production description: Run a production deployment and verify the rollout --- # Deploy to production 1. Confirm the release PR has merged to `main`. 2. Trigger the deploy workflow… ``` The directory defaults to `skills/`, but you can point at any directory in the repo. Files under a skill's `scripts/`, `references/`, and `assets/` subdirectories are mounted alongside it; other files are ignored. > Skills are agent-agnostic instructions — they describe *how to do a thing*, not which agent should run. Keep Firetiger-specific routing out of `SKILL.md`. ## Prerequisites - A [GitHub Connection](../integrations/developer-tools/github.txt) whose installation can read the repository holding your skills. ## Connect a repository In the Firetiger UI, open [Integrations → Skills]({{ site.ui_url }}/integrations/skills) and click **Add Skills**: 1. **Display Name** — a label for this provider (e.g. "Platform Runbooks"). 2. **GitHub Connection** — the connection that grants repository access. Only GitHub connections are eligible. 3. **Repository** — `owner/repo`. 4. **Directory** — the directory of skills within the repo. Defaults to `skills`. 5. **Branch** — the git ref to read from. Leave blank to use the repository's default branch. Save, and Firetiger resolves the repository's skills immediately. ## Review what was discovered Open the provider from the [Skills]({{ site.ui_url }}/integrations/skills) list to see every skill Firetiger found in the directory — its slug, its description, and the full `SKILL.md`. This is the same set your agents see, fetched live from GitHub, so it's the quickest way to confirm a repository is laid out correctly. If a skill is missing, check that it lives in an immediate subdirectory of the configured directory and contains a `SKILL.md`. ## How agents use them Resolved skills are mounted read-only at `/n` (`/run/skills`) inside every agent's environment, alongside the built-in library. Agents discover them by listing the mount and read a skill's body when it's relevant to the task at hand. - **Org-wide.** Every agent in your organization sees the org's custom skills. Per-agent targeting is reserved for a future release. - **Built-ins win.** If a custom skill has the same name as a built-in library skill, the built-in takes precedence. - **Always current.** Bodies are fetched at agent runtime from the configured branch, so merging a change to a `SKILL.md` updates what agents read — no re-import step. ## Disable or remove a provider Editing a provider exposes an **Enabled** toggle. Turn it off to stop serving its skills while keeping the configuration, or delete the provider to remove it entirely. Either change takes effect for new agent work within a short cache window. Credentials live on the GitHub connection, not on the provider, so deleting a provider never touches your connection. ## API Everything above is available over the API — register providers and list the skills a provider exposes. See the [Skills Bundles API reference](../api-reference/skills-bundles.txt). --- # Integrations Connect Firetiger to your existing tools and infrastructure. ## Categories - **[Databases](databases/)** — PostgreSQL, MySQL, ClickHouse, Trino, Elasticsearch, Iceberg - **[Infrastructure](infrastructure/)** — AWS, GCP, Cloudflare, CloudFront, Convex - **[Developer Tools](developer-tools/)** — GitHub, Linear, incident.io, Pylon, WorkOS, Clerk, Vanta, coding agents (Cursor, Devin, Tembo, Replicas, Coder, Inspect) - **[Communications](communications/)** — Slack, SendGrid, Google Postmaster - **[Observability](observability/)** — Vector, Datadog, PromQL, PagerDuty - **[Custom](custom/)** — HTTP APIs, OpenAPI, gRPC, GraphQL, MCP servers, Web Search ## Databases Connect Firetiger agents to your databases for querying and analysis. ### PostgreSQL Connections PostgreSQL connections enable agents to query relational databases. **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/settings/connections` ## Network Configuration Firetiger's query servers must be able to reach your database. Choose the appropriate networking setup based on your database configuration: ### 1. Private Network Database **Scenario**: Database runs in a private VPC/network (AWS, GCP, Azure) **Solution**: Set up a private network connection: - **AWS**: Configure AWS PrivateLink - **GCP**: Configure Private Service Connect - **Azure**: Configure Private Link Contact Firetiger support to coordinate private network setup. ### 2. Public Database with IP Allowlist **Scenario**: Database accepts public connections but restricts access by IP address **Solution**: Add Firetiger's static IP addresses to your database's allowlist - Contact Firetiger support to obtain the static IP addresses for your deployment's query servers - Add these IPs to your database firewall rules or security groups ### 3. Public Database with Standard Authentication **Scenario**: Database accepts public connections with username/password authentication **Solution**: No special networking configuration needed - Ensure your database accepts connections on the standard PostgreSQL port (5432) - Verify firewall allows inbound connections from the internet - Use `ssl_mode: "verify-full"` for secure connections ## Connection Parameters A PostgreSQL connection requires the following configuration: ### Required Parameters | Parameter | Type | Description | |-----------|------|-------------| | `host` | string | Database hostname or IP address (e.g., `db.example.com`) | | `port` | int32 | Database port number (typically `5432`) | | `database` | string | Name of the database to connect to | | `username` | string | Username for authentication | | `password` | string | Password for authentication (stored securely as a secret) | ### Optional Parameters | Parameter | Type | Description | Default | |-----------|------|-------------|---------| | `ssl_mode` | string | SSL/TLS connection mode (see below) | `require` | | `role` | string | PostgreSQL role to assume after connecting | None | | `read_only` | bool | Enable database-level read-only enforcement | `false` | ## SSL Modes The `ssl_mode` parameter controls how the connection encrypts data in transit. Choose the appropriate mode based on your security requirements: | Mode | Description | Use When | |------|-------------|----------| | `disable` | No SSL encryption | **Not recommended** - Only for local development or testing | | `require` | Require SSL but don't verify server certificate | You want encryption but certificate verification isn't critical | | `verify-ca` | Require SSL and verify the server certificate is signed by a trusted CA | You have a proper CA-signed certificate | | `verify-full` | Require SSL, verify certificate, and check hostname matches | **Most secure** - Recommended for production | For production environments, use `verify-full` when possible to prevent man-in-the-middle attacks. ## Read-Only Mode The `read_only` parameter enables database-level read-only enforcement for the connection. **How it works**: After connection, `SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY` is executed automatically. **Effect**: PostgreSQL enforces read-only at the database level - any attempt to INSERT, UPDATE, DELETE, TRUNCATE, or perform other write operations will fail with an error. **When to use**: - **Production agent connections** - Recommended for all AI agent access to prevent accidental data modification - **Analytics/reporting connections** - Ensure reporting tools cannot modify data - **Compliance requirements** - Meet regulatory requirements for read-only access **Example**: ```json { "read_only": true } ``` This provides strong database-level protection against data modification. Unlike role-based permissions, this setting prevents writes regardless of the user's privileges. **Note**: Default is `false` for backward compatibility. We recommend setting `read_only: true` for all new production connections. ## Role-Based Access Control The `role` parameter enables privilege separation - connect with admin credentials but execute queries with restricted permissions. **How it works**: After connection, `SET ROLE ` is executed automatically. **Common pattern** - Use built-in read-only role: ```json { "role": "pg_read_all_data" } ``` This prevents data modification while allowing full read access. ## Description Field The `description` should document your schema to help agents write effective queries. **Example**: ``` Production customer database. Tables: - users: user_id (uuid), email (text), status (text: 'active'/'suspended') - subscriptions: subscription_id (uuid), user_id (uuid), plan_name (text), status (text) - billing_events: event_id (uuid), subscription_id (uuid), event_type (text), amount_cents (int) Common patterns: - Find user by email: WHERE email = '' - Active subscriptions: WHERE status = 'active' ``` ## Example Connection ```json { "display_name": "Production Customer Database", "description": "Production customer database...", "connection_details": { "postgres": { "host": "prod-db.example.com", "port": 5432, "database": "customers", "username": "admin", "password": "password", "ssl_mode": "verify-full", "role": "pg_read_all_data", "read_only": true } } } ``` ## Query Support Firetiger supports standard PostgreSQL query syntax including: - SELECT statements with all standard clauses (WHERE, JOIN, GROUP BY, ORDER BY, LIMIT, etc.) - Common Table Expressions (CTEs) with WITH - SQL comments (both `--` single-line and `/* */` multi-line styles) - All PostgreSQL functions (aggregate, string, date/time, etc.) ## Security Model Firetiger provides multiple layers of security for PostgreSQL connections: 1. **Database-level read-only enforcement** - Use `read_only: true` to prevent writes at the PostgreSQL level 2. **Role-based access control** - Use read-only roles (`pg_read_all_data`) to limit permissions 3. **SSL encryption** - Use `ssl_mode: "verify-full"` for secure connections **Recommended approach**: Enable `read_only: true` for all production agent connections. This provides strong database-level protection regardless of the user's actual permissions. **Defense in depth**: Combine `read_only: true` with a read-only role for maximum protection: - `read_only: true` - Prevents writes even if the role has write permissions - `role: "pg_read_all_data"` - Limits permissions at the database role level ## Best Practices - **Enable read-only mode** - Set `read_only: true` for all production agent connections - **Use read-only roles** - Set `role: "pg_read_all_data"` or custom read-only roles for defense in depth - **Document your schema** - Include table/column information in the description field - **Enable SSL in production** - Use `ssl_mode: "verify-full"` - **Limit data exposure** - Only grant access to necessary tables through PostgreSQL permissions ### MySQL Connections MySQL connections enable agents to query MySQL databases. **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/settings/connections` ## Network Configuration Firetiger's query servers must be able to reach your database. Choose the appropriate networking setup based on your database configuration: ### 1. Private Network Database **Scenario**: Database runs in a private VPC/network (AWS, GCP, Azure) **Solution**: Set up a private network connection: - **AWS**: Configure AWS PrivateLink - **GCP**: Configure Private Service Connect - **Azure**: Configure Private Link Contact Firetiger support to coordinate private network setup. ### 2. Public Database with IP Allowlist **Scenario**: Database accepts public connections but restricts access by IP address **Solution**: Add Firetiger's static IP addresses to your database's allowlist - Contact Firetiger support to obtain the static IP addresses for your deployment's query servers - Add these IPs to your database firewall rules or security groups ### 3. Public Database with Standard Authentication **Scenario**: Database accepts public connections with username/password authentication **Solution**: No special networking configuration needed - Ensure your database accepts connections on the standard MySQL port (3306) - Verify firewall allows inbound connections from the internet - Use `ssl_mode: "VERIFY_IDENTITY"` for secure connections ## Connection Parameters A MySQL connection requires the following configuration: ### Required Parameters | Parameter | Type | Description | |-----------|------|-------------| | `host` | string | Database hostname or IP address (e.g., `db.example.com`) | | `port` | int32 | Database port number (typically `3306`) | | `database` | string | Name of the database to connect to | | `username` | string | Username for authentication | | `password` | string | Password for authentication (stored securely as a secret) | ### Optional Parameters | Parameter | Type | Description | Default | |-----------|------|-------------|---------| | `ssl_mode` | string | SSL/TLS connection mode (see below) | `PREFERRED` | | `read_only` | bool | Enable database-level read-only enforcement | `false` | ## SSL Modes The `ssl_mode` parameter controls how the connection encrypts data in transit. Choose the appropriate mode based on your security requirements: | Mode | Description | Use When | |------|-------------|----------| | `DISABLED` | No SSL encryption | **Not recommended** - Only for local development or testing | | `PREFERRED` | Use SSL if available, fall back to unencrypted | Default - Provides encryption when the server supports it | | `REQUIRED` | Require SSL but don't verify server certificate | You want encryption but certificate verification isn't critical | | `VERIFY_IDENTITY` | Require SSL, verify certificate, and check hostname matches | **Most secure** - Recommended for production | For production environments, use `VERIFY_IDENTITY` when possible to prevent man-in-the-middle attacks. ## Read-Only Mode The `read_only` parameter enables database-level read-only enforcement for the connection. **How it works**: After connection, `SET SESSION TRANSACTION READ ONLY` is executed automatically. **Effect**: MySQL enforces read-only at the session level - any attempt to INSERT, UPDATE, DELETE, TRUNCATE, or perform other write operations will fail with an error. **When to use**: - **Production agent connections** - Recommended for all AI agent access to prevent accidental data modification - **Analytics/reporting connections** - Ensure reporting tools cannot modify data - **Compliance requirements** - Meet regulatory requirements for read-only access **Example**: ```json { "read_only": true } ``` This provides strong database-level protection against data modification. **Note**: Default is `false` for backward compatibility. We recommend setting `read_only: true` for all new production connections. ## Description Field The `description` should document your schema to help agents write effective queries. **Example**: ``` Production customer database (MySQL 8.0). Tables: - users: user_id (INT, PK), email (VARCHAR), status (ENUM: 'active','suspended') - subscriptions: subscription_id (INT, PK), user_id (INT, FK), plan_name (VARCHAR), status (VARCHAR) - billing_events: event_id (INT, PK), subscription_id (INT, FK), event_type (VARCHAR), amount_cents (INT) Common patterns: - Find user by email: WHERE email = '' - Active subscriptions: WHERE status = 'active' ``` ## Example Connection ```json { "display_name": "Production Customer Database", "description": "Production customer database...", "connection_details": { "mysql": { "host": "prod-db.example.com", "port": 3306, "database": "customers", "username": "admin", "password": "password", "ssl_mode": "VERIFY_IDENTITY", "read_only": true } } } ``` ## Query Support Firetiger supports standard MySQL query syntax including: - SELECT statements with all standard clauses (WHERE, JOIN, GROUP BY, ORDER BY, LIMIT, etc.) - Common Table Expressions (CTEs) with WITH (MySQL 8.0+) - Subqueries and derived tables - SQL comments (both `--` single-line and `/* */` multi-line styles) - All MySQL functions (aggregate, string, date/time, JSON, etc.) - MySQL-specific syntax such as `LIMIT offset, count` and backtick-quoted identifiers ## Security Model Firetiger provides multiple layers of security for MySQL connections: 1. **Database-level read-only enforcement** - Use `read_only: true` to prevent writes at the MySQL session level 2. **SSL encryption** - Use `ssl_mode: "VERIFY_IDENTITY"` for secure connections **Recommended approach**: Enable `read_only: true` for all production agent connections. This provides strong database-level protection regardless of the user's actual permissions. ## Best Practices - **Enable read-only mode** - Set `read_only: true` for all production agent connections - **Document your schema** - Include table/column information in the description field - **Enable SSL in production** - Use `ssl_mode: "VERIFY_IDENTITY"` - **Limit data exposure** - Only grant access to necessary tables through MySQL permissions - **Use a dedicated user** - Create a MySQL user specifically for Firetiger with minimal required privileges ### ClickHouse Connections ClickHouse connections enable agents to query ClickHouse analytical databases. **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/settings/connections` ## Network Configuration Firetiger's query servers must be able to reach your ClickHouse instance. Choose the appropriate networking setup based on your deployment: ### 1. ClickHouse Cloud **Scenario**: Managed ClickHouse Cloud instance **Important**: ClickHouse Cloud exposes two ports — `8443` (HTTP protocol) and `9440` (native protocol). Firetiger uses the **native protocol**, so you must connect on port **`9440`** with `secure: true`. Using port `8443` will result in a connection error. **Solution**: Ensure Firetiger's IP addresses are allowed in your ClickHouse Cloud service's IP access list. - Contact Firetiger support to obtain the static IP addresses for your deployment's query servers - Add these IPs in ClickHouse Cloud under **Settings > Security > IP Access List** ### 2. Private Network Database **Scenario**: ClickHouse runs in a private VPC/network (AWS, GCP, Azure) **Solution**: Set up a private network connection: - **AWS**: Configure AWS PrivateLink - **GCP**: Configure Private Service Connect Contact Firetiger support to coordinate private network setup. ### 3. Public Instance with Standard Authentication **Scenario**: ClickHouse accepts public connections with username/password authentication **Solution**: No special networking configuration needed - Ensure your ClickHouse instance accepts connections on the configured port - Verify firewall allows inbound connections - Enable TLS (`secure: true`) for encrypted connections ## Connection Parameters A ClickHouse connection requires the following configuration: ### Required Parameters | Parameter | Type | Description | |-----------|------|-------------| | `host` | string | Database hostname (e.g., `abc.us-east-1.aws.clickhouse.cloud`) | | `database` | string | Name of the database to connect to | | `username` | string | Username for authentication | | `password` | string | Password for authentication (stored securely as a secret) | ### Optional Parameters | Parameter | Type | Description | Default | |-----------|------|-------------|---------| | `port` | int32 | Database port number | `9440` | | `secure` | bool | Enable TLS encryption | `true` | ## Ports and Protocols Firetiger connects to ClickHouse using the **native protocol**. The default port depends on whether TLS is enabled: | Configuration | Port | Description | |---------------|------|-------------| | `secure: true` | `9440` | Native protocol with TLS (default) | | `secure: false` | `9000` | Native protocol without TLS | ClickHouse Cloud uses port `9440` by default with TLS enabled. ## TLS (Secure Mode) The `secure` parameter controls whether the connection uses TLS encryption. - **`true` (default)** - TLS enabled. Recommended for all environments, required for ClickHouse Cloud. - **`false`** - TLS disabled. Only use for local development or testing. For production environments, always use `secure: true`. ## Description Field The `description` should document your schema to help agents write effective queries. **Example**: ``` ClickHouse analytics database for event tracking. Tables: - events: event_id (UUID), user_id (String), event_type (String), timestamp (DateTime), properties (String) - page_views: view_id (UUID), user_id (String), url (String), referrer (String), timestamp (DateTime) - sessions: session_id (UUID), user_id (String), start_time (DateTime), duration_seconds (UInt32) Common patterns: - Events in time range: WHERE timestamp >= '2024-01-01' AND timestamp < '2024-02-01' - Aggregate by day: GROUP BY toDate(timestamp) - Top events: SELECT event_type, count() FROM events GROUP BY event_type ORDER BY count() DESC ``` ## Example Connection ```json { "display_name": "Analytics ClickHouse", "description": "ClickHouse analytics database...", "connection_details": { "clickhouse": { "host": "abc.us-east-1.aws.clickhouse.cloud", "port": 9440, "database": "analytics", "username": "readonly_user", "password": "password", "secure": true } } } ``` ## Query Support Firetiger supports standard ClickHouse SQL syntax including: - SELECT statements with all standard clauses (WHERE, JOIN, GROUP BY, ORDER BY, LIMIT, etc.) - Common Table Expressions (CTEs) with WITH - ClickHouse-specific functions (toDate, toDateTime, formatDateTime, etc.) - Aggregate functions (count, sum, avg, uniq, quantile, etc.) - Array and map functions - SQL comments (both `--` single-line and `/* */` multi-line styles) Query results are streamed with a timeout of 5 minutes. ## Security Model Firetiger provides multiple layers of security for ClickHouse connections: 1. **Read-only users** - Create a dedicated read-only ClickHouse user for Firetiger 2. **TLS encryption** - Use `secure: true` for encrypted connections 3. **Network restrictions** - Use IP allowlists or private networking to restrict access **Recommended approach**: Create a dedicated read-only user in ClickHouse with access limited to the necessary databases and tables. **Example** - Create a read-only user in ClickHouse: ```sql CREATE USER firetiger_reader IDENTIFIED BY 'secure_password'; GRANT SELECT ON analytics.* TO firetiger_reader; ``` ## Best Practices - **Use a read-only user** - Create a dedicated ClickHouse user with only SELECT permissions - **Enable TLS** - Set `secure: true` for all production connections - **Document your schema** - Include table/column information and common query patterns in the description field - **Limit database access** - Only grant access to necessary databases and tables - **Use ClickHouse Cloud defaults** - Port `9440` with `secure: true` for ClickHouse Cloud instances ### Iceberg Connections Iceberg connections point an agent at an [Apache Iceberg](https://iceberg.apache.org/) REST catalog. Agents query Iceberg tables via SQL using the `iceberg_query` tool. Every Firetiger deployment auto-provisions a virtual, read-only Iceberg connection that targets the deployment's own data lake (logs, spans, metrics). You only need to create a new Iceberg connection if you want agents to query an **external** Iceberg warehouse — for example, a customer's data lake or a separate analytical platform. **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/settings/connections` ## Connection Parameters | Parameter | Type | Description | |-----------|------|-------------| | `catalog_uri` | string | REST catalog URI (e.g., `https://iceberg-gw.example.com/iceberg/v1`) | ## Authentication Set exactly one of the following auth methods: ### Basic Auth HTTP Basic authentication with username and password. ```json "basic": { "username": "user", "password": "pass" } ``` ### Bearer Token Static Bearer token sent as `Authorization: Bearer `. ```json "bearer": { "token": "" } ``` ### Context Auth Forwards the caller's own credentials. Used by the auto-provisioned Firetiger gateway connection so each agent run authenticates as the originating user. ```json "context": {} ``` ## Example Connection ```json { "display_name": "Customer Data Lake", "description": "Read-only access to the customer's Iceberg warehouse for analytics queries", "connection_details": { "iceberg": { "catalog_uri": "https://iceberg.example.com/v1", "bearer": { "token": "" } } } } ``` ## Best Practices - **Use the auto-provisioned gateway** for the deployment's own data lake — agents already have it as the default profile - **Document namespaces and tables** in the description field so agents can write effective queries without round-tripping through `LIST` calls - **Prefer read-only credentials** at the catalog level when connecting to a customer's warehouse ### Trino Connections Trino connections enable agents to run SQL against a [Trino](https://trino.io/) coordinator. Agents use the `trino_query` tool to execute reads across whichever catalogs the coordinator exposes (Hive, Iceberg, Postgres, MySQL, TPCH, etc.). **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/settings/connections` ## Network Configuration Firetiger's query servers must be able to reach your Trino coordinator. The same options as other database connections apply: - **Private network**: Set up AWS PrivateLink, GCP Private Service Connect, or Azure Private Link (contact Firetiger support) - **Public coordinator with IP allowlist**: Add Firetiger's static IP addresses to your firewall (contact Firetiger support) - **Public coordinator with HTTPS**: No additional networking needed ## Connection Parameters ### Required Parameters | Parameter | Type | Description | |-----------|------|-------------| | `host` | string | Trino coordinator host (e.g., `trino.example.com`) | | `port` | int32 | Trino coordinator port (`443` for HTTPS, `8080` for HTTP) | | `catalog` | string | Default catalog (e.g., `hive`, `iceberg`, `tpch`) | | `username` | string | Username for authentication | ### Optional Parameters | Parameter | Type | Description | Default | |-----------|------|-------------|---------| | `schema` | string | Default schema within the catalog | None | | `password` | string | Password — Trino supports no-auth setups | None | | `secure` | bool | Use HTTPS | `true` | ## Example Connection ```json { "display_name": "Analytics Warehouse", "description": "Trino coordinator over the analytics warehouse...", "connection_details": { "trino": { "host": "trino.example.com", "port": 443, "catalog": "iceberg", "schema": "warehouse", "username": "firetiger", "password": "", "secure": true } } } ``` ## Description Field Document the catalogs, schemas, and key tables agents should know about. Trino's federation means a single connection can span very different backends — listing them up front avoids agents calling `SHOW CATALOGS` and `SHOW TABLES` on every run. ``` Analytics Trino coordinator (Iceberg + Hive). Catalogs: - iceberg.warehouse: orders, customers, line_items (Iceberg, partition by event_date) - hive.legacy: events_2023, events_2024 (Parquet, partition by dt) Common patterns: - Cross-catalog join: iceberg.warehouse.orders JOIN hive.legacy.events_2024 - Time bound: WHERE event_date >= DATE '2024-01-01' ``` ## Best Practices - **Use a dedicated read-only Trino user** with `SELECT` privileges only - **Enable HTTPS** (`secure: true`) for any non-local coordinator - **Document partition columns** in the description so agents add the right `WHERE` predicates and avoid full-warehouse scans - **Prefer fully-qualified names** (`catalog.schema.table`) in agent prompts to make queries portable across coordinators ### Elasticsearch Connections Elasticsearch connections expose a cluster's [SQL API](https://www.elastic.co/guide/en/elasticsearch/reference/current/sql-rest.txt) (`POST /_sql?format=json`) to agents. Supported on Elasticsearch 6.3+ — the SQL endpoint is part of the free basic tier on 8.x. **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/settings/connections` ## Connection Parameters ### Required Parameters | Parameter | Type | Description | |-----------|------|-------------| | `url` | string | Cluster base URL — must use `https://` (e.g., `https://es.example.com:9200`) | The fireshell network proxy drops plain HTTP egress, so an `http://` URL will pass create-time validation but fail at runtime with no auth applied. Front a plain Elasticsearch cluster with a TLS terminator (nginx, `xpack.security`, etc.) before connecting. ### Optional Parameters | Parameter | Type | Description | Default | |-----------|------|-------------|---------| | `tls_skip_verify` | bool | Skip TLS certificate verification | `false` | Use `tls_skip_verify` only for self-signed clusters in a trusted network. ## Authentication Set exactly one of the following — auth is **required** at create time, even for unsecured local clusters (use `none` explicitly). ### Basic Auth ```json "basic": { "username": "elastic", "password": "" } ``` ### API Key A pre-created Elasticsearch API key. The server renders it as `Authorization: ApiKey ` — Elasticsearch routes API-key credentials through a different auth realm than Bearer tokens, so this branch is distinct from a generic Bearer. ```json "api_key": { "token": "" } ``` Reference: [Create API key API](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.txt). ### No Authentication For local or development clusters with security disabled (`xpack.security.enabled=false`). ```json "none": {} ``` ## Example Connection ```json { "display_name": "Production ES", "description": "Production Elasticsearch logs cluster (8.x).\n\nIndices:\n- logs-app-*: application logs (timestamp, service, level, message)\n- logs-access-*: NGINX access logs (timestamp, remote_addr, request, status)", "connection_details": { "elasticsearch": { "url": "https://es.example.com:9200", "api_key": { "token": "" } } } } ``` ## Description Field Elasticsearch SQL exposes indices as tables. List the indices (or index patterns) agents should query, plus the relevant fields and types — agents otherwise spend several round trips on `DESCRIBE`. ## Best Practices - **Use API keys, not user passwords** — keys can be scoped to specific indices and rotated independently - **Scope API keys to read-only** — the SQL endpoint only needs `read` and `view_index_metadata` on the target indices - **Keep `tls_skip_verify: false`** in production — only enable for self-signed dev clusters ### Databricks Connections Databricks is not a native Firetiger connection type. Instead, you connect agents to your Databricks Delta Lake tables through Databricks' **managed MCP servers** — hosted [Model Context Protocol](https://modelcontextprotocol.io/) endpoints that run SQL against Unity Catalog on your behalf. Firetiger registers the endpoint as an [MCP Server connection]({% link integrations/custom/mcp.md %}) and the agent invokes its tools. **Recommended**: Create and manage the connection via the web UI at `{{ site.ui_url }}/integrations/mcp` ## How it works ``` Firetiger agent → MCP Server connection (this guide) → https:///api/2.0/mcp/sql (Databricks managed MCP server) → SQL Warehouse → Unity Catalog (Delta Lake tables) ``` Databricks enforces Unity Catalog permissions for the authenticating identity, so the agent can only see and query tables that identity is allowed to read. ## Prerequisites In your Databricks workspace: 1. A running **SQL Warehouse** (Databricks managed MCP uses it as the compute layer). 2. The authenticating identity needs Unity Catalog grants on everything the agent will touch: - `USE CATALOG` on the target catalog(s) - `USE SCHEMA` on the target schema(s) - `SELECT` on the tables to be queried or described - `CAN_USE` on the SQL Warehouse 3. Your workspace hostname, e.g. `dbc-1234abcd-5e6f.cloud.databricks.com`. ## Managed MCP server URLs Databricks exposes more than one managed MCP server. The two relevant for querying Delta Lake tables are: | Server | URL | Use it for | |--------|-----|------------| | Databricks SQL | `https:///api/2.0/mcp/sql` | Running SQL across Unity Catalog tables | | Genie | `https:///api/2.0/mcp/genie/` | Natural-language questions over a specific Genie Space | Use the **Databricks SQL** URL for direct table queries. Use **Genie** if you'd rather the agent ask questions in natural language against a curated Genie Space. ## Setup (UI) The UI is the quick path for the **bearer-token (PAT)** mode: 1. Navigate to **Settings > MCP Servers** in the Firetiger UI (`{{ site.ui_url }}/integrations/mcp`). 2. Click **Connect Server**. 3. Enter the **Server URL**: `https:///api/2.0/mcp/sql`. 4. Choose **Bearer token** and paste a Databricks personal access token, then click **Connect**. Once the connection reports **Connected**, Firetiger fetches the tool list from the server. The connection becomes available to agents that reference it. > **For OAuth, use the API, not the UI.** The UI's static-OAuth form only submits a > client ID and secret — it can't pass OAuth **scopes**. Databricks requires the `sql` > scope (and `offline_access` for token refresh), so set up Databricks OAuth through the > API as shown in [OAuth — pre-registered app](#oauth--pre-registered-app-recommended) > below. ## Authentication The Databricks managed MCP servers authenticate with **OAuth** (on-behalf-of-user). Two modes work with Databricks — pre-registered (static) OAuth, or a personal access token as a bearer token. Pick based on your needs. ### OAuth — pre-registered app (recommended) Register an OAuth app (a "custom app integration") in your Databricks account console, then connect with **Static OAuth** using its `client_id` / `client_secret`. This queries as the authorizing user, so Unity Catalog permissions are enforced per-user. When you register the custom app, configure two things or the flow below will fail: - **Redirect URL** — add `{{ site.api_url }}/mcp/oauth/callback` (or your deployment-specific API base). Firetiger sends this exact value as the `redirect_uri`, and Databricks rejects the authorization with a redirect-URI mismatch if it isn't pre-registered. - **Scopes** — grant the app the scopes Firetiger requests: `sql` and `offline_access` (or a broader scope such as `all-apis`). An app created without them either rejects the authorization or can't issue the refresh token Firetiger relies on — leaving a connection that stops working when the access token expires. Set this up through the **API** (the UI's OAuth form can't pass the required scopes): ```bash curl -X POST "{{ site.api_url }}/v1/mcp-connections:initiateStaticOAuth" \ -H "Authorization: Bearer $FIRETIGER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "server_url": "https:///api/2.0/mcp/sql", "client_id": "", "client_secret": "", "scopes": ["sql", "offline_access"], "display_name": "Databricks SQL — Production" }' ``` The response carries an `authorization_url`; open it to authorize, and the connection is created when the flow completes. Request the `offline_access` scope (as above) so Databricks issues a refresh token — without it, Firetiger cannot rotate the access token and the connection stops working when the token expires. > **Dynamic OAuth is not supported by Databricks.** Databricks managed MCP servers do > **not** support RFC 7591 dynamic client registration, so the `:initiateDynamicOAuth` > flow will not work here. Pre-register an OAuth app in the Databricks account console > and use **Static OAuth**, as above. ### Bearer token — Databricks PAT (simplest to validate) For a quick end-to-end check, use a Databricks **personal access token** as a bearer token. This is the fastest way to confirm tool discovery and querying work, but it queries as a **single** identity (the token owner) rather than per-agent-user. ```bash curl -X POST "{{ site.api_url }}/v1/mcp-connections" \ -H "Authorization: Bearer $FIRETIGER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "mcp_connection": { "server_url": "https:///api/2.0/mcp/sql", "display_name": "Databricks SQL (PAT)", "bearer_token": { "token": "" } } }' ``` Bearer tokens are write-only — Firetiger never returns them in responses. ## Private workspaces If your Databricks workspace is only reachable over a private network (e.g. AWS PrivateLink, no public endpoint), route the connection through a [network transport]({% link integrations/networking/network-transports.md %}) such as a [Tailscale]({% link integrations/networking/tailscale.md %}) tailnet. Set the connection's `network_transport` to the configured transport and Firetiger sends the MCP traffic through it instead of the public internet. **Important:** `network_transport` is only settable on the direct-create auth modes — `bearer_token` and `no_auth`. **OAuth connections always connect over the public internet.** So for a privately-networked workspace, authenticate with a Databricks **personal access token** ([bearer token](#bearer-token--databricks-pat-simplest-to-validate)) and attach the network transport; OAuth is not an option until the workspace is publicly reachable. Most Databricks workspaces are publicly reachable and don't need this. ## Viewing available tools After connecting: 1. Open the connection card under **Settings > MCP Servers**. 2. Review the **Available Tools** section — the Databricks SQL server typically exposes tools to list catalogs/schemas, describe tables, and execute SQL. 3. Click **Refresh Tools** if Databricks publishes new tools. ## Using it from an agent Reference the connection from the agent's plan. Once attached, the agent can call the Databricks tools to explore and query, for example: > Describe `main.sales.orders`, then sum revenue by region for the last 30 days. The agent describes the table to learn its schema, then issues a `SELECT` through the SQL Warehouse. Results come back as structured tool output. ## Best practices - **Scope the SQL Warehouse and grants narrowly** — the agent inherits exactly the Unity Catalog access of the authenticating identity. Grant only `SELECT` on the tables it needs. - **Prefer OAuth over a PAT** for production, so queries run per-user and tokens rotate automatically. - **Document the connection** — use the description field to tell the agent which catalogs/schemas are relevant and what the data represents. - **Validate with a PAT first** — confirm tool discovery and a sample query end-to-end, then switch to OAuth for the real deployment. ## Related - [MCP Server connections]({% link integrations/custom/mcp.md %}) — the underlying mechanism this guide builds on. - [Network transports]({% link integrations/networking/network-transports.md %}) — for privately-networked workspaces. ## Infrastructure Ingest data from and connect to cloud infrastructure providers. ### AWS Connections AWS connections enable Firetiger to access your AWS resources using IAM role assumption (STS AssumeRole). This provides secure, temporary credentials without sharing long-term access keys. **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/settings/connections` > The connection works once **your role's trust policy allows this deployment's Firetiger principal** with the matching `sts:ExternalId` condition (see [Trust policy](#trust-policy)). You can name the role and its policies whatever you like. The CloudFormation templates below configure the trust policy for you — only follow the manual [Trust policy](#trust-policy) steps if you create the role yourself. ## Setup Overview Setting up an AWS connection involves two steps: 1. **Deploy CloudFormation Stack** - Creates an IAM role in your AWS account that Firetiger can assume 2. **Create Connection** - Enter the Role ARN and External ID from the CloudFormation outputs ## Step 1: Deploy CloudFormation Stack ### Option A: AWS Console (One-Click) 1. Navigate to **Settings > Connections** in the Firetiger UI 2. Click **New Connection** and select **AWS** 3. Select your AWS region 4. Click **Launch Stack in AWS Console** 5. Review the stack parameters and click **Create stack** 6. Wait for the stack to complete (typically 2-3 minutes) ### Option B: AWS CLI ```bash aws cloudformation create-stack \ --stack-name firetiger-cloudwatch-logs \ --template-url https://firetiger-public.s3.us-west-2.amazonaws.com/ingest/aws/cloudwatch/logs/ingest-and-iam-onboarding.yaml \ --parameters \ "ParameterKey=FiretigerEndpoint,ParameterValue={{ site.ingest_url }}" \ "ParameterKey=FiretigerUsername,ParameterValue={your-username}" \ "ParameterKey=FiretigerPassword,ParameterValue={your-password}" \ --capabilities CAPABILITY_NAMED_IAM \ --region us-east-1 ``` Replace `{your-username}` and `{your-password}` with your Firetiger credentials. ## Step 2: Get CloudFormation Outputs After the stack completes, retrieve the outputs: **AWS Console:** 1. Go to CloudFormation > Stacks > firetiger-cloudwatch-logs 2. Click the **Outputs** tab 3. Copy the values for `FiretigerRoleArn` and `FiretigerExternalId` **AWS CLI:** ```bash aws cloudformation describe-stacks \ --stack-name firetiger-cloudwatch-logs \ --query 'Stacks[0].Outputs' \ --output table ``` ## Step 3: Create the Connection 1. In the Firetiger UI, enter the **Role ARN** from the CloudFormation outputs 2. Enter the **External ID** from the CloudFormation outputs 3. Select the **AWS Region** where your resources are located 4. Click **Create Connection** ## Connection Fields | Field | Required | Description | |-------|----------|-------------| | `display_name` | Yes | Human-readable name (e.g., "Production AWS") | | `description` | Yes | Description of what this connection accesses | | `role_arn` | Yes | IAM Role ARN to assume (e.g., `arn:aws:iam::123456789012:role/firetiger-access`). Any role name is fine. | | `external_id` | No | External ID for additional security (recommended) | | `region` | Yes | AWS region (default: `us-east-1`) | | `session_duration_seconds` | No | How long credentials are valid (900-43200, default: 3600) | ## What the CloudFormation Stack Creates The stack deploys: | Resource | Purpose | |----------|---------| | Lambda Function | Processes CloudWatch logs and forwards to Firetiger | | IAM Role (Lambda) | Allows Lambda to read CloudWatch logs | | IAM Role (Firetiger) | Cross-account role that Firetiger assumes | | Subscription Filters | Automatically subscribes to matching log groups | | CloudWatch Log Group | Stores Lambda function logs | ## IAM Role Permissions The IAM role created for Firetiger has read-only access: ```json { "Effect": "Allow", "Action": [ "logs:DescribeLogGroups", "logs:DescribeLogStreams", "logs:GetLogEvents", "logs:FilterLogEvents" ], "Resource": "*" } ``` ## Trust policy If you create the IAM role yourself (instead of using the CloudFormation templates), its trust policy must allow this deployment's Firetiger principal to assume it, with your External ID. Use the IAM role ARN of the Firetiger principal (the `role/...` form — *not* the `assumed-role/...` session ARN you may see in error messages): ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::{% if site.aws_account_id and site.aws_account_id != "" %}{{ site.aws_account_id }}{% else %}{% endif %}:role/FiretigerECSApiRole@{{ site.deployment_name }}" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "sts:ExternalId": "" } } } ] } ``` {% unless site.aws_account_id and site.aws_account_id != "" %} Replace `` with this deployment's AWS account ID — find the exact principal ARN on the connection setup screen in the Firetiger UI. {% endunless %} Replace `` with the External ID shown on the connection setup screen (or generated by the CloudFormation stack). `FiretigerECSApiRole@{{ site.deployment_name }}` is the role that performs the assumption at runtime; you may additionally trust `FiretigerLambdaApiRole@{{ site.deployment_name }}` to stay compatible with future deployment topologies. You can name your own role anything — Firetiger assumes whatever ARN you configure, gated by this trust policy. ## Security ### External ID The External ID prevents the [confused deputy problem](https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.txt). When configured: - Only requests with the matching External ID can assume the role - The CloudFormation stack generates a unique External ID automatically - Always use the External ID provided in the stack outputs ### Credential Rotation Firetiger automatically: - Requests new STS credentials before expiration - Caches credentials with a 5-minute expiry buffer - Uses unique session names for CloudTrail auditing ## Verification After creating the connection, Firetiger automatically verifies it by: 1. Calling STS AssumeRole with your Role ARN and External ID 2. Making a test API call (sts:GetCallerIdentity) 3. Confirming the assumed role identity If verification fails, check: - The Role ARN is correct and the role exists - The External ID matches exactly - The role's trust policy allows Firetiger's AWS account ## Troubleshooting ### "Access Denied" when assuming role `AccessDenied` almost always means the role's trust policy or External ID is wrong: - Ensure the role's [trust policy](#trust-policy) trusts the Firetiger principal ARN shown on the connection setup screen (the `role/...` form, not the `assumed-role/...` session ARN). - Check the External ID matches exactly. - Verify the Role ARN is correct and the role exists. ### "Role does not exist" - Confirm the CloudFormation stack completed successfully - Check you're using the correct AWS region ### Credentials expire too quickly - Increase `session_duration_seconds` (max 43200 = 12 hours) - Note: The IAM role's max session duration must also allow this ## Best Practices - **Use descriptive names** - Include environment and purpose (e.g., "Production CloudWatch Logs") - **Document access scope** - Describe which log groups or resources are accessible - **Use External ID** - Always configure for cross-account security - **Limit permissions** - The default role has read-only access; don't add write permissions unless needed ### GCP Connections GCP connections enable Firetiger to access Google Cloud Platform resources using service account authentication. This provides scoped access to GCP APIs through a service account key. **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/settings/connections` ## Setup Overview Setting up a GCP connection involves two steps: 1. **Create a Service Account** - Create a GCP service account with appropriate permissions 2. **Create Connection** - Upload the service account key JSON to Firetiger ## Step 1: Create a Service Account ### Google Cloud Console 1. Go to [IAM & Admin > Service Accounts](https://console.cloud.google.com/iam-admin/serviceaccounts) 2. Click **Create Service Account** 3. Enter a name (e.g., `firetiger-reader`) and description 4. Grant the **Viewer** role (or more restrictive custom role) 5. Click **Done** 6. Click the service account, go to **Keys > Add Key > Create New Key** 7. Select **JSON** and click **Create** 8. Save the downloaded key file securely ### gcloud CLI ```bash # Create the service account gcloud iam service-accounts create firetiger-reader \ --display-name="Firetiger Reader" # Grant Viewer role on the project gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \ --member="serviceAccount:firetiger-reader@YOUR_PROJECT_ID.iam.gserviceaccount.com" \ --role="roles/viewer" # Create and download a key gcloud iam service-accounts keys create key.json \ --iam-account="firetiger-reader@YOUR_PROJECT_ID.iam.gserviceaccount.com" ``` ## Step 2: Create the Connection 1. In the Firetiger UI, click **New Connection** and select **GCP** 2. Enter your **Project ID** 3. Select the **Region** where your resources are located 4. Paste the contents of your service account key JSON file 5. Click **Create Connection** ## Connection Fields | Field | Required | Description | |-------|----------|-------------| | `display_name` | Yes | Human-readable name (e.g., "Production GCP") | | `description` | Yes | Description of what this connection accesses | | `project_id` | Yes | Default GCP project ID for API calls | | `region` | Yes | Default GCP region (e.g., `us-central1`) | | `service_account_key` | Yes | Service account key in JSON format (write-only) | ### Supported Regions | Region | Location | |--------|----------| | `us-central1` | Iowa | | `us-east1` | South Carolina | | `us-east4` | Northern Virginia | | `us-west1` | Oregon | | `us-west2` | Los Angeles | | `europe-west1` | Belgium | | `europe-west2` | London | | `europe-west3` | Frankfurt | | `asia-east1` | Taiwan | | `asia-southeast1` | Singapore | | `asia-northeast1` | Tokyo | | `australia-southeast1` | Sydney | ## Using This Connection from Agents Once a GCP connection is created, Firetiger agents and investigations can run the `gcloud` CLI directly in their shell environment. The service account key is materialized transparently — no manual `gcloud auth` step is required. Common examples: ```bash # Read a single GCS object gcloud storage cat gs://my-bucket/path/to/object.json # List objects gcloud storage ls gs://my-bucket/prefix/ # List Compute Engine instances gcloud compute instances list # Query project metadata gcloud projects describe YOUR_PROJECT_ID ``` The connection is scoped to `https://www.googleapis.com/auth/cloud-platform`, so any Google Cloud API the service account has IAM permission to call will work. ### Granting Additional Access The connection scope (`cloud-platform`) lets the service account call any Google Cloud API its IAM permissions allow. Whether a given `gcloud` command succeeds is determined entirely by the roles bound to the service account. For resource-specific access — including buckets in a different project than the service account, or tightening down from the broad **Viewer** role granted during setup — bind a narrower role directly to the target resource. For example, to grant read-only access to objects in a specific bucket: ```bash gcloud storage buckets add-iam-policy-binding gs://YOUR_BUCKET \ --member="serviceAccount:firetiger-reader@YOUR_PROJECT_ID.iam.gserviceaccount.com" \ --role="roles/storage.objectViewer" ``` Common choices: `roles/storage.objectViewer` for `gcloud storage cat` / `ls`, or a custom role containing only the specific permissions needed. **Note**: If you replace the project-level **Viewer** role with narrower bindings, ensure the service account retains `resourcemanager.projects.get` on the project — connection verification calls `Projects.Get` and will fail without it. ## Service Account Key The service account key is a JSON file that looks like: ```json { "type": "service_account", "project_id": "your-project-id", "private_key_id": "key-id", "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n", "client_email": "firetiger-reader@your-project-id.iam.gserviceaccount.com", "client_id": "123456789", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/...", "universe_domain": "googleapis.com" } ``` The `type` field must be `"service_account"`. The private key is stored securely and is never returned after creation. ## Verification After creating the connection, Firetiger automatically verifies it by: 1. Creating credentials from the service account key 2. Calling the Cloud Resource Manager API to get project info 3. Confirming the service account has access to the specified project If verification fails, check: - The service account key JSON is valid and complete - The service account has not been deleted or disabled - The project ID matches the service account's project (or the account has cross-project access) - The service account has sufficient IAM permissions ## Security ### Key Management - Service account keys are stored encrypted and never returned after creation - Rotate keys periodically using `gcloud iam service-accounts keys create` - Delete old keys after rotation: `gcloud iam service-accounts keys delete KEY_ID` ### Least Privilege Grant only the minimum IAM roles needed: - **Viewer** (`roles/viewer`) - Read-only access to all resources (broad) - **Custom roles** - Create a custom role with only the specific permissions needed ### Key Rotation To rotate a service account key: 1. Create a new key for the service account 2. Update the Firetiger connection with the new key 3. Verify the connection works 4. Delete the old key from GCP ## Troubleshooting ### "Permission denied" errors - Verify the service account has the correct IAM roles - Check that the project ID in the connection matches the target project - Ensure the service account hasn't been disabled ### "Invalid key" errors - Confirm the JSON is complete and properly formatted - Check that `type` is `"service_account"` - Verify the key hasn't been deleted in GCP ### Connection verification fails - Ensure the service account has at least `resourcemanager.projects.get` permission - The **Viewer** role includes this permission ## Best Practices - **Use descriptive names** - Include environment and purpose (e.g., "Production BigQuery Access") - **Document access scope** - Describe which GCP resources and APIs are accessible - **Follow least privilege** - Grant only the IAM roles needed for the specific use case - **Rotate keys regularly** - Create new keys and delete old ones periodically - **Use dedicated service accounts** - Create a separate service account for Firetiger rather than reusing existing ones ### AWS CloudWatch Logs # Send AWS Cloudwatch Logs to Firetiger The easiest way to ingest Cloudwatch Logs is to deploy one of our Cloudwatch Log Ingest Integrations ### Deploy via Terraform The terraform module can be found here in git, along with instructions on how to configure and deploy: [https://github.com/firetiger-inc/public/tree/main/aws/](https://github.com/firetiger-inc/public/tree/main/aws/) ### Deploy via Cloudformation Documentation: [https://github.com/firetiger-inc/public/tree/main/aws/](https://github.com/firetiger-inc/public/tree/main/aws/) ### AWS ECS Events # Send AWS ECS Events to Firetiger AWS ECS Events provide rich information about your ECS Task health and lifecycle, including: Container Failures, OOMKilled Events, and other signal helpful in triaging ECS application issues. The easiest way to ingest ECS Events is to deploy our ECS Event Ingest Integration ### Deploy via Terraform The terraform module can be found here in git, along with instructions on how to configure and deploy: [https://github.com/firetiger-inc/public](https://github.com/firetiger-inc/public) ### Deploy via Cloudformation [https://github.com/firetiger-inc/public](https://github.com/firetiger-inc/public) ### CloudFront Kinesis Integration # CloudFront to Firetiger Integration via Kinesis Data Firehose This guide explains how to configure AWS CloudFront real-time logs to stream to Firetiger using Kinesis Data Streams and Kinesis Data Firehose. ## Prerequisites - AWS Account with CloudFront distributions - Firetiger deployment with ingest endpoint accessible from AWS - IAM permissions to create Kinesis resources and modify CloudFront configurations - Firetiger API credentials for authentication ## Architecture Overview ``` CloudFront → Kinesis Data Stream → Kinesis Data Firehose → Firetiger Ingest API ``` ## Step 1: Create Kinesis Data Stream First, create a Kinesis Data Stream to receive CloudFront real-time logs: ```bash aws kinesis create-stream \ --stream-name cloudfront-logs-stream \ --shard-count 1 \ --region us-east-1 ``` Wait for the stream to become active: ```bash aws kinesis describe-stream \ --stream-name cloudfront-logs-stream \ --region us-east-1 ``` ## Step 2: Configure CloudFront Real-time Logs ### Via AWS Console 1. Navigate to CloudFront in AWS Console 2. Select your distribution 3. Go to the "Telemetry" tab 4. Under "Real-time logs", click "Create configuration" 5. Configure the following: - **Name**: `firetiger-realtime-logs` - **Sampling rate**: 100 (adjust based on volume) - **Fields**: Select all fields or customize based on your needs - **Endpoint**: Select "Kinesis Data Streams" - **Stream**: Select `cloudfront-logs-stream` - **IAM Role**: Create new role with Kinesis write permissions ### Via AWS CLI ```bash aws cloudfront create-realtime-log-config \ --name firetiger-realtime-logs \ --stream-type Kinesis \ --kinesis-stream-config StreamArn=arn:aws:kinesis:us-east-1:ACCOUNT_ID:stream/cloudfront-logs-stream,RoleArn=arn:aws:iam::ACCOUNT_ID:role/CloudFrontRealtimeLogRole \ --fields timestamp,c-ip,s-ip,time-to-first-byte,sc-status,sc-bytes,cs-method,cs-protocol,cs-host,cs-uri-stem,cs-bytes,x-edge-location,x-edge-request-id,x-host-header,time-taken,cs-protocol-version,c-ip-version,cs-user-agent,cs-referer,cs-cookie,cs-uri-query,x-edge-response-result-type,x-forwarded-for,ssl-protocol,ssl-cipher,x-edge-result-type,c-country \ --sampling-rate 100 ``` ## Step 3: Create IAM Role for Kinesis Data Firehose Create an IAM role that allows Kinesis Data Firehose to read from the stream: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "firehose.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } ``` Attach the following policy: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "kinesis:DescribeStream", "kinesis:GetShardIterator", "kinesis:GetRecords", "kinesis:ListShards" ], "Resource": "arn:aws:kinesis:us-east-1:ACCOUNT_ID:stream/cloudfront-logs-stream" }, { "Effect": "Allow", "Action": [ "logs:PutLogEvents", "logs:CreateLogGroup", "logs:CreateLogStream" ], "Resource": "*" } ] } ``` ## Step 4: Create Kinesis Data Firehose Delivery Stream To configure the Delivery Stream, you'll need your organization's Firetiger Ingest username and password credentials. These can be found in the Firetiger UI on the /settings page: `{{ site.ui_url }}/settings`. Substitute those values for `$FIRETIGER_USERNAME` and `$FIRETIGER_PASSWORD` in the following commands: ### Via AWS CLI ```bash export ACCESS_KEY=$(echo -n "$FIRETIGER_USERNAME:$FIRETIGER_PASSSWORD" | base64) aws firehose create-delivery-stream \ --delivery-stream-name cloudfront-to-firetiger \ --delivery-stream-type KinesisStreamAsSource \ --kinesis-stream-source-configuration '{ "KinesisStreamARN": "arn:aws:kinesis:us-east-1:ACCOUNT_ID:stream/cloudfront-logs-stream", "RoleARN": "arn:aws:iam::ACCOUNT_ID:role/firehose-role" }' \ --http-endpoint-destination-configuration '{ "EndpointConfiguration": { "Url": "{{ site.ingest_url }}/aws/cloudfront/kinesis?fields=timestamp,c-ip,s-ip,time-to-first-byte,sc-status,sc-bytes,cs-method,cs-protocol,cs-host,cs-uri-stem,cs-bytes,x-edge-location,x-edge-request-id,x-host-header,time-taken,cs-protocol-version,c-ip-version,cs-user-agent,cs-referer,cs-cookie,cs-uri-query,x-edge-response-result-type,x-forwarded-for,ssl-protocol,ssl-cipher,x-edge-result-type,c-country", "Name": "Firetiger", "AccessKey": "$ACCESS_KEY" }, "BufferingHints": { "IntervalInSeconds": 60, "SizeInMBs": 1 }, "CompressionFormat": "GZIP", "RequestConfiguration": { "ContentEncoding": "GZIP" }, "RetryConfiguration": { "DurationInSeconds": 3600 }, "S3Configuration": { "BucketARN": "arn:aws:s3:::your-backup-bucket", "Prefix": "failed-records/", "ErrorOutputPrefix": "error-records/", "CompressionFormat": "GZIP", "RoleARN": "arn:aws:iam::ACCOUNT_ID:role/firehose-role" } }' ``` ### Configuration Parameters - **Url**: Your Firetiger ingest endpoint - `{{ site.ingest_url }}/aws/cloudfront/kinesis` - **AccessKey**: Your basic-auth Firetiger ingest credentials, as shown above - **BufferingHints**: - `IntervalInSeconds`: How often to send data (60-900 seconds) - `SizeInMBs`: Buffer size before sending (1-128 MB) - **CompressionFormat**: Use GZIP to reduce bandwidth - **S3Configuration**: Backup location for failed records ## Step 5: Attach CloudFront Distribution to Real-time Log Configuration ```bash aws cloudfront update-distribution \ --id YOUR_DISTRIBUTION_ID \ --realtime-log-config-arn arn:aws:cloudfront::ACCOUNT_ID:realtime-log-config/firetiger-realtime-logs ``` ## Step 6: Verify Data Flow After configuration, verify that logs are flowing: 1. Generate some traffic to your CloudFront distribution 2. Monitor Kinesis Data Stream metrics in CloudWatch 3. Check Kinesis Data Firehose metrics for successful deliveries 4. Query your data in Firetiger to confirm ingestion ## Troubleshooting ### Common Issues 1. **Authentication Failures** - Verify your API key is correct - Ensure the endpoint URL includes the correct protocol (https) 2. **No Data Flowing** - Check CloudWatch Logs for Kinesis Data Firehose error messages - Verify IAM roles have correct permissions - Ensure CloudFront distribution is attached to the real-time log configuration 3. **High Error Rate** - Check the S3 backup bucket for failed records - Review error messages in CloudWatch Logs - Verify endpoint is accessible from AWS ## Advanced Configuration ### Custom Field Selection CloudFront allows you to customize which fields to include in real-time logs. This is useful for reducing data volume and costs. You can configure this when creating or updating your CloudFront real-time log configuration: ```bash aws cloudfront update-realtime-log-config \ --name firetiger-realtime-logs \ --fields timestamp,c-ip,sc-status,cs-method,cs-uri-stem,x-edge-location ``` **Important:** When you customize CloudFront fields, you must also update your Kinesis Data Firehose endpoint URL to specify which fields you're sending. This ensures Firetiger parses the log records correctly. #### Configuring Firetiger for Custom Fields When configuring custom CloudFront fields, add a `?fields=` query parameter to your Firehose endpoint URL that lists the fields in the same order as your CloudFront configuration: ```bash export ACCESS_KEY=$(echo -n "$FIRETIGER_USERNAME:$FIRETIGER_PASSWORD" | base64) aws firehose create-delivery-stream \ --delivery-stream-name cloudfront-to-firetiger \ --delivery-stream-type KinesisStreamAsSource \ --kinesis-stream-source-configuration '{ "KinesisStreamARN": "arn:aws:kinesis:us-east-1:ACCOUNT_ID:stream/cloudfront-logs-stream", "RoleARN": "arn:aws:iam::ACCOUNT_ID:role/firehose-role" }' \ --http-endpoint-destination-configuration '{ "EndpointConfiguration": { "Url": "{{ site.ingest_url }}/aws/cloudfront/kinesis?fields=timestamp,c-ip,sc-status,cs-method,cs-uri-stem,x-edge-location", "Name": "Firetiger", "AccessKey": "$ACCESS_KEY" }, ... }' ``` #### Field Mapping Examples **Default (All Fields):** If you don't specify a `?fields=` parameter, Firetiger expects all 45 standard CloudFront fields in canonical order. This is the recommended configuration for most use cases. ``` {{ site.ingest_url }}/aws/cloudfront/kinesis ``` **Minimal Fields:** For cost-sensitive deployments, you can send only essential fields: ``` {{ site.ingest_url }}/aws/cloudfront/kinesis?fields=timestamp,c-ip,sc-status,cs-method,cs-uri-stem ``` **Custom Fields:** Select specific fields based on your analytics needs: ``` {{ site.ingest_url }}/aws/cloudfront/kinesis?fields=timestamp,c-ip,cs-method,cs-host,cs-uri-stem,sc-status,sc-bytes,time-taken,cs-user-agent,x-edge-location,x-edge-response-result-type ``` **Important Notes:** 1. **Field names** must match CloudFront field names exactly (e.g., `timestamp`, `c-ip`, `sc-status`) 2. **Field order** must match your CloudFront real-time log configuration exactly - CloudFront always sends fields in canonical order 3. **Changing fields**: If you add or remove fields in your CloudFront configuration, you **must** update your Kinesis Data Firehose URL to match the new field list. AWS automatically reorders fields in canonical order when you modify the configuration. 4. Field names are comma-separated with no spaces 5. The query parameter can handle all 45 standard fields 6. If you omit fields from your CloudFront configuration, those attributes will not be populated in Firetiger **Example of field ordering:** - Initial CloudFront config: `time-to-first-byte, sc-status, c-country` (3 fields) - Firehose URL: `?fields=time-to-first-byte,sc-status,c-country` - Later you add `sc-bytes` and `time-taken` to CloudFront - CloudFront automatically reorders to: `time-to-first-byte, sc-status, sc-bytes, time-taken, c-country` (canonical order) - You **must** update Firehose URL to: `?fields=time-to-first-byte,sc-status,sc-bytes,time-taken,c-country` - The `c-country` field moves from position 3 to position 5 in the log records ### Multi-Region Setup For global distributions, consider: 1. Creating Kinesis streams in multiple regions 2. Using cross-region replication 3. Configuring regional Firehose delivery streams ## CloudFront Log Format Details ### Real-time Log Fields CloudFront real-time logs are delivered as **tab-delimited** records with 40-69 fields (depending on configuration). Each record in the Kinesis Data Firehose payload is base64-encoded. **Note:** AWS has expanded the CloudFront real-time log format over time. Firetiger supports: - Fields 1-45: Fully parsed with structured field names - Fields 46-69+: Gracefully ignored (CMCD media streaming fields and future extensions) The fields are: #### Core Fields (1-41) 1. **timestamp** - Unix timestamp with milliseconds (e.g., 1733270958.123) 2. **c-ip** - Client IP address 3. **s-ip** - CloudFront server IP address 4. **time-to-first-byte** - Time to first byte in seconds 5. **sc-status** - HTTP status code 6. **sc-bytes** - Response size in bytes 7. **cs-method** - HTTP method (GET, POST, etc.) 8. **cs-protocol** - Protocol (http/https) 9. **cs-host** - Host header value 10. **cs-uri-stem** - URI path 11. **cs-bytes** - Request size in bytes 12. **x-edge-location** - CloudFront edge location code 13. **x-edge-request-id** - Unique request identifier 14. **x-host-header** - Host header sent to origin 15. **time-taken** - Total time taken in seconds 16. **cs-protocol-version** - HTTP protocol version 17. **c-ip-version** - IP version (IPv4/IPv6) 18. **cs-user-agent** - User agent string 19. **cs-referer** - Referer header 20. **cs-cookie** - Cookie header 21. **cs-uri-query** - Query string 22. **x-edge-response-result-type** - Cache result (Hit, Miss, Error) 23. **x-forwarded-for** - X-Forwarded-For header 24. **ssl-protocol** - SSL/TLS protocol version 25. **ssl-cipher** - SSL/TLS cipher suite 26. **x-edge-result-type** - How request was classified 27. **fle-encrypted-fields** - Field-level encryption 28. **fle-status** - Field-level encryption status 29. **sc-content-type** - Response content type 30. **sc-content-len** - Response content length 31. **sc-range-start** - Range request start 32. **sc-range-end** - Range request end 33. **c-port** - Client port 34. **x-edge-detailed-result-type** - Detailed result type 35. **c-country** - Client country code 36. **cs-accept-encoding** - Accept-Encoding header 37. **cs-accept** - Accept header 38. **cache-behavior-path-pattern** - Cache behavior pattern 39. **cs-headers** - Custom headers 40. **cs-header-names** - Custom header names 41. **cs-headers-count** - Count of custom headers #### Extended Fields (42-45, added October 2022) 42. **primary-distribution-id** - Primary distribution identifier 43. **primary-distribution-dns-name** - Primary distribution DNS name 44. **origin-fbl** - Origin first-byte latency (time from edge to origin's first byte, in seconds) 45. **origin-lbl** - Origin last-byte latency (time from edge to origin's last byte, in seconds) #### CMCD and Extended Fields (46-69+, added April 2024) Fields 46 and beyond are gracefully ignored but accepted in log records. The known CMCD field names are: 46. **asn** - Autonomous system number 47. **c-asn** - Client autonomous system number 48. **cmcd-buffer-length** - CMCD buffer length (milliseconds) 49. **cmcd-buffer-starvation** - CMCD buffer starvation indicator 50. **cmcd-content-id** - CMCD content identifier 51. **cmcd-deadline** - CMCD playback deadline 52. **cmcd-encoded-bitrate** - CMCD encoded bitrate (kbps) 53. **cmcd-measured-throughput** - CMCD measured throughput (kbps) 54. **cmcd-next-object-request** - CMCD next object request 55. **cmcd-next-range-request** - CMCD next range request 56. **cmcd-object-duration** - CMCD object duration (milliseconds) 57. **cmcd-object-type** - CMCD object type (m=manifest, a=audio, v=video, etc.) 58. **cmcd-playback-rate** - CMCD playback rate 59. **cmcd-requested-maximum-throughput** - CMCD requested max throughput 60. **cmcd-startup** - CMCD startup indicator 61. **cmcd-stream-type** - CMCD stream type (v=VOD, l=live) 62. **cmcd-streaming-format** - CMCD streaming format (d=DASH, h=HLS, etc.) 63. **cmcd-top-bitrate** - CMCD top bitrate (kbps) 64. **cmcd-version** - CMCD version 65. **r-host** - Request host 66. **sc-range-count** - Range request count 67. **sc-response-body-time** - Response body time 68. **sr-reason** - Server reason code 69. **x-sc-response-body-time** - Extended response body time These fields are only present if configured in your CloudFront real-time log configuration. CMCD fields are primarily used for media streaming analytics and are sent by compatible media players. Firetiger accepts but does not parse these fields. For complete documentation, see the [AWS CloudFront Real-time Logs documentation](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/real-time-logs.txt). #### Field Value Conventions - Fields with no value are represented as `-` (hyphen) - Numeric fields use standard decimal notation - Timestamps use Unix epoch format with decimal seconds (e.g., 1733270958.123) ### Kinesis Data Firehose Request Format The HTTP request from Kinesis Data Firehose follows this structure: ```json { "requestId": "ed4acda5-034f-9f42-bba1-f29aea6d7d8f", "timestamp": 1733270958000, "records": [ { "data": "MTczMzI3MDk1OC4xMjMJMTkyLjE2OC4xLjEJ..." } ] } ``` - `requestId`: Unique identifier for the Firehose request (matches X-Amz-Firehose-Request-Id header) - `timestamp`: Unix timestamp in milliseconds when the request was created - `records`: Array of base64-encoded CloudFront log records ### Expected Response Format Firetiger must respond with the following format: Success (200 OK): ```json { "requestId": "ed4acda5-034f-9f42-bba1-f29aea6d7d8f", "timestamp": 1733270958123 } ``` Error (4xx/5xx): ```json { "requestId": "ed4acda5-034f-9f42-bba1-f29aea6d7d8f", "timestamp": 1733270958123, "errorMessage": "Error description" } ``` ### Cloudflare Workers Integration Export traces and logs from your [Cloudflare Workers](https://developers.cloudflare.com/workers/) directly to Firetiger using built-in OpenTelemetry support. No additional packages or bindings are required. This guide covers **application-level telemetry** from your Worker code (traces, `console.log()` output, and system logs). For **HTTP request logs** from Cloudflare's edge network, see the [Cloudflare Logpush Integration](cloudflare-logpush.txt). ## Prerequisites - An active Firetiger deployment - A Cloudflare account with at least one deployed Worker - Access to the [Cloudflare dashboard](https://dash.cloudflare.com) or `wrangler` CLI ## Step 1: Get your Firetiger ingest credentials 1. Log in to your Firetiger account 2. Navigate to the **Integrations** page 3. Copy your **Ingest Basic Auth Credentials** (endpoint, username, and password) Generate the Base64-encoded credentials: ```bash echo -n "username:password" | base64 ``` Replace `username` and `password` with the values from your Integrations page. ## Step 2: Create OTLP destinations in Cloudflare In the Cloudflare dashboard, go to **Compute & AI > Workers Observability > Destinations**. ### Traces destination Click **Add destination** and configure: | Field | Value | | :----------------- | :--------------------------------------------------------------------------- | | Destination name | `firetiger-traces` | | Destination type | Traces | | OTLP endpoint | `{{ site.ingest_url }}/v1/traces` | | Custom header name | `Authorization` | | Custom header value| `Basic ` | ### Logs destination Click **Add destination** again and configure: | Field | Value | | :----------------- | :--------------------------------------------------------------------------- | | Destination name | `firetiger-logs` | | Destination type | Logs | | OTLP endpoint | `{{ site.ingest_url }}/v1/logs` | | Custom header name | `Authorization` | | Custom header value| `Basic ` | Replace `` with the output from the base64 command above. ## Step 3: Enable observability on your Worker You can enable observability via the dashboard or `wrangler.toml`. ### Option A: Dashboard 1. Go to **Compute & AI > Workers & Pages** 2. Select your Worker 3. Go to **Settings > Observability** 4. Enable traces and logs, and select the `firetiger-traces` and `firetiger-logs` destinations ### Option B: wrangler.toml Add the following to your Worker's `wrangler.toml`: ```toml [observability.traces] enabled = true head_sampling_rate = 1.0 destinations = ["firetiger-traces"] [observability.logs] enabled = true head_sampling_rate = 1.0 destinations = ["firetiger-logs"] ``` Adjust `head_sampling_rate` as needed (1.0 = 100% of requests, 0.01 = 1%). ## Step 4: Deploy and verify 1. Deploy your Worker with `wrangler deploy` 2. Send a few requests to your Worker 3. Log in to Firetiger and check your traces and logs views Traces will include the full request lifecycle through your Worker, and logs will include all `console.log()` output and system-generated logs. ## What data is exported | Signal | Included | | :------ | :----------------------------------------------------------------------- | | Traces | Request lifecycle spans, sub-requests, bindings (KV, D1, R2, etc.) | | Logs | `console.log()` / `console.error()` output, system-generated logs | | Metrics | Not yet supported by Cloudflare Workers observability | ## Sampling Cloudflare applies head-based sampling at the edge. The `head_sampling_rate` controls what percentage of requests produce telemetry: - `1.0` - Export telemetry for every request (recommended for low-traffic Workers) - `0.1` - Export for 10% of requests - `0.01` - Export for 1% of requests (recommended for high-traffic Workers) When a request is sampled, all traces and logs for that request are exported together. ## Troubleshooting **No data appearing in Firetiger?** - Verify the OTLP endpoints are correct (check for typos in the deployment name) - Ensure the `Authorization` header value includes the `Basic ` prefix - Confirm the destinations are selected on the Worker's observability settings - Check that `head_sampling_rate` is not set to `0` - Try setting `head_sampling_rate = 1.0` temporarily to rule out sampling **Authentication errors?** - Re-generate your Base64 credentials and update the destination header value - Ensure there are no extra spaces or newlines in the credentials ## Related documentation - [Cloudflare Workers Observability: Exporting OpenTelemetry Data](https://developers.cloudflare.com/workers/observability/exporting-opentelemetry-data/) - [Cloudflare Logpush Integration](cloudflare-logpush.txt) - For HTTP request logs from Cloudflare's edge - [OpenTelemetry Integration](../../guides/opentelemetry.txt) - For sending OTLP data from other sources ### Cloudflare Logpush Integration Firetiger supports ingesting Cloudflare Logpush data via direct HTTP endpoints. ## Setup ### Configure Cloudflare Logpush You can find your Firetiger ingest credentials (endpoint, username, and password) on the **Integrations** page in the Firetiger console. To generate the Base64-encoded credentials for the URL: ```bash echo -n "username:password" | base64 | sed 's/=/%3D/g' ``` Replace `username` and `password` with the values from your Integrations page. This command also URL-encodes any `=` padding characters in the Base64 output. 1. Login to the [Cloudflare Dashboard](https://dash.cloudflare.com) 2. Navigate to your domain's **Analytics & Logs** > **Logpush** 3. Click **Create a Logpush Job** 4. Select **HTTP Destination** 5. Configure the HTTP endpoint URL with authentication: ``` {{ site.ingest_url }}/cloudflare/logpush/http_requests?header_Authorization=Basic%20 ``` Replace `` with the output from the base64 command above. Note: The `header_Authorization` URL parameter sets the HTTP Authorization header. The space after "Basic" must be URL-encoded as `%20`. 6. Select the **HTTP Requests** dataset 7. Configure job settings - **Job Name**: Choose a descriptive name (e.g., `firetiger-http-requests`) - **Fields**: Select the fields you want to push (recommend selecting all fields) - **Advanced > Log Delivery Method**: Choose `Edge` for lowest latency 8. Save and enable the job ## Related Documentation - [Cloudflare Logpush Documentation](https://developers.cloudflare.com/logs/get-started/) - [Cloudflare HTTP Destination Setup](https://developers.cloudflare.com/logs/logpush/logpush-job/enable-destinations/http/) - [Cloudflare HTTP Requests Schema](https://developers.cloudflare.com/logs/reference/log-fields/zone/http_requests/) ### Convex Log Streams Convex connections ingest logs from a [Convex](https://www.convex.dev/) deployment via the [Log Streams](https://docs.convex.dev/production/integrations/log-streams) webhook. Convex POSTs each batch of function logs to a Firetiger ingest URL; Firetiger verifies the HMAC-SHA256 signature, decodes the payload, and writes the events to the Iceberg table `convex/logs/console`. **Recommended**: Create the connection via the web UI at `{{ site.ui_url }}/settings/connections` — the UI displays the ingest URL after the connection is created. ## How It Works 1. You create a Convex connection in Firetiger and copy the resulting webhook URL. 2. In the Convex dashboard, you configure a **Log Stream** that POSTs to that URL with the same shared secret. 3. Convex signs each request with HMAC-SHA256 over the raw body bytes; Firetiger verifies the signature using the stored secret. 4. Verified events land in `iceberg.convex.logs.console` and are queryable from agents and the Firetiger UI. ## Setup ### 1. Create the Firetiger Connection | Parameter | Type | Description | |-----------|------|-------------| | `webhook_secret` | string | Shared secret used to sign requests — generate a strong random value | ```json { "display_name": "Convex Production Logs", "description": "Production Convex deployment console logs", "connection_details": { "convex": { "webhook_secret": "" } } } ``` The connection's webhook URL is shown in the Firetiger UI once it's created. ### 2. Configure the Convex Log Stream In the Convex dashboard: 1. Open **Settings → Integrations** 2. Add a new **Log Stream** (Webhook) 3. Set the URL to the Firetiger webhook URL 4. Set the secret to the same `webhook_secret` you used in step 1 Convex begins streaming function logs to Firetiger immediately. ## Querying the Logs Once events are flowing, query them via the Iceberg connection: ```sql USE iceberg; SELECT * FROM "convex/logs/console" WHERE timestamp >= now() - interval '1 hour' ORDER BY timestamp DESC LIMIT 100; ``` The `convex/logs/console` table follows the [Convex Log Streams payload schema](https://docs.convex.dev/production/integrations/log-streams) — each row carries the deployment, project, function path, log level, and message. ## Best Practices - **Use a long, random secret** — the only thing standing between an attacker and a flood of fabricated log events is the HMAC verification - **One connection per deployment** — separate Convex prod / staging / dev deployments into separate connections so agents and dashboards can scope queries cleanly - **Rotate the secret in lockstep** — update the secret in Firetiger and Convex within a short window; mismatched secrets reject all events with a `401` ### Vercel Firetiger ingests data from Vercel via [Vercel Drains](https://vercel.com/docs/drains) (also referred to as Log Drains or Log Sinks). Two drains are involved: one sends logs to Firetiger over HTTP, and one sends OTLP traces. Together they cover the same surface as the one-click setup on the Firetiger Integrations page — this guide documents the manual equivalent so you can configure it by hand if the auto-setup fails or your organization restricts personal access tokens. ## Prerequisites - An owner or admin role on the Vercel team that owns the projects you want to send data from. - Your Firetiger ingest credentials. The endpoint, username, and password are visible on the [Integrations page]({{ site.ui_url }}/integrations/ingest) in the Firetiger console. - A Base64-encoded `username:password` pair for the `Authorization` header: ```bash echo -n "username:password" | base64 ``` Replace `username` and `password` with the values from the Integrations page. Save the output — you will paste it into both drains below. ## Configure the logs drain 1. Open the [Vercel dashboard](https://vercel.com/dashboard) and navigate to your team's settings. 2. Go to **Drains** > **Create Drain**. 3. Configure the drain: - **Name**: `Send logs to Firetiger` - **Projects**: select the projects to send logs from. - **Schema**: `Log` (version `v1`) - **Delivery type**: `HTTP` - **Endpoint**: ``` {{ site.ingest_url }}/vercel/logs ``` - **Encoding**: `JSON` - **Custom headers**: add a header named `Authorization` with value `Basic `, where `` is the output from the Base64 command above. 4. Configure the filter: - **Sources**: `Lambda` and `Edge` (the auto-setup uses these two; add `Build` and `Static` if you also want to ingest build and static-asset logs). - **Environments**: `Production`. Add `Preview` if you want preview deployments to flow into Firetiger as well. 5. Save and enable the drain. ## Configure the traces drain Vercel sends traces as OTLP over HTTP. Create a second drain alongside the logs drain. 1. From the same **Drains** page, click **Create Drain** again. 2. Configure the drain: - **Name**: `Send traces to Firetiger` - **Projects**: select the same projects you chose for the logs drain. - **Schema**: `Trace` (version `v1`) - **Delivery type**: `OTLP HTTP` - **Endpoint**: ``` {{ site.ingest_url }}/v1/traces ``` - **Encoding**: `JSON` - **Custom headers**: add the same `Authorization: Basic ` header you used for the logs drain. 3. Save and enable the drain. ## Verifying ingest Trigger a deployment or fresh request against one of the selected projects, then: - Open the [Integrations page]({{ site.ui_url }}/integrations/ingest) in the Firetiger console and check the **Detected sources** panel — Vercel logs should appear within a minute. - Logs land in the `vercel_logs` table; traces are merged into your OpenTelemetry traces table. - If a drain reports delivery errors in the Vercel dashboard, double-check the endpoint URL and the Base64 of your ingest credentials. Firetiger's ingest endpoint always returns `200 OK` on a successful POST so Vercel will not auto-disable the drain on transient errors. ## Related documentation - [Vercel Drains](https://vercel.com/docs/drains) - [OpenTelemetry on Firetiger]({{ site.baseurl }}/guides/opentelemetry.txt) ### Fastly Firetiger ingests Fastly access and edge logs via Fastly's [HTTPS log streaming endpoint](https://www.fastly.com/documentation/guides/integrations/logging-endpoints/protocol-based-and-self-hosted/log-streaming-https/). Unlike the Vercel and Cloudflare integrations, Fastly does not have a fixed log shape — you choose what fields to include via Fastly's format string, and Firetiger infers the schema and stores rows in a table whose name comes from the URL path you point Fastly at. ## Prerequisites - Owner or Engineer access to the Fastly service you want to send logs from. - Your Firetiger ingest credentials. The endpoint, username, and password are visible on the [Integrations page]({{ site.ui_url }}/integrations/ingest) in the Firetiger console. - A Base64-encoded `username:password` pair for the `Authorization` header: ```bash echo -n "username:password" | base64 ``` Replace `username` and `password` with the values from the Integrations page. ## Configure the Fastly HTTPS log endpoint In the Fastly console, open your service and go to **Logging** > **Create endpoint** > **HTTPS**. Configure the fields below. ### URL ``` {{ site.ingest_url }}/datapoints/fastly/logs/{slug} ``` The trailing `{slug}` is a **subdimension** that splits Fastly logs into separate Firetiger tables — `datapoints/fastly/logs/{slug}`. Pick a slug that matches how you'll query the data later. Common choices: - An environment: `production`, `staging`, `dev` - The Fastly service name or shortname - The customer-facing hostname, with dots replaced by dashes (e.g. `www-example-com`) - A datacenter or region label Configure one Fastly HTTPS endpoint per slug — for example one endpoint on the `production` service pointing at `…/datapoints/fastly/logs/production`, and another on the `staging` service pointing at `…/datapoints/fastly/logs/staging`. Splitting the data across tables keeps your queries simple (filter by table, not by row attribute) and lets retention or compaction policies diverge per environment if you ever need that. If you genuinely want everything in one table, use a single slug like `all`. ### Method, content type, and JSON framing - **Method**: `POST` - **Content type**: `application/json` - **JSON log entry format**: `Array of JSON` (recommended). `Newline delimited` also works — Firetiger's ingest accepts both formats. ### Authentication Fastly's HTTPS endpoint sends a single custom header on every log POST, which is how we pass basic auth: - **Custom header name**: `Authorization` - **Custom header value**: `Basic ` (use the value produced by the `base64` command above) ### Compression Optional. The Firetiger ingest endpoint accepts `gzip` and other `Content-Encoding` values, so feel free to enable compression for bandwidth-heavy services. ### Format string Pick the fields you want in the table. Set **Format version** to `2` and use a JSON object for the **Format** field. A reasonable starting point: ```json { "timestamp": "%{begin:%Y-%m-%dT%H:%M:%S%z}t", "client_ip": "%h", "method": "%m", "url": "%{json.escape(req.url)}V", "status": %s, "host": "%{json.escape(req.http.host)}V", "user_agent": "%{json.escape(req.http.User-Agent)}V", "request_time_ms": %{time.elapsed.msec}V, "fastly_service_id": "%{json.escape(req.service_id)}V", "fastly_pop": "%{json.escape(server.datacenter)}V" } ``` Add or remove fields freely — Firetiger infers the schema from whatever JSON Fastly sends. See Fastly's [custom log formats](https://www.fastly.com/documentation/guides/integrations/logging-endpoints/changing-where-log-files-are-written/custom-log-formats/) reference for the full set of placeholders and VCL variables. Note that the table is partitioned by **ingest receive time**, not by any timestamp inside your JSON payload. Including a `timestamp` field is still useful — it preserves the edge timestamp for use in queries — but it does not affect how Firetiger lays out the data on disk. ## Domain-control challenge Before activating a new HTTPS endpoint, Fastly issues a `GET` to `/.well-known/fastly/logging/challenge` to verify endpoint ownership. Firetiger's ingest server already serves a wildcard response on that path, so the challenge succeeds automatically and you don't need to do anything to handle it. ## Verifying ingest Trigger some traffic against the Fastly service. Logs typically appear within a few minutes — Fastly batches them and the Firetiger ingest flushes on its own cadence. Each event is wrapped into a row whose top-level `timestamp` column is the ingest receive time (used for partitioning) and whose `datapoint` column is the JSON object you sent. The full table name is the literal URL path you configured — for example `datapoints/fastly/logs/production` — and you can query it from any of Firetiger's query interfaces. ## Related documentation - Fastly: [Log streaming: HTTPS](https://www.fastly.com/documentation/guides/integrations/logging-endpoints/protocol-based-and-self-hosted/log-streaming-https/) - Fastly: [Custom log formats](https://www.fastly.com/documentation/guides/integrations/logging-endpoints/changing-where-log-files-are-written/custom-log-formats/) - Firetiger: [Integrations page]({{ site.ui_url }}/integrations/ingest) — find your ingest credentials - Firetiger: [Vector]({{ site.baseurl }}/integrations/observability/vector.txt) — same `/datapoints/` ingest backend, useful when you'd rather ship logs through Vector than directly from Fastly ## Developer Tools Integrate Firetiger with developer and incident management tools. ### GitHub Connections > **Note:** Codebase Search currently does not handle large numbers of repositories well. Improving this integration is on our roadmap. GitHub connections enable agents to access your repositories for codebase context during investigations, and ingest GitHub events (pushes, pull requests, workflow runs, etc.). **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/settings/connections` ## Installation 1. Navigate to **Settings > Connections** in the Firetiger UI 2. Click **New Connection** 3. Select **GitHub** as the connection type 4. Click **Connect GitHub** 5. You will be redirected to GitHub to authorize the Firetiger GitHub App 6. Select the GitHub organization or user account where you want to install the app 7. Choose which repositories to grant access to (all repositories or select specific ones) 8. Confirm the installation The connection will be created automatically with your GitHub account details. GitHub will automatically send webhook events to Firetiger for the selected repositories. ## What Gets Created When you install the GitHub app, Firetiger automatically creates a connection with: | Field | Value | |-------|-------| | `connection_id` | `github-{installation_id}` | | `display_name` | Your GitHub organization or user name | | `description` | `GitHub: {account_name}` | ## Permissions The Firetiger GitHub App requests the following permissions: | Permission | Access | Purpose | |------------|--------|---------| | Repository contents | Read | Access code for context during investigations | | Metadata | Read | Basic repository information | | Pull requests | Read | PR details and status | | Actions | Read | Workflow and job information | ## Updating Repository Access To add or remove repositories from your GitHub connection: 1. Navigate to the [Firetiger GitHub App page](https://github.com/apps/firetiger-agent) 2. Click **Configure** 3. Select your GitHub organization or user account 4. Under **Repository access**, choose: - **All repositories** - Grant access to all current and future repositories - **Only select repositories** - Choose specific repositories to grant access to 5. Click **Save** Changes take effect immediately. Firetiger will automatically update the available repositories for codebase search. ## Re-authorization To reconnect or fix authentication issues: 1. Navigate to **Settings > Connections** in the Firetiger UI 2. Find your GitHub connection 3. Click **Reconnect** or delete and recreate the connection ## Capabilities GitHub connections enable: - **Codebase context** - Agents can search and read repository code during investigations - **Event ingestion** - Automatic ingestion of GitHub events (pushes, PRs, workflow runs) - **Repository insights** - Activity tracking and change history ## Connection Settings The GitHub connection exposes webhook-driven settings on its edit form: | Setting | Proto field (`GithubConnectionDetails`) | Default | Effect | |---------|-----------------------------------------|---------|--------| | **Auto-Create Deployments** | `auto_create_deployments` | Enabled | When GitHub sends a `deployment` webhook event, Firetiger creates a matching [Deployment](../../api-reference/deployments.txt) and tracks its status. Disable if your GitHub deployments don't represent production releases. | | **Auto-Monitor Opened Pull Requests** | `auto_monitor_pull_requests` | Disabled (opt-in) | When a PR is opened on a repository covered by the installation, Firetiger creates a [Change Monitor](../../guides/change-monitor.txt) agent and a dormant [MonitoringPlan](../../api-reference/monitoring-plans.txt) keyed to the PR. The plan activates once the PR merges and rolls out, then the agent watches the affected service for regressions. | | **Filter** | `auto_monitor_pr_filter` | Empty (monitor all) | Optional free-text filter applied when Auto-Monitor is enabled. Firetiger runs a single fast LLM call per opened PR judging title, body, labels, base branch, and author against the filter. PRs that don't match get a skip comment explaining why, instead of a monitoring plan. Leave blank to monitor every opened PR. | All settings are per-connection: a single tenant can have multiple GitHub installations with different auto-monitor behavior. ### Filtering which PRs get auto-monitored When auto-monitoring is on, the default is "every opened PR on every repo covered by the GitHub App installation". That's usually too broad — most teams only care about PRs that touch production-meaningful surfaces. Set **Filter** to a natural-language description of the PRs you want monitored, for example: > PRs that touch the checkout, billing, or auth services. Skip docs, tests, and CI-only changes. On each `pull_request.opened` webhook, Firetiger sends the PR title, body, labels, and base branch to a light LLM (Haiku) along with your filter. If the model decides the PR matches, a monitoring plan is created as usual. If not, **no plan is created** — instead, Firetiger posts a comment on the PR that looks like: > **Firetiger deploy monitoring skipped** > > This PR didn't match the auto-monitor filter configured on your GitHub connection: > > PRs that touch the checkout, billing, or auth services. Skip docs, tests, and CI-only changes. > > **Reason:** Only edits README.md. > > To monitor this PR anyway, reply with `@firetiger monitor this`. **Manual override.** The skip comment tells the PR author how to opt in: reply with `@firetiger monitor this`. That mention flows through the existing `@firetiger` pipeline and creates a monitoring plan for the PR on the spot — no connection-level change required. **Fail-open behavior.** If the LLM call errors (network, provider outage, rate limit), Firetiger falls back to creating a monitoring plan. This is deliberate: occasional over-monitoring is preferable to silently dropping coverage on a PR the operator expected to be watched. ## Description Field The `description` field helps agents understand which repositories and use cases this connection covers. **Example**: ``` Production infrastructure repositories. Key repositories: - firetiger-inc/core - Main application code - firetiger-inc/infra - Terraform and deployment configs - firetiger-inc/docs - Documentation Use for investigating deployment issues, code changes, and CI/CD failures. ``` ## Best Practices - **Limit repository access** - Only grant access to repositories that agents need for investigations - **Use descriptive names** - Helps agents select the right connection when multiple GitHub accounts are connected - **Document repository purposes** - Include which repositories are relevant for different investigation types ## GitHub App Page The Firetiger GitHub App is available at: [https://github.com/apps/firetiger-agent](https://github.com/apps/firetiger-agent) ## Related Documentation - [GitHub Webhook Ingestion](./github-webhook.txt) - Details on GitHub event ingestion - [GitHub Apps Documentation](https://docs.github.com/en/apps) ### GitHub Webhooks When you install the [Firetiger GitHub App](./github.txt), Firetiger automatically ingests GitHub webhook events for your connected repositories. ## Supported Events The following event types are captured: - **Push events** - Code pushes to branches - **Pull request events** - PR opened, closed, merged, etc. - **Workflow run events** - GitHub Actions workflow executions - **Workflow job events** - Individual job status within workflows - **Check run events** - CI/CD check results - **Check suite events** - Groups of check runs - **Installation events** - App installation changes ## Table All GitHub events are stored in the `github_events` table. ### Incident.io ## Setup Instructions ### 1. Create API Key Go to https://app.incident.io/settings/api-keys and create an API key with the following permissions: - "View data, like public incidents and organisation settings" - "Create incidents" - "Edit incidents" - "View all incident data, including private incidents" Copy the generated API key. ### 2. Configure Connection Go to {{ site.ui_url }}/settings/connections and create a new HTTP connection with: **Base URL:** ``` https://api.incident.io ``` **Allowed Routes:** ``` GET /v2/incidents POST /v2/incidents GET /v2/incidents/{id} POST /v2/incidents/{id}/actions/edit GET /v1/severities GET /v1/severities/{id} GET /v1/incident_statuses GET /v1/incident_statuses/{id} ``` **Headers:** ``` Authorization: Bearer Content-Type: application/json ``` Replace `` with the API key you copied from step 1. ### 3. Add Description In the Description field, paste the content from the markdown block below: ````markdown The incident.io Incidents API v2 provides endpoints to create, read, list, and edit incidents. Incidents are a core resource in incident.io, on which many other resources (actions, etc.) are created. ## Available Operations ### 1. List Incidents **Purpose:** Retrieve a paginated list of all incidents for an organization with advanced filtering capabilities. **Query Parameters:** - `page_size` (integer, optional): Number of records to return (default: 25, max: 500) - `after` (string, optional): Incident ID for pagination - returns incidents after this ID - `status` (object, optional): Filter by incident status ID - Operators: `one_of`, `not_in` - Example: `status[one_of]=01GBSQF3FHF7FWZQNWGHAVQ804` - `status_category` (object, optional): Filter by status category - Operators: `one_of`, `not_in` - Values: `triage`, `declined`, `merged`, `canceled`, `live`, `learning`, `closed` - Example: `status_category[one_of]=live` - `created_at` (object, optional): Filter by creation timestamp - Operators: `gte` (greater than or equal), `lte` (less than or equal), `date_range` - Format: ISO date (YYYY-MM-DD) - Example: `created_at[gte]=2024-05-01` - Date range example: `created_at[date_range]=2024-12-02~2024-12-08` - `updated_at` (object, optional): Filter by last update timestamp - Operators: `gte`, `lte`, `date_range` - Format: ISO date (YYYY-MM-DD) - Example: `updated_at[lte]=2024-12-31` - `severity` (object, optional): Filter by severity ID or rank - Operators: `one_of`, `not_in`, `gte`, `lte` - Example: `severity[gte]=01GBSQF3FHF7FWZQNWGHAVQ804` - `incident_type` (object, optional): Filter by incident type ID - Operators: `one_of`, `not_in` - Example: `incident_type[one_of]=01GBSQF3FHF7FWZQNWGHAVQ804` - `incident_role` (object, optional): Filter by incident role assignment status - Operators: `one_of`, `is_blank` - Format: `incident_role[ROLE_ID][operator]=value` - Example: `incident_role[01GBSQF3FHF7FWZQNWGHAVQ804][is_set]=true` - `custom_field` (object, optional): Filter by custom field values - Operators vary by field type - Format: `custom_field[FIELD_ID][operator]=value` - Example: `custom_field[01GBSQF3FHF7FWZQNWGHAVQ804][one_of]=XYZ` - `mode` (object, optional): Filter by incident mode - Operator: `one_of` - Values: `standard`, `retrospective`, `test`, `tutorial` - Default: `{"one_of": ["standard", "retrospective"]}` (excludes test and tutorial) - Example: `mode[one_of]=retrospective` **Important Notes:** - All query parameters must be URI encoded - Multiple filters can be combined (incidents must match ALL filters) - Maximum page size is 250 - By default, test and tutorial incidents are excluded unless explicitly requested via `mode` filter **Response:** ```json { "incidents": [ { "id": "01FDAG4SAP5TYPT98WGR2N7W91", "reference": "INC-123", "name": "Our database is sad", "summary": "Our database is really really sad, and we don't know why yet.", "status": {...}, "severity": {...}, "incident_type": {...}, "mode": "standard", "visibility": "public", "created_at": "2021-08-17T13:28:57.801578Z", "updated_at": "2021-08-17T13:28:57.801578Z", "slack_channel_id": "C02AW36C1M5", "slack_channel_name": "inc-165-green-parrot", "permalink": "https://app.incident.io/incidents/123" } ], "pagination_meta": { "after": "01FCNDV6P870EA6S7TK1DSYDG0", "page_size": 25, "total_record_count": 238 } } ``` **Example Request:** ```json { "method": "GET", "path": "/v2/incidents", "query_params": { "status_category[one_of]": "live", "page_size": "25" } } ``` --- ### 2. Create Incident **Purpose:** Create a new incident. When mode is set to "retrospective", the incident will not be announced in Slack. **Request Body Fields:** Required fields: - `idempotency_key` (string, required): Unique string to prevent duplicate incident creation - `visibility` (string, required): Incident visibility - `public` (open to all in Slack workspace) or `private` (invite-only) Optional fields: - `name` (string): Short explanation/title of the incident - `summary` (string): Detailed description of the incident - `severity_id` (string): ID of the severity level to assign - `incident_type_id` (string): ID of the incident type - `incident_status_id` (string): ID of the status to assign - `mode` (string): Incident mode - `standard`, `retrospective`, `test`, or `tutorial` - `slack_team_id` (string): Slack Team ID to create the incident in - `slack_channel_name_override` (string): Custom name for the Slack channel - `custom_field_entries` (array): Array of custom field values - Each entry contains: - `custom_field_id` (string): ID of the custom field - `values` (array): Array of value objects with appropriate value type fields - `incident_role_assignments` (array): Array of role assignments - Each assignment contains: - `incident_role_id` (string): ID of the role - `assignee` (object): Can specify by `id`, `email`, or `slack_user_id` - `incident_timestamp_values` (array): Array of timestamp values - Each value contains: - `incident_timestamp_id` (string): ID of the timestamp field - `value` (string): ISO 8601 timestamp - `retrospective_incident_options` (object): Options for retrospective incidents - `slack_channel_id` (string): Existing Slack channel ID - `postmortem_document_url` (string): URL to postmortem document - `external_id` (integer): External reference ID **Response:** ```json { "incident": { "id": "01FDAG4SAP5TYPT98WGR2N7W91", "reference": "INC-123", "name": "Our database is sad", "summary": "Our database is really really sad, and we don't know why yet.", "mode": "standard", "visibility": "public", "created_at": "2021-08-17T13:28:57.801578Z", "updated_at": "2021-08-17T13:28:57.801578Z", "permalink": "https://app.incident.io/incidents/123", ... } } ``` **Example Request:** ```json { "method": "POST", "path": "/v2/incidents", "body": "{\"idempotency_key\": \"alert-uuid-12345\", \"name\": \"Database connection pool exhausted\", \"summary\": \"Production database connection pool has reached capacity\", \"severity_id\": \"01FH5TZRWMNAFB0DZ23FD1TV96\", \"incident_type_id\": \"01FH5TZRWMNAFB0DZ23FD1TV96\", \"mode\": \"standard\", \"visibility\": \"public\"}" } ``` --- ### 3. Get Incident (Show) **Purpose:** Retrieve a single incident by its ID or reference number. **Path Parameters:** - `id` (string, required): The incident's full ID (e.g., `01FDAG4SAP5TYPT98WGR2N7W91`) OR the numeric part of its reference (e.g., `123` for incident `INC-123`) **Response:** ```json { "incident": { "id": "01FDAG4SAP5TYPT98WGR2N7W91", "reference": "INC-123", "name": "Our database is sad", "summary": "Our database is really really sad, and we don't know why yet.", "call_url": "https://zoom.us/foo", "created_at": "2021-08-17T13:28:57.801578Z", "updated_at": "2021-08-17T13:28:57.801578Z", "creator": {...}, "custom_field_entries": [...], "incident_role_assignments": [...], "incident_status": {...}, "incident_timestamp_values": [...], "incident_type": {...}, "severity": {...}, "mode": "standard", "visibility": "public", "slack_channel_id": "C02AW36C1M5", "slack_channel_name": "inc-165-green-parrot", "slack_team_id": "T02A1FSLE8J", "permalink": "https://app.incident.io/incidents/123", "postmortem_document_url": "https://docs.google.com/my_doc_id", "has_debrief": false, "workload_minutes_total": 60.7, "workload_minutes_working": 20, "workload_minutes_sleeping": 0, "workload_minutes_late": 40.7 } } ``` **Example Requests:** ```json { "method": "GET", "path": "/v2/incidents/01FDAG4SAP5TYPT98WGR2N7W91" } ``` ```json { "method": "GET", "path": "/v2/incidents/123" } ``` --- ### 4. Edit Incident **Purpose:** Edit properties of an existing incident such as severity, status, custom fields, role assignments, etc. Only provided fields will be updated (omitted fields are ignored). **Path Parameters:** - `id` (string, required): The unique identifier of the incident to edit **Request Body Fields:** Required fields: - `incident` (object, required): Object containing fields to update - `notify_incident_channel` (boolean, required): Whether to send Slack notifications about the update (won't work if channel is archived) The `incident` object can contain any of these optional fields: - `name` (string): Updated incident name - `summary` (string): Updated incident summary - `severity_id` (string): New severity ID - `incident_status_id` (string): New status ID - `call_url` (string): Video call URL for the incident - `slack_channel_name_override` (string): Override Slack channel name - `custom_field_entries` (array): Update custom field values (same structure as create) - `incident_role_assignments` (array): Update role assignments (same structure as create) - `incident_timestamp_values` (array): Update timestamp values (same structure as create) **Response:** ```json { "incident": { "id": "01FDAG4SAP5TYPT98WGR2N7W91", "reference": "INC-123", "name": "Our database is sad", "summary": "Our database is really really sad, and we don't know why yet.", ... } } ``` **Example Request:** ```json { "method": "POST", "path": "/v2/incidents/01FDAG4SAP5TYPT98WGR2N7W91/actions/edit", "body": "{\"incident\": {\"severity_id\": \"01G0J1EXE7AXZ2C93K61WBPYEH\", \"summary\": \"Database connection pool exhausted - identified root cause in connection timeout config\"}, \"notify_incident_channel\": true}" } ``` --- ### 5. List Severities **Purpose:** Retrieve all incident severities configured for the organization. Severities are required when creating incidents and help categorize incidents by urgency/impact. **Response:** ```json { "severities": [ { "id": "01FCNDV6P870EA6S7TK1DSYDG0", "name": "Minor", "description": "Issues with **low impact**.", "rank": 1, "created_at": "2021-08-17T13:28:57.801578Z", "updated_at": "2021-08-17T13:28:57.801578Z" } ] } ``` **Example Request:** ```json { "method": "GET", "path": "/v1/severities" } ``` --- ### 6. Get Severity **Purpose:** Retrieve a single severity by its ID. **Path Parameters:** - `id` (string, required): Unique identifier of the severity **Response:** ```json { "severity": { "id": "01FCNDV6P870EA6S7TK1DSYDG0", "name": "Minor", "description": "Issues with **low impact**.", "rank": 1, "created_at": "2021-08-17T13:28:57.801578Z", "updated_at": "2021-08-17T13:28:57.801578Z" } } ``` **Example Request:** ```json { "method": "GET", "path": "/v1/severities/01FCNDV6P870EA6S7TK1DSYDG0" } ``` --- ### 7. List Incident Statuses **Purpose:** Retrieve all incident statuses configured for the organization. Statuses are required when creating or editing incidents and help communicate where an incident is in its lifecycle. **Response:** ```json { "incident_statuses": [ { "id": "01FCNDV6P870EA6S7TK1DSYD5H", "name": "Closed", "description": "Impact has been **fully mitigated**, and we're ready to learn from this incident.", "category": "triage", "rank": 4, "created_at": "2021-08-17T13:28:57.801578Z", "updated_at": "2021-08-17T13:28:57.801578Z" } ] } ``` **Important Notes:** - Status categories include: `triage`, `declined`, `merged`, `canceled`, `live`, `learning`, `closed` - Lower rank numbers appear first in lists - The `triage` and `declined` statuses are special and cannot be modified **Example Request:** ```json { "method": "GET", "path": "/v1/incident_statuses" } ``` --- ### 8. Get Incident Status **Purpose:** Retrieve a single incident status by its ID. **Path Parameters:** - `id` (string, required): Unique identifier of the incident status **Response:** ```json { "incident_status": { "id": "01FCNDV6P870EA6S7TK1DSYD5H", "name": "Closed", "description": "Impact has been **fully mitigated**, and we're ready to learn from this incident.", "category": "triage", "rank": 4, "created_at": "2021-08-17T13:28:57.801578Z", "updated_at": "2021-08-17T13:28:57.801578Z" } } ``` **Example Request:** ```json { "method": "GET", "path": "/v1/incident_statuses/01FCNDV6P870EA6S7TK1DSYD5H" } ``` --- ## Best Practices 1. **Idempotency:** Always use unique `idempotency_key` values when creating incidents to prevent duplicates 2. **Avoid Automation Spam:** Be careful with automated incident creation - duplicate incidents can be distracting and impact reporting 3. **Pagination:** When listing incidents, use the `after` parameter with the last incident ID from `pagination_meta` to fetch the next page 4. **Filtering:** Combine multiple filters to narrow down incidents efficiently 5. **Partial Updates:** When editing, only include fields you want to change - omitted fields remain unchanged 6. **Reference Shortcuts:** You can use just the numeric part of an incident reference (e.g., `123` instead of full ID) when getting or editing incidents 7. **Retrospective Mode:** Use `mode: "retrospective"` when importing historical incidents to avoid Slack notifications 8. **URI Encoding:** Always URI encode query parameters, especially when using special characters or operators 9. **Severities and Statuses:** Always list available severities and statuses first before creating/editing incidents to ensure you're using valid IDs ```` ## Webhook Ingest (optional) Send incident.io events into Firetiger as they happen. Events land in Iceberg tables under `incident-io/events/{event_type}` (one table per event type, schema inferred on first event). This is separate from the API connection above and can be enabled in addition to it. The API connection uses the `api_token` field; webhook ingest uses `signing_secret`. ### 1. Generate the webhook URL In the connection's settings, click **Generate Webhook URL**. Copy the URL that appears in the **Webhook URL** field. ### 2. Add the webhook in incident.io Go to → **Add webhook**: - **URL**: paste the URL from step 1 - **Events**: select the event types to forward (e.g. `incident.created`, `incident.updated`, `alert.fired`) incident.io shows the **signing secret** once after saving. Copy it. ### 3. Save the signing secret In the connection's settings, paste the secret into the **Signing Secret** field and save. Inbound deliveries are verified with HMAC-SHA256 (Svix scheme); deliveries with no/invalid signature are rejected. ### Cursor [Cursor](https://cursor.com) is an AI code editor whose **cloud agents** can read a GitHub issue, propose a fix as a pull request, and iterate on review comments. A Cursor connection in Firetiger surfaces Cursor in the `Fix ▾` dropdown on every issue — clicking it opens a Cursor session seeded with the issue's description and investigation details. This page covers setting up the connection. For the end-to-end workflow (what happens after you click **Fix**), see [Fixing issues with coding agents](../../guides/fixing-issues-with-coding-agents.txt). **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/integrations/connections`. ## Installation 1. Generate a Cursor API key at [cursor.com/dashboard/cloud-agents](https://cursor.com/dashboard/cloud-agents) under **My User API Keys**. The key should start with `key_`. 2. In the Firetiger UI, navigate to **Integrations → Connections**, click **New Connection**, and select **Cursor** from the picker. 3. Fill in the form: | Field | Description | |:------|:------------| | **Display name** | Shows up verbatim in the `Fix ▾` dropdown. If you have multiple Cursor keys (e.g., one per team), give each a distinct name like *"Cursor — platform team"*. | | **Description** | Free-text context. Not shown to Cursor itself; just helps your teammates recognize the connection. | | **API Key** | Paste the `key_...` value from step 1. Stored encrypted and never returned via the API; omit it on subsequent edits to keep the existing value. | 4. Click **Create Connection**. Cursor now appears in the `Fix ▾` dropdown on any issue. ## What gets created A single [Connection](../../api-reference/connections.txt) resource with: | Field | Value | |:------|:------| | `connection_id` | Whatever you supplied in **Display name**, kebab-cased, or an auto-generated `cursor-...` id | | `connection_type` | `CONNECTION_TYPE_CURSOR` | | `display_name` | The value you entered | | `secret_id` | Managed by Firetiger's secrets backend — the API key itself is never returned | No webhooks, no GitHub App install, no extra side effects — just a stored credential and a row that shows up in the `Fix` dropdown. ## Multiple Cursor connections You can register as many Cursor connections as you want. Each one renders as a separate row in the `Fix ▾` dropdown with its own display name, so you can scope keys per team, environment, or seat allocation without forcing users to pick between them at configuration time — they pick at fix time. ## Revoking a key To rotate a compromised or expired Cursor key: 1. Revoke it at [cursor.com/dashboard/cloud-agents](https://cursor.com/dashboard/cloud-agents). 2. In Firetiger, go to **Integrations → Connections**, find the Cursor connection, click **Edit**, and paste the new key. If you omit the API key on edit, Firetiger keeps the previous value — useful for updating only the display name or description without touching the credential. ## Capabilities A configured Cursor connection enables: - **Fix-from-issue** — `Fix ▾ → Cursor` on any issue detail or issue-card surface, seeded with the issue's description and details. - **Multiple simultaneous sessions** — clicking Cursor on different issues launches independent Cursor agent sessions that work in parallel. ## Related - [Fixing issues with coding agents](../../guides/fixing-issues-with-coding-agents.txt) — end-to-end workflow, from `Fix ▾` click to closed issue - [Coding Agents API](../../api-reference/coding-agents.txt) — programmatic access to launch / list / get sessions - [Connections API](../../api-reference/connections.txt) — create and update the underlying Cursor connection via API ### Pylon Connections Pylon connections enable agents to create internal issues and look up customer accounts in Pylon. Create and manage connections via the web UI at `{{ site.ui_url }}/settings/connections` ## Overview [Pylon](https://usepylon.com) is a customer support platform. With this connection, agents can: - Create internal issues for escalation and tracking - Search for customer accounts by domain - Retrieve account details for context during investigations ## Setup Instructions ### 1. Generate a Pylon API Token 1. Log in to Pylon at [app.usepylon.com](https://app.usepylon.com) 2. Navigate to **Settings** > **API Tokens** 3. Click **Create API Token** 4. Name your token (e.g., "Firetiger Integration") 5. Copy and securely store the generated token See the [Pylon API Authentication docs](https://docs.usepylon.com/pylon-docs/developer/api/authentication) for details. ### 2. Create the Connection In the Firetiger UI, create a new Pylon connection with your API token. ## Connection Parameters | Parameter | Type | Required | Description | | ----------- | ------ | -------- | -------------------- | | `api_token` | string | Yes | Your Pylon API token | ## Agent Capabilities Once configured, agents can use the following tools: | Tool | Description | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------ | | **CreateIssue** | Creates an internal issue in Pylon (not visible to customers). Supports title, HTML body, account linking, priority, and tags. | | **SearchAccounts** | Finds customer accounts by domain. | | **GetAccount** | Retrieves full details for a specific account. | | **ListAccounts** | Lists all accounts with pagination. | ## Description Field Use the description field to guide agents on when and how to use this connection, and where relevant, with any specific details about how your Pylon instance is structured. **Example**: ``` Pylon connection for internal issue creation and account lookups. Use cases: - Create internal issues when monitoring detects customer-impacting problems - Look up customer accounts by domain during investigations - Get account context for triage and escalation Note: Issues created are INTERNAL only (not visible to customers). ``` ## API Reference If creating connections via the API instead of the UI: ```json { "display_name": "Pylon - Internal Issues", "description": "Pylon connection for internal issue creation...", "connection_details": { "pylon": { "api_token": "pylon_api_xxxxxxxxxxxxx" } } } ``` ### WorkOS Connect Firetiger agents to your WorkOS account so they can look up users, inspect SSO connections, browse directory sync data, and review audit log events. This gives agents the context they need to investigate authentication issues, onboarding problems, and access-related incidents. **What agents can do with WorkOS:** - Look up users by email or ID to check account status and email verification - List organization memberships to understand access and roles - Inspect SSO connections and their state - Browse directory sync data (users, groups) to correlate identity provider state - Review audit log events during incident investigation ## Setup 1. In your [WorkOS Dashboard](https://dashboard.workos.com), create or copy an [API key](https://workos.com/docs/reference/api-authentication) (starts with `sk_`). 2. In Firetiger, go to **Settings > Connections** and click **New Connection**. 3. Select **WorkOS**, paste your API key, and save. **Read-only mode** (enabled by default) restricts agents to GET requests only. Disable it only if you want agents to create or update resources. ## Agent Tools WorkOS connections provide two tools via OpenAPI: - **`openapi_schema`** — discovers available WorkOS API endpoints - **`openapi_request`** — makes authenticated requests to `api.workos.com` ## API Coverage | Resource | Description | |:---------|:------------| | Users | CRUD, email verification, auth factors | | Organization Memberships | CRUD, activate/deactivate | | Organizations | CRUD | | Invitations | Send, list, get, accept, resend, revoke | | SSO Connections | List, get, delete | | Directories | List, get, delete | | Directory Users / Groups | List, get | | Audit Log Events | Create | | Events | List | ### Inspect [Inspect](https://inspect.ramp.engineering) is Ramp's internal coding-agent platform. An Inspect connection surfaces Inspect in the `Fix ▾` dropdown on every Firetiger issue — clicking it opens an Inspect session seeded with the issue's description and investigation details. This page covers setting up the connection. For the end-to-end workflow (what happens after you click **Fix**), see [Fixing issues with coding agents](../../guides/fixing-issues-with-coding-agents.txt). **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/integrations/connections`. ## Installation 1. Get an Inspect bearer token from the Ramp Inspect team. Tokens start with `riu_` (user-scoped) or `ris_` (service-scoped). 2. In the Firetiger UI, navigate to **Integrations → Connections**, click **New Connection**, and select **Inspect** from the picker. 3. Fill in the form: | Field | Description | |:------|:------------| | **Display name** | Shows up verbatim in the `Fix ▾` dropdown. Use a descriptive name if you register multiple tokens (e.g., per team). | | **Description** | Free-text context for your teammates. Not sent to Inspect. | | **Server URL** | Pre-filled with `https://inspect.ramp.engineering` (Ramp production). Change only if you're pointing at a non-production Inspect deployment (staging, regional, or a private instance). | | **API Key** | Paste the `riu_...` or `ris_...` bearer token from step 1. Stored encrypted and never returned via the API; omit it on subsequent edits to keep the existing value. | 4. Click **Create Connection**. Inspect now appears in the `Fix ▾` dropdown alongside any other coding agents you've configured. ## What gets created A single [Connection](../../api-reference/connections.txt) resource with: | Field | Value | |:------|:------| | `connection_id` | Whatever you supplied in **Display name**, kebab-cased, or an auto-generated `inspect-...` id | | `connection_type` | `CONNECTION_TYPE_INSPECT` | | `display_name` | The value you entered | | `secret_id` | Managed by Firetiger's secrets backend — the bearer token itself is never returned | No webhooks, no GitHub App install, no extra side effects — just a stored credential and a row that shows up in the `Fix` dropdown. ## Multiple Inspect connections You can register as many Inspect connections as you want — each one renders as a distinct row in the `Fix ▾` dropdown with its own display name. Useful for scoping tokens per team or for combining user and service tokens in a single Firetiger workspace. ## Revoking a token To rotate a compromised or expired Inspect token: 1. Revoke it via the Ramp Inspect team. 2. In Firetiger, go to **Integrations → Connections**, find the Inspect connection, click **Edit**, and paste the new token. If you omit the token on edit, Firetiger keeps the previous value — useful for updating only the display name or description without touching the credential. ## Capabilities A configured Inspect connection enables: - **Fix-from-issue** — `Fix ▾ → Inspect` on any issue detail or issue-card surface, seeded with the issue's description and details. - **Multiple simultaneous sessions** — clicking Inspect on different issues launches independent sessions that work in parallel. ## Availability Inspect is Ramp-internal and only surfaced on the Firetiger deployment hosted for Ramp. It is not available on Firetiger Cloud or on other BYOC stacks. If you're evaluating a different coding agent, see [Cursor](cursor.txt) or the [Coding Agents API](../../api-reference/coding-agents.txt) for what Firetiger supports generally. ## Related - [Fixing issues with coding agents](../../guides/fixing-issues-with-coding-agents.txt) — end-to-end workflow, from `Fix ▾` click to closed issue - [Coding Agents API](../../api-reference/coding-agents.txt) — programmatic access to launch / list / get sessions - [Connections API](../../api-reference/connections.txt) — create and update the underlying Inspect connection via API ### Linear Connections Linear connections let agents read and write issues, comments, projects, and users in a [Linear](https://linear.app) workspace. Authentication uses Linear's OAuth flow with `actor=app`, so agent activity appears in Linear under the Firetiger app identity rather than impersonating a real user. ## Installation 1. Navigate to **Integrations** in the Firetiger UI 2. Click **New Connection** 3. Select **Linear** as the connection type 4. Click **Install Linear App** 5. In the Linear authorization screen, select your workspace and click **Allow** The connection is created automatically with your organization details. The OAuth refresh token is stored as a secret and renewed transparently. ## What Gets Stored | Field | Source | |-------|--------| | `organization_id` | Linear API | | `organization_name` | Linear API | | `access_token` | OAuth (refreshed automatically) | | `refresh_token` | OAuth | | `scopes` | OAuth grant | ## Tools | Tool | Description | |------|-------------| | `linear_create_issue` | Create a new issue | | `linear_update_issue` | Update an existing issue (status, assignee, labels, etc.) | | `linear_get_issue` | Fetch a single issue by ID or identifier (e.g., `ENG-123`) | | `linear_list_issues` | List/search issues with filters | | `linear_list_issue_statuses` | List workflow states for a team | | `linear_list_issue_labels` | List labels | | `linear_list_projects` | List projects | | `linear_list_users` | List workspace members | | `linear_list_comments` | List comments on an issue | | `linear_create_comment` | Post a comment on an issue | Agents typically call `linear_list_issue_statuses` and `linear_list_issue_labels` once at the start of a workflow to resolve names to IDs before creating or updating issues. ## Description Field Document which teams and projects agents should target, and the conventions for issue creation: ``` Engineering Linear workspace. Teams: - ENG: backend services (default for production incident issues) - WEB: frontend (default for UI bugs) Conventions: - Production incidents → team=ENG, label="incident", state="Triage" - Bugs from monitoring → label="auto-detected", priority=2 - Always link the originating investigation URL in the description ``` ## Re-authorization To refresh permissions or reconnect a workspace: 1. Navigate to **Integrations** 2. Find your Linear connection 3. Delete and recreate the connection — Linear OAuth scopes are not additive ## Best Practices - **One workspace per connection** — separate connections for separate Linear orgs avoids agents posting issues to the wrong place - **Document the default team/project** in the description so agents don't have to guess - **Use labels to mark agent-generated issues** so you can audit and filter agent activity in Linear ### Clerk Connections Clerk connections let agents call [Clerk's Backend API](https://clerk.com/docs/reference/backend-api) to inspect users, organizations, sessions, and invitations. Authentication uses a Clerk Secret Key as a Bearer token. **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/settings/connections` ## Setup 1. Open the Clerk Dashboard for the instance you want to connect 2. Navigate to **API Keys** 3. Copy the **Secret Key** — it starts with `sk_live_` (production) or `sk_test_` (development) ## Connection Parameters ### Authentication Clerk's API uses a single Bearer-token auth scheme. ```json "bearer_token": { "token": "sk_live_..." } ``` ### Optional Parameters | Parameter | Type | Description | Default | |-----------|------|-------------|---------| | `read_only` | bool | Advisory hint to the agent that only `GET` requests are allowed | `false` | `read_only` is **advisory** — it surfaces in the agent's connection prompt and the OpenAPI calling skill, steering the agent toward `GET`-only usage. It is **not** enforced by the network proxy or the shell environment, so a Clerk Secret Key with write permissions can still issue mutating requests if the agent ignores the hint. Scope the API key itself to read-only when you need a hard guarantee. ## Example Connection ```json { "display_name": "Production Clerk", "description": "Production Clerk instance — user/org lookup for incident response", "connection_details": { "clerk": { "bearer_token": { "token": "sk_live_..." }, "read_only": true } } } ``` ## Description Field Document which Clerk instance this is and what it's used for. Useful pointers for agents: ``` Production Clerk instance for app.example.com. Common workflows: - User lookup by email → GET /v1/users?email_address= - Organization membership → GET /v1/organizations/{org_id}/memberships - Recent sessions for a user → GET /v1/sessions?user_id= ``` ## Best Practices - **Enable `read_only: true`** for incident-response and customer-support agents — they almost never need to write to Clerk - **Use `sk_test_*` keys for non-production deployments** so agent activity in dev is isolated from production user data - **Rotate keys after personnel changes** — Clerk Secret Keys grant full backend access; treat them like database admin credentials ### Vanta Connections Vanta connections let agents call the [Vanta API](https://developer.vanta.com/) to inspect controls, evidence, vendors, and audits in your [Vanta](https://www.vanta.com/) workspace. Authentication uses OAuth 2.0 Client Credentials — Firetiger exchanges client ID and secret for an access token at request time and refreshes it automatically. **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/settings/connections` ## Setup 1. In the Vanta web app, open **Settings → API & Integrations** 2. Create a new API client and copy the **client ID** and **client secret** 3. Note the scopes you select — they determine which API endpoints the connection can call ## Connection Parameters ### Authentication Vanta only supports OAuth Client Credentials. ```json "oauth_client_credentials": { "token_url": "https://api.vanta.com/oauth/token", "client_id": "", "client_secret": "", "scopes": "vanta-api.all:read" } ``` | Field | Description | |-------|-------------| | `token_url` | Vanta's token endpoint — `https://api.vanta.com/oauth/token` | | `client_id` | API client ID from Vanta | | `client_secret` | API client secret from Vanta | | `scopes` | Space-separated scopes (e.g., `vanta-api.all:read`) | ### Optional Parameters | Parameter | Type | Description | Default | |-----------|------|-------------|---------| | `read_only` | bool | Advisory hint to the agent that only `GET` requests are allowed — not enforced by the proxy. Scope the OAuth client to read-only scopes for a hard guarantee | `false` | ## Example Connection ```json { "display_name": "Vanta Compliance", "description": "Vanta compliance API — control status, evidence, and vendor lookups", "connection_details": { "vanta": { "oauth_client_credentials": { "token_url": "https://api.vanta.com/oauth/token", "client_id": "", "client_secret": "", "scopes": "vanta-api.all:read" }, "read_only": true } } } ``` ## Best Practices - **Request only the scopes agents need** — `vanta-api.all:read` is fine for read-heavy workflows; avoid write scopes unless an agent must modify state in Vanta - **Enable `read_only: true`** for any agent that doesn't need to mutate evidence or controls - **Rotate client secrets periodically** in the Vanta dashboard; the OAuth token cache will pick up the new secret on the next refresh ### Tembo Connections Tembo connections let Firetiger launch coding-agent runs on [Tembo](https://app.tembo.io) when issues are created or updated. Each connection runs in one of two modes: - **Task mode** — Firetiger POSTs to `/task/create` with the prompt + GitHub repo for an ad-hoc agent run. - **Automation mode** — Firetiger POSTs to `/automation/{key}/trigger` with issue context as the event payload, letting you pre-configure agent, skill, and model selection inside Tembo. Tembo sits alongside Firetiger's other coding-agent integrations ([Cursor](cursor.txt){% if site.deployment_name == "ft-ramp" %}, [Inspect](inspect.txt){% endif %}). Pick the one that matches the workflow you want. **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/settings/connections` ## Setup 1. Sign in to [app.tembo.io](https://app.tembo.io) 2. Navigate to **Settings → API Keys** and copy your bearer token 3. (Automation mode only) Create the automation in Tembo first and copy its key or UUID ## Connection Parameters ### Required Parameters | Parameter | Type | Description | |-----------|------|-------------| | `api_key` | string | Bearer token for `api.tembo.io` | ### Mode (exactly one) #### Task Mode ```json "task": { "default_agent": "claudeCode:claude-sonnet-4-6" } ``` | Field | Description | Default | |-------|-------------|---------| | `default_agent` | Tembo agent identifier passed as the `agent` field on `/task/create` | Tembo's org-level default | #### Automation Mode ```json "automation": { "automation_key": "" } ``` | Field | Description | |-------|-------------| | `automation_key` | Tembo automation key or UUID — required | ## Example Connections **Task mode**: ```json { "display_name": "Tembo Ad-Hoc", "description": "Tembo task launcher for one-off coding-agent runs from Firetiger issues", "connection_details": { "tembo": { "api_key": "", "task": { "default_agent": "claudeCode:claude-sonnet-4-6" } } } } ``` **Automation mode**: ```json { "display_name": "Tembo Triage Automation", "description": "Triggers the 'incident-triage' Tembo automation with issue context", "connection_details": { "tembo": { "api_key": "", "automation": { "automation_key": "incident-triage" } } } } ``` ## Best Practices - **Use automation mode when agent/skill/model selection is stable** — it keeps that configuration in Tembo where the rest of your team can edit it - **Use task mode for experimental workflows** — easier to iterate on the prompt from Firetiger without redeploying a Tembo automation - **One connection per automation** — connections are 1:1 with a launch target, so a separate connection per workflow is the cleanest mapping ### Devin [Devin](https://devin.ai) is Cognition's cloud coding agent — it can read an issue, work through a fix on its own infrastructure, and open a pull request for review. A Devin connection in Firetiger surfaces Devin in the `Fix ▾` dropdown on every issue, and can be selected as the [Autofix](../../api-reference/autofix.txt) agent so newly actionable issues are dispatched to Devin automatically. This page covers setting up the connection. For the end-to-end workflow (what happens after you click **Fix**), see [Fixing issues with coding agents](../../guides/fixing-issues-with-coding-agents.txt). **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/integrations/connections`. ## Prerequisites - A Devin plan that includes **service users** (Devin's machine-credential mechanism). Firetiger uses Devin's v3 organization-scoped API, which authenticates with service-user keys (`cog_...`) — legacy personal API keys (`apk_...`) are **not** supported. - Permission to create service users in your Devin organization (**Settings → Service users**). ## Installation 1. In [Devin settings](https://app.devin.ai/settings), go to **Service users** and create a service user for Firetiger. Grant it these permissions: | Permission | Why Firetiger needs it | |:-----------|:-----------------------| | **ManageOrgSessions** | Launch new Devin sessions when you click **Fix** (or Autofix does) | | **ViewOrgSessions** | Read session status back on demand | Copy the generated API key — it starts with `cog_`. While you're on this page, also note your **organization ID** (`org-...`); you may need it in step 3. 2. In the Firetiger UI, navigate to **Integrations → Connections**, click **New Connection**, and select **Devin** from the picker. 3. Fill in the form: | Field | Description | |:------|:------------| | **Display name** | Shows up verbatim in the `Fix ▾` dropdown. If you have multiple Devin connections (e.g., one per team), give each a distinct name like *"Devin — platform team"*. | | **Description** | Free-text context. Not shown to Devin itself; just helps your teammates recognize the connection. | | **API Key** | Paste the `cog_...` value from step 1. Stored encrypted and never returned via the API; omit it on subsequent edits to keep the existing value. | | **Organization ID** | Optional. Leave blank and Firetiger auto-detects it from the key at save time via Devin's `/v3/self` endpoint. Auto-detection requires the **ReadAccountMeta** permission (an enterprise-level grant many minimal service users lack) — if the save fails asking for the org ID, paste the `org-...` value from step 1. | | **Default repository** | Optional. Fallback GitHub repository (e.g. `https://github.com/owner/repo`) used to seed the Devin session when the issue carries no GitHub link. A GitHub link on the issue takes precedence. Leave empty to require a link on every issue. | | **Max ACU limit** | Optional. Per-session ACU spend cap forwarded to Devin. A useful guardrail when Autofix launches sessions automatically. 0 or empty means no cap. | 4. Click **Create Connection**. Firetiger validates the API key (and organization ID) against Devin before saving, so a bad credential fails fast instead of surfacing later as a broken `Fix` button. Once saved, Devin appears in the `Fix ▾` dropdown on any issue. ## What gets created A single [Connection](../../api-reference/connections.txt) resource with: | Field | Value | |:------|:------| | `connection_id` | Whatever you supplied in **Display name**, kebab-cased, or an auto-generated `devin-...` id | | `connection_type` | `CONNECTION_TYPE_DEVIN` | | `display_name` | The value you entered | | `secret_id` | Managed by Firetiger's secrets backend — the API key itself is never returned | No webhooks, no GitHub App install, no extra side effects — just a stored credential and a row that shows up in the `Fix` dropdown. ## How fixing works Clicking `Fix ▾ → Devin` (or an Autofix dispatch) launches a Devin session seeded with the issue's description and investigation details, plus the repository from the issue's GitHub link (falling back to the connection's **Default repository**). The session opens in Devin's own UI at `app.devin.ai`, and its URL is written onto the issue's links so you can jump back to it later. Devin works through the fix on its own infrastructure and opens a pull request that references the issue's call sign (`Fixes FT-42`), so the merge is tracked through **Verifying Fix → Resolved** like any other coding-agent fix. Firetiger does not poll Devin — session status is fetched on demand when you ask for it. ## Troubleshooting - **Save fails asking for an organization ID** — the key can't auto-detect its org because it lacks the **ReadAccountMeta** permission on Devin's `/v3/self` endpoint. Copy the `org-...` value from the same **Settings → Service users** page where you created the key and paste it into the **Organization ID** field. - **Save rejects the credentials** — check that the key starts with `cog_` (legacy `apk_` keys are not supported), that the service user has both **ManageOrgSessions** and **ViewOrgSessions**, and that the organization ID (if you entered one) belongs to the same org as the key. ## Revoking a key To rotate a compromised or expired Devin key: 1. Revoke the service user's key in Devin under **Settings → Service users**. 2. In Firetiger, go to **Integrations → Connections**, find the Devin connection, click **Edit**, and paste the new key. If you omit the API key on edit, Firetiger keeps the previous value — useful for updating only the display name, default repository, or ACU limit without touching the credential. ## Related - [Fixing issues with coding agents](../../guides/fixing-issues-with-coding-agents.txt) — end-to-end workflow, from `Fix ▾` click to closed issue - [Autofix API](../../api-reference/autofix.txt) — launch a coding agent automatically when an issue becomes Actionable - [Coding Agents API](../../api-reference/coding-agents.txt) — programmatic access to launch / list / get sessions - [Connections API](../../api-reference/connections.txt) — create and update the underlying Devin connection via API ## Communications Connect Firetiger to messaging and email platforms. ### Slack # Firetiger App for Slack [Firetiger](https://www.firetiger.com) is an AI agent platform for production monitoring. Firetiger agents identify and fix problems in production by combining observability data, codebase understanding, and knowledge of your business. The Firetiger app for Slack connects your Slack workspace to Firetiger so that agents can send real-time notifications and alerts directly to your Slack channels. When an agent detects an issue, investigates a root cause, or deploys a fix, it can post updates to the channels you've invited it to — keeping your team informed without leaving Slack. > **AI Disclaimer:** Firetiger uses AI agents to analyze observability data and generate insights. AI-generated content, including messages sent to Slack, may occasionally contain inaccurate or incomplete information. Users should verify critical findings independently. ## Installation 1. Navigate to **Integrations** in the Firetiger UI 2. Click **New Connection** 3. Select **Slack** as the connection type 4. Click **Install App for Slack** 5. In the popup, select your Slack workspace and click **Allow** The connection will be created automatically with your workspace details. ## What Gets Created When you install the Firetiger app for Slack, Firetiger automatically creates a connection with: | Field | Value | |-------|-------| | `connection_id` | `slack-{workspace_id}` (e.g., `slack-t1234567890`) | | `display_name` | Your Slack workspace name | | `description` | `Slack workspace: {workspace_name}` | ## Give Firetiger access to channels Firetiger can only send to (and receive `@mentions` in) channels the app has been **invited to** — channel membership is the source of truth, and there is no allowlist to configure. After connecting the workspace, invite the app to each channel you want it to use: 1. Open the channel in Slack. 2. Type `/invite @Firetiger` (or mention `@Firetiger`) and confirm. The channel becomes available immediately — it shows up under **Channels Firetiger can post to** on the connection page, in the channel picker for triggers, and via the `slack_list_channels` tool. If an agent tries to post to a channel the app isn't in, the send fails with a message telling you to invite the app. `#general` is always blocked, even if the app is invited to it. ## Agent-Specific @mentions After Slack is connected, you can reserve custom Slack handles for individual agents, such as `@checkout-oncall`. Create a **Slack @mention** trigger on the agent's **Plan** page, then create or select a `SlackHandle` and choose the channels where that agent should respond. > Custom handles are Slack user groups, so this feature requires a Slack workspace with user groups — typically **Business+** or **Enterprise Grid**. If an admin restricts user-group creation, ask them to create the Slack user group first, then add that existing handle in Firetiger. See [Create a Custom Slack Handle for an Agent → Slack workspace requirements](../../guides/agent-slack-handles.txt#slack-workspace-requirements). See [Create a Custom Slack Handle for an Agent](../../guides/agent-slack-handles.txt) for the full setup flow. ## Permissions The Firetiger app for Slack requests permissions for messaging, channel access, reactions, custom handles, and user lookup. Key permissions include: | Scope | Purpose | |-------|---------| | `channels:read` | List available channels | | `channels:join` | Join public channels to send messages | | `chat:write` | Send messages to channels | | `chat:write.public` | Send messages to public channels without joining | | `team:read` | Get workspace information | | `usergroups:read` | Find Slack user groups used for custom agent handles | | `usergroups:write` | Create Slack user groups for new custom agent handles | | `users:read` | List users for mentions | ## Re-authorization To update permissions or reconnect a workspace: 1. Navigate to **Integrations** 2. Find your Slack connection 3. Click **Reconnect** or delete and recreate the connection Slack scopes are additive - re-authorizing adds new permissions without removing existing ones. ## Tools Slack connections enable the following agent tools: | Tool | Description | |------|-------------| | `slack-send-message` | Send messages to one or more Slack channels | ### slack-send-message Send notifications to Slack channels. Supports Slack's mrkdwn format for rich text. **Parameters:** | Parameter | Required | Description | |-----------|----------|-------------| | `connection` | Yes | The Slack connection to use | | `channels` | Yes | List of channel names or IDs (e.g., `["#alerts", "C1234567890"]`) | | `message` | Yes | Message content (use Slack mrkdwn: `*bold*`, `_italic_`, `` `code` ``) | | `title` | No | Optional title displayed as a bold header | | `investigation_url` | No | Optional URL to link back to an investigation | ## Description Field The `description` field helps agents understand when to use this Slack workspace. **Example**: ``` Primary workspace for engineering alerts. Use for: - Incident notifications → #incidents - Deployment updates → #deployments - Agent status updates → #firetiger-alerts ``` ## Best Practices - **Invite Firetiger to the channels it should use** - The app can only post to (and be mentioned in) channels it's a member of. Invite it with `/invite @Firetiger`. - **Use descriptive workspace names** - Helps agents select the right workspace when multiple are connected - **Document channel conventions** - Include which channels to use for different alert types - **Limit to necessary workspaces** - Only connect workspaces that agents need to notify ## Privacy & Terms - [Privacy Policy](https://www.firetiger.com/privacy-policy) - [Terms of Service](https://www.firetiger.com/terms-of-service) - [Trust Center](https://trust.firetiger.com) ### SendGrid Event Webhooks Configure SendGrid Event Webhook following the [official documentation](https://www.twilio.com/docs/sendgrid/for-developers/tracking-events/getting-started-event-webhook). ## Endpoint `{{ site.ingest_url }}/sendgrid/` ## SendGrid Configuration 1. Go to **Settings** > **Mail Settings** > **Event Webhook** 2. Enter the Firetiger endpoint URL: `{{ site.ingest_url }}/sendgrid/` 3. Select the event types you want to capture 4. Enable the webhook ## Tables Each SendGrid event type is stored in its own table under `sendgrid/events/{event-type}`: | Event Type | Table | |------------|-------| | processed | `sendgrid/events/processed` | | delivered | `sendgrid/events/delivered` | | deferred | `sendgrid/events/deferred` | | bounce | `sendgrid/events/bounce` | | dropped | `sendgrid/events/dropped` | | open | `sendgrid/events/open` | | click | `sendgrid/events/click` | | spamreport | `sendgrid/events/spamreport` | | unsubscribe | `sendgrid/events/unsubscribe` | | group_unsubscribe | `sendgrid/events/group_unsubscribe` | | group_resubscribe | `sendgrid/events/group_resubscribe` | Tables are created automatically when the first event of that type is received. Schema inference automatically adapts to new fields from SendGrid. ## Example Query ```sql SELECT event.email, event.response, timestamp FROM "sendgrid/events/delivered" WHERE timestamp > '2024-01-01' ``` ### Google Postmaster Tools Connections Google Postmaster Tools connections enable agents to query email deliverability metrics for domains sending to Gmail users. **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/settings/connections` ## Overview Google Postmaster Tools provides insights into: - **Domain/IP Reputation** - How Gmail perceives your sending reputation - **Spam Rates** - Percentage of emails marked as spam by users - **Authentication** - SPF, DKIM, and DMARC success rates - **Encryption** - TLS usage for email transport - **Delivery Errors** - Common delivery issues and their frequency ## Prerequisites Before creating a Google Postmaster Tools connection, complete these setup steps: ### 1. Create a Google Cloud Project 1. Go to [Google Cloud Console](https://console.cloud.google.com/) 2. Create a new project or select an existing one ### 2. Enable the Postmaster Tools API 1. Go to [Enable Postmaster Tools API](https://console.cloud.google.com/flows/enableapi?apiid=gmailpostmastertools.googleapis.com) 2. Select your project and click **Enable** ### 3. Create a Service Account 1. Go to [Service Accounts](https://console.cloud.google.com/iam-admin/serviceaccounts) 2. Click **Create Service Account** 3. Fill in: - **Name**: e.g., "Postmaster API Client" - **ID**: auto-generated - **Description**: "Service account for Postmaster Tools API access" 4. Click **Create and Continue**, then **Done** ### 4. Enable Domain-Wide Delegation 1. Click on the service account you created 2. Go to the **Details** tab 3. Click **Show Advanced Settings** 4. Under **Domain-wide delegation**, click **Enable Google Workspace Domain-wide Delegation** 5. Note the **Client ID** (a numeric ID like `123456789012345678901`) ### 5. Download Service Account Credentials 1. On the service account page, go to the **Keys** tab 2. Click **Add Key** → **Create new key** 3. Choose **JSON** format and click **Create** 4. Save the downloaded JSON file securely ### 6. Configure Domain-Wide Delegation in Google Workspace This step requires **Google Workspace Super Admin** access. 1. Go to [Google Workspace Admin Console](https://admin.google.com/) 2. Navigate to **Security** → **Access and data control** → **API controls** 3. Scroll to **Domain-wide delegation** → Click **Manage Domain Wide Delegation** 4. Click **Add new** 5. Fill in: - **Client ID**: The numeric Client ID from step 4 - **OAuth scopes**: `https://www.googleapis.com/auth/postmaster.readonly` 6. Click **Authorize** ### 7. Verify Postmaster Access The user you'll impersonate must have access to Postmaster Tools: 1. Go to [Gmail Postmaster Tools](https://postmaster.google.com/) 2. Sign in as the user who will be impersonated 3. Verify they can see the domains you want to query ## Connection Parameters ### Required Parameters | Parameter | Type | Description | |-----------|------|-------------| | `service_account_json` | string | The full contents of the service account JSON key file | | `impersonate_user` | string | Email of a user with access to Postmaster Tools (e.g., `admin@yourdomain.com`) | ## Available Tools Once configured, agents have access to two tools: ### ListDomains Lists all domains registered in the Postmaster Tools dashboard. **Use this to**: Discover which domains are available for querying. **Returns**: Domain names and permission levels (OWNER/READER). ### GetTrafficStats Retrieves daily traffic statistics for a domain. **Parameters**: - `domain` (required): The domain to query (e.g., `example.com`) - `start_date` (optional): Start date in YYYY-MM-DD format (defaults to 7 days ago) - `end_date` (optional): End date in YYYY-MM-DD format (defaults to today) **Returns**: - Domain reputation (HIGH, MEDIUM, LOW, BAD) - IP reputations with sample IPs - User-reported spam ratio - SPF/DKIM/DMARC success rates - Encryption ratios (inbound/outbound TLS) - Delivery errors by type ## Description Field Document the domains and use cases for the connection. **Example**: ``` Google Postmaster Tools for monitoring email deliverability to Gmail. Available domains: - example.com - Primary marketing domain - transactional.example.com - Transactional email domain Use this connection to: - Check domain/IP reputation after email campaigns - Investigate spam complaints or delivery issues - Monitor authentication (SPF, DKIM, DMARC) success rates - Track encryption compliance Note: Google requires sufficient email volume for metrics to be available. Domains with low Gmail volume may not show statistics. ``` ## Example Connection ```json { "display_name": "Postmaster Tools - Example Corp", "description": "Google Postmaster Tools for monitoring email deliverability...", "connection_details": { "google_postmaster": { "service_account_json": "{\"type\": \"service_account\", \"project_id\": \"your-project\", ...}", "impersonate_user": "admin@example.com" } } } ``` ## Troubleshooting ### "Not authorized to access this resource" - Verify domain-wide delegation is configured with the correct Client ID - Check that the scope `https://www.googleapis.com/auth/postmaster.readonly` is authorized - Ensure the impersonate_user has access to Postmaster Tools ### "No domains found" - The impersonated user must have verified domains at https://postmaster.google.com/ - Domain verification requires adding DNS records (follow Google's instructions) ### "No traffic statistics available" Google requires sufficient email volume to display statistics. This can happen if: - The domain has low email volume to Gmail users - The date range has no email activity - The domain was recently verified ### API returns 403 - Verify the Postmaster Tools API is enabled in your Google Cloud project - Check that domain-wide delegation is properly configured ## Best Practices - **Use a dedicated service account** - Create a separate service account for Postmaster Tools access - **Impersonate an admin user** - The impersonated user should have access to all domains you need to query - **Monitor regularly** - Set up regular checks for reputation changes or spam spikes - **Document domains** - List available domains in the description field for agent context ## API Version This integration uses the Google Postmaster Tools API v1 (stable). Future versions may add support for v2beta features including compliance status and batch queries. ## Observability Forward telemetry data from observability tools into Firetiger. ### Vector # Configure Vector to send logs, metrics, or traces to Firetiger Vector can send data to Firetiger using the generic HTTP sink with the `/datapoints/` endpoint. Each sink writes to a separate Firetiger table based on the URL path, and Vector's template syntax lets you route data dynamically. ## Configuration Steps In your Vector configuration file(s) (`vector.toml`), add a new HTTP sink using the configuration provided below. Within the `inputs` array, specify your source names (e.g., `["system_logs", "application_logs"]`). Replace `YOUR_INGEST_USERNAME` and `YOUR_INGEST_PASSWORD` with your ingest credentials — see [Authentication](#authentication) below. ### Static table name Route all events from a sink to a single table: ```toml [sinks.firetiger] type = "http" inputs = ["..."] # add your sources here encoding.codec = "json" uri = "{{ site.ingest_url }}/datapoints/vector/logs" method = "post" healthcheck = false compression = "zstd" [sinks.firetiger.auth] strategy = "basic" user = "YOUR_INGEST_USERNAME" password = "YOUR_INGEST_PASSWORD" ``` This sends all events to the `datapoints/vector/logs` table. ### Dynamic table name using Vector templates Use Vector's [template syntax](https://vector.dev/docs/reference/configuration/template-syntax/) to route events to different tables based on event fields: ```toml [sinks.firetiger] type = "http" inputs = ["..."] encoding.codec = "json" uri = "{{ site.ingest_url }}/datapoints/vector/{% raw %}{{ source_type }}{% endraw %}" method = "post" healthcheck = false compression = "zstd" [sinks.firetiger.auth] strategy = "basic" user = "YOUR_INGEST_USERNAME" password = "YOUR_INGEST_PASSWORD" ``` This creates a separate table per source type (e.g., `datapoints/vector/file`, `datapoints/vector/syslog`). ## Supported Formats The `/datapoints/` endpoint accepts: - **JSON array**: `[{"key": "value"}, ...]` - **JSONL / NDJSON**: One JSON object per line (use `Content-Type: application/jsonl`) - **Single JSON object**: Automatically detected as JSONL Vector's `encoding.codec = "json"` sends events as JSON arrays, which is the recommended format. Compression (`gzip`, `zstd`, `deflate`, `snappy`) is supported via the `Content-Encoding` header. ## Authentication Firetiger expects HTTP basic auth for ingestion. Your ingest username and password can be found in the Firetiger UI on the [Settings page]({{ site.ui_url }}/settings). These are the same credentials used for OpenTelemetry collectors and other ingest integrations. Substitute those values for `YOUR_INGEST_USERNAME` and `YOUR_INGEST_PASSWORD` in the configuration examples above. ### Datadog Agent # Forward Traces and Metrics from a Datadog Agent to Firetiger This guide covers configuring a Datadog Agent to forward APM traces and metrics to Firetiger, where they are converted to OpenTelemetry format and stored in Iceberg. For log forwarding, see [DataDog Log Forwarding](datadog-forward.txt). ## Prerequisites - An active Firetiger deployment - Datadog Agent v7+ running in your environment - Firetiger credentials from the **Data Connections** page Your Firetiger ingest endpoint for Datadog data is: ``` {{ site.ingest_url }}/datadog ``` ## Step 1: Build your Firetiger API key 1. Log in to your Firetiger account 2. Navigate to **Data Connections → OpenTelemetry** 3. Note the **Username** and **Password** shown on the page The Datadog Agent takes a single `DD_API_KEY` string, so you need to combine the username and password into one Base64 token: ```bash # Linux / macOS echo -n 'USERNAME:PASSWORD' | base64 ``` Replace `USERNAME` and `PASSWORD` with the values from the Data Connections page. Use the resulting Base64 string wherever `` appears below. ## Option A: Firetiger Only Replace the Datadog backend entirely. All trace and/or metric data flows to Firetiger. The Datadog Agent treats API key values as opaque strings, so the Firetiger token works without issues. ### Traces only ```bash DD_API_KEY= DD_APM_DD_URL={{ site.ingest_url }}/datadog ``` ### Metrics only ```bash DD_API_KEY= DD_DD_URL={{ site.ingest_url }}/datadog ``` ### Traces + Metrics ```bash DD_API_KEY= DD_APM_DD_URL={{ site.ingest_url }}/datadog DD_DD_URL={{ site.ingest_url }}/datadog ``` ## Option B: Dual-Write (Datadog + Firetiger) Keep sending data to Datadog AND add Firetiger as an additional destination. Your existing Datadog setup remains unchanged. The Datadog Agent natively supports additional endpoint variables -- a JSON map of `{url: [api_keys]}`. Each endpoint gets its own API key. The real Datadog key stays in `DD_API_KEY` for the primary destination. ### Traces only ```bash DD_API_KEY= DD_APM_ADDITIONAL_ENDPOINTS='{"{{ site.ingest_url }}/datadog": [""]}' ``` ### Metrics only ```bash DD_API_KEY= DD_ADDITIONAL_ENDPOINTS='{"{{ site.ingest_url }}/datadog": [""]}' ``` ### Traces + Metrics ```bash DD_API_KEY= DD_APM_ADDITIONAL_ENDPOINTS='{"{{ site.ingest_url }}/datadog": [""]}' DD_ADDITIONAL_ENDPOINTS='{"{{ site.ingest_url }}/datadog": [""]}' ``` `DD_APM_ADDITIONAL_ENDPOINTS` is used by APM trace forwarding, while `DD_ADDITIONAL_ENDPOINTS` is used by the metrics forwarder. ## Configuration Examples ### Docker (Firetiger Only -- Traces + Metrics) ```bash docker run -d \ -e DD_API_KEY="" \ -e DD_APM_DD_URL="{{ site.ingest_url }}/datadog" \ -e DD_DD_URL="{{ site.ingest_url }}/datadog" \ -e DD_APM_ENABLED=true \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ -v /proc/:/host/proc/:ro \ -v /sys/fs/cgroup/:/host/sys/fs/cgroup:ro \ gcr.io/datadoghq/agent:7 ``` ### Docker (Dual-Write -- Traces + Metrics) ```bash docker run -d \ -e DD_API_KEY="" \ -e DD_APM_ADDITIONAL_ENDPOINTS='{"{{ site.ingest_url }}/datadog": [""]}' \ -e DD_ADDITIONAL_ENDPOINTS='{"{{ site.ingest_url }}/datadog": [""]}' \ -e DD_APM_ENABLED=true \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ -v /proc/:/host/proc/:ro \ -v /sys/fs/cgroup/:/host/sys/fs/cgroup:ro \ gcr.io/datadoghq/agent:7 ``` ### Docker Compose (Firetiger Only) ```yaml services: datadog-agent: image: gcr.io/datadoghq/agent:7 environment: DD_API_KEY: "" DD_APM_DD_URL: "{{ site.ingest_url }}/datadog" DD_DD_URL: "{{ site.ingest_url }}/datadog" DD_APM_ENABLED: "true" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - /proc/:/host/proc/:ro - /sys/fs/cgroup/:/host/sys/fs/cgroup:ro ``` ### Docker Compose (Dual-Write) ```yaml services: datadog-agent: image: gcr.io/datadoghq/agent:7 environment: DD_API_KEY: "" DD_APM_ADDITIONAL_ENDPOINTS: '{"{{ site.ingest_url }}/datadog": [""]}' DD_ADDITIONAL_ENDPOINTS: '{"{{ site.ingest_url }}/datadog": [""]}' DD_APM_ENABLED: "true" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - /proc/:/host/proc/:ro - /sys/fs/cgroup/:/host/sys/fs/cgroup:ro ``` ### Kubernetes Helm (Firetiger Only) In your `values.yaml` for the Datadog Helm chart: ```yaml datadog: apiKey: "" apm: portEnabled: true env: - name: DD_APM_DD_URL value: "{{ site.ingest_url }}/datadog" - name: DD_DD_URL value: "{{ site.ingest_url }}/datadog" ``` ### Kubernetes Helm (Dual-Write) ```yaml datadog: apiKey: "" apm: portEnabled: true env: - name: DD_APM_ADDITIONAL_ENDPOINTS value: '{"{{ site.ingest_url }}/datadog": [""]}' - name: DD_ADDITIONAL_ENDPOINTS value: '{"{{ site.ingest_url }}/datadog": [""]}' ``` ### datadog.yaml (Firetiger Only) ```yaml api_key: "" apm_config: enabled: true apm_dd_url: "{{ site.ingest_url }}/datadog" dd_url: "{{ site.ingest_url }}/datadog" ``` ### datadog.yaml (Dual-Write) ```yaml api_key: "" apm_config: enabled: true additional_endpoints: "{{ site.ingest_url }}/datadog": - "" additional_endpoints: "{{ site.ingest_url }}/datadog": - "" ``` ## Verify 1. Restart the Datadog Agent after configuration changes 2. Check agent status: ```bash datadog-agent status ``` 3. Look for the APM section -- it should show your configured endpoint 4. Generate some traffic in your application 5. Log in to Firetiger and check for trace data under traces 6. Check for metric data under metrics -- it may take a few minutes for the agent to flush its first metric payload ## Troubleshooting **No trace data appearing in Firetiger?** - Verify the ingest endpoint URL matches `{{ site.ingest_url }}/datadog` - Check that APM is enabled on the agent (`DD_APM_ENABLED=true`) - Ensure your application is instrumented with a Datadog tracing library and sending traces to the agent - Check agent logs for connection errors: `datadog-agent status` or `docker logs ` **No metric data appearing in Firetiger?** - Confirm you set `DD_DD_URL` (Firetiger-only) or `DD_ADDITIONAL_ENDPOINTS` (dual-write) -- these are separate from the APM trace variables - Do not confuse `DD_ADDITIONAL_ENDPOINTS` (metrics) with `DD_APM_ADDITIONAL_ENDPOINTS` (traces) -- they control different forwarders - Metrics are batched by the agent and flushed periodically; wait at least 2 minutes after restarting the agent - For containerized agents, DogStatsD must be enabled if you want custom metrics (`DD_DOGSTATSD_NON_LOCAL_TRAFFIC=true`) **Authentication errors (401)?** - Go to **Data Connections → OpenTelemetry** and verify your username and password - Re-run `echo -n 'USERNAME:PASSWORD' | base64` with the correct values - Ensure the Base64 token is copied exactly with no extra whitespace or quotes - Verify there are no escaping issues in your environment variables **Agent startup warnings about /api/v1/validate?** - These warnings are harmless and do not affect trace or metric delivery - Firetiger handles this endpoint for connectivity checks **Dual-write not working?** - Verify the JSON format of `DD_APM_ADDITIONAL_ENDPOINTS` / `DD_ADDITIONAL_ENDPOINTS` is correct - The value must be a JSON object mapping URL to an array of API keys - Check for proper escaping in your shell or YAML configuration ### DataDog Log Forwarding # Forward logs from DataDog to Firetiger This guide covers forwarding logs from Datadog to Firetiger via a custom log destination. For forwarding traces and metrics via the Datadog Agent, see [Datadog Agent](datadog.txt). ## Configuring a log forwarding custom destination Consult the DataDog docs here: [https://docs.datadoghq.com/logs/log_configuration/forwarding_custom_destinations/?tab=http](https://docs.datadoghq.com/logs/log_configuration/forwarding_custom_destinations/?tab=http) You’ll need three inputs from Firetiger, an ingest endpoint, username, and password (also known as an ingest secret). The ingest endpoint for DataDog is `{{ site.ingest_url }}/datadog/logs` Configure DataDog to use Basic auth, with username and secret provided to you by Firetiger. # Log message handling DataDog sends logs as JSON arrays: `[ { "date": "2025-08-20T06:01:07.323Z", "service": "auth-service", "host": "localhost", "attributes": { "level": "INFO", "service": "auth-service", "host": "localhost", "source": "python-log-generator", "@timestamp": "2025-08-20T06:01:07.323115Z", "tags": "env:dev,version:1.0.0,service:web-server" }, "_id": "AZjGER5YAACoY1YER9GoqwZA", "message": "Authentication token validated", "status": "info", "tags": ["source:undefined", "datadog.submission_auth:api_key"] } ]` This log will be ingested with: - Service: `auth-service` - Host: `localhost` - Timestamp: `2025-08-20T06:01:07.323Z` - Severity: INFO - Message: "Authentication token validated" - Tags parsed as attributes ## Timestamp handling Firetiger uses the top-level **`date`** field as the log record timestamp. The `@timestamp` field inside `attributes`, if present, is stored as part of the attributes and is queryable, but does not affect the record timestamp. ## Severity The **`status`** field from the Datadog log object is stored as-is. No normalization is applied, so the value in Firetiger will match exactly what Datadog sends (e.g., `"info"`, `"warn"`, `"error"`). ### GCP Cloud Monitoring Give your Firetiger agents direct access to Google Cloud Monitoring metrics. When investigating issues, agents can pull real-time infrastructure data — CPU spikes, memory pressure, cache hit ratios, connection counts — and correlate it with application-level signals like logs and traces. This works with any GCP service that reports metrics to Cloud Monitoring: Cloud SQL, Compute Engine, AlloyDB, Cloud Run, GKE, Load Balancers, and more. ## Setup 1. You need an existing [GCP connection](../infrastructure/gcp.txt) — this provides the service account used to authenticate 2. Grant the **Monitoring Viewer** role (`roles/monitoring.viewer`) to the service account if it doesn't already have **Viewer** access 3. You should see the Cloud Monitoring tools are enabled on the GCP Connection's settings page That's it. Your agents can now query any Cloud Monitoring metric in the project. ## What Agents Can Do | Capability | Description | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Browse metrics** | Explore what metrics exist in the project using hierarchical navigation | | **Query metrics** | Query metrics via GCP's [Prometheus-compatible API (PromQL)](https://cloud.google.com/monitoring/promql) — instant values, time series over a range, aggregations across instances | You don't need to know PromQL — just ask questions in natural language and the agent constructs the right queries. ## Permissions The service account needs the **Monitoring Viewer** role (`roles/monitoring.viewer`), which grants read-only access to metric data. The broader **Viewer** role also works. ## Troubleshooting **Empty results** — Confirm the project has active resources generating metrics. Some metrics have up to 5 minutes of reporting delay. **Permission denied** — Grant `roles/monitoring.viewer` to the service account. If using a custom role, it needs `monitoring.timeSeries.list` and `monitoring.metricDescriptors.list`. ### PromQL Give your Firetiger agents direct access to any Prometheus-compatible metrics API. When investigating issues, agents can query real-time and historical metrics — CPU usage, error rates, latency percentiles, saturation signals — and correlate them with logs and traces. Works with Prometheus, Thanos, Victoria Metrics, Cortex, Grafana Mimir, and Amazon Managed Prometheus (AMP). ## Setup 1. In the Firetiger UI, go to **Settings → Connections** and click **New Connection** 2. Select **PromQL** as the connection type 3. Enter a display name and the **Base URL** of your Prometheus-compatible API. Do not include `/api/v1` — Firetiger appends it automatically. For vanilla Prometheus this is the bare host (e.g., `https://prometheus.example.com`); some managed services (Chronosphere, AMP, Grafana Cloud, etc.) sit behind a gateway path that must be included — check your provider's PromQL API docs 4. Choose an authentication method and provide credentials (see [Authentication](#authentication) below) 5. Save the connection — your agents can now query metrics through it ## What Agents Can Do | Capability | Description | | --- | --- | | **Instant query** | Evaluate a PromQL expression at a single point in time | | **Range query** | Evaluate a PromQL expression over a time range, returning a time series | | **List labels** | Browse all label names present in the metrics store | | **Label values** | Get all values for a specific label name | | **Find series** | Discover time series matching a set of label matchers | | **Metric metadata** | Look up the type, help text, and unit for a metric | You don't need to know PromQL — just ask questions in natural language and the agent constructs the right queries. ## Authentication | Method | When to use | | --- | --- | | **None** | Public or unauthenticated endpoints | | **Basic Auth** | Username and password (e.g., Grafana Cloud, self-hosted Prometheus with basic auth) | | **Bearer Token** | API key or token (e.g., Grafana Cloud API key, custom auth middleware) | | **AWS SigV4** | Amazon Managed Prometheus (AMP) — signs requests with AWS credentials | ### AWS SigV4 (Amazon Managed Prometheus) Set the **Region** to the AWS region of your AMP workspace (e.g., `us-east-1`). Then choose a credential source: - **IAM Role** — provide a role ARN (e.g., `arn:aws:iam::123456789012:role/firetiger-amp-access`) for Firetiger to assume. Add an optional external ID to prevent confused deputy attacks. - **Static Credentials** — provide an Access Key ID and Secret Access Key directly. IAM role assumption is recommended for production use as credentials rotate automatically. #### Setting up an IAM role for AMP access **Step 1 — Create the role in your AWS account** Create an IAM role with the following trust policy. This allows Firetiger's AWS account to assume the role using STS. The `sts:ExternalId` condition is optional but strongly recommended — it prevents the [confused deputy problem](https://docs.aws.amazon.com/IAM/latest/UserGuide/confused-deputy.txt) by ensuring only Firetiger can use this role for your specific connection. Copy the External ID from the connection form in the Firetiger UI before creating the role. ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::975050257559:root" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "sts:ExternalId": "{external-id-from-firetiger}" } } } ] } ``` **Step 2 — Attach a permissions policy** Attach the following inline or managed policy to the role. Replace `{region}`, `{account-id}`, and `{workspace-id}` with your AMP workspace details. Scoping the resource to a specific workspace follows least-privilege — use `*` only if you need to grant access to all workspaces in the account. ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "aps:QueryMetrics", "aps:GetLabels", "aps:GetSeries", "aps:GetMetricMetadata" ], "Resource": "arn:aws:aps:{region}:{account-id}:workspace/{workspace-id}" } ] } ``` These four actions map directly to the agent capabilities listed above: instant/range queries, label listing, series discovery, and metric metadata lookup. **Step 3 — Enter the role ARN in Firetiger** Copy the role ARN (e.g., `arn:aws:iam::123456789012:role/firetiger-amp-access`) and paste it into the **Role ARN** field in the Firetiger connection form. If you added an External ID condition in Step 1, enter the same value in the **External ID** field. ## Troubleshooting **Every query returns 404** — The Base URL is likely missing a gateway path prefix. The connection authenticates fine but Prometheus's HTTP API isn't served at the root. Check your provider's PromQL / Query API docs for the URL pattern — anything ending in `/api/v1/query` in their docs becomes the Base URL with that suffix removed. **Connection refused or unreachable** — Confirm the Base URL is reachable from Firetiger's network. If your Prometheus is on a private network, you may need to configure a [network transport](../networking/network-transports.txt). **Authentication errors** — Double-check credentials. For Bearer tokens, make sure the token value does not include a `Bearer ` prefix. For AWS SigV4, confirm the IAM role or access key has `aps:QueryMetrics`, `aps:GetLabels`, `aps:GetSeries`, and `aps:GetMetricMetadata` permissions on the AMP workspace, and that the trust policy principal is set to Firetiger's AWS account (`arn:aws:iam::975050257559:root`). **Empty results** — Confirm the time range includes data. Some exporters have scrape delays of 15–60 seconds. Try a broader range or use an instant query against a known metric like `up`. ### PagerDuty Connections PagerDuty connections let agents call the [PagerDuty REST API](https://developer.pagerduty.com/api-reference/) to look up incidents, services, on-call schedules, and escalation policies. Authentication uses a PagerDuty API token. **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/settings/connections` ## Setup 1. Sign in to PagerDuty as an admin or account owner 2. Navigate to **Integrations → API Access Keys** (or open ) 3. Click **Create New API Key** 4. Choose **Read-only** unless an agent must create or acknowledge incidents 5. Copy the generated token ## Connection Parameters | Parameter | Type | Description | |-----------|------|-------------| | `api_token` | string | PagerDuty API token — sent as `Authorization: Token token=` | ## Example Connection ```json { "display_name": "Production PagerDuty", "description": "Production PagerDuty account — used for incident lookup and on-call routing", "connection_details": { "pagerduty": { "api_token": "" } } } ``` ## Webhook Ingest (optional) Send PagerDuty V3 webhook events into Firetiger as they happen. Events land in Iceberg tables under `pagerduty/events/{event_type}` (one table per event type, schema inferred on first event). This is separate from the API connection above and can be enabled in addition to it. The API connection uses the `api_token` field; webhook ingest uses `signing_secret`. ### 1. Generate the webhook URL In the connection's settings, click **Generate Webhook URL**. Copy the URL that appears in the **Webhook URL** field. ### 2. Create the webhook subscription in PagerDuty In PagerDuty, go to **Integrations → Generic Webhooks (V3)** (account-level) or **Service Directory → service → Integrations → Webhooks** (per-service), then **+ Add a Webhook**: - **URL**: paste the URL from step 1 - **Events to subscribe to**: pick the event types to forward (e.g. `incident.triggered`, `incident.acknowledged`, `incident.resolved`) PagerDuty shows the **signing secret** once after saving. Copy it. ### 3. Save the signing secret In the connection's settings, paste the secret into the **Signing Secret** field and save. Inbound deliveries are verified with HMAC-SHA256 against the `X-PagerDuty-Signature` header; deliveries with no/invalid signature are rejected. To verify the wiring without firing a real incident, use PagerDuty's **Send Test Event** button on the subscription. The test event arrives as a `pagey.ping` and lands in `pagerduty/events/pagey.ping`. ## Description Field Document the services, escalation policies, and tag conventions that agents should know about: ``` Production PagerDuty. Common queries: - Active incidents → GET /incidents?statuses[]=triggered&statuses[]=acknowledged - On-call for a schedule → GET /oncalls?schedule_ids[]=PXXXXXX - Service by name → GET /services?query= Conventions: - Incident urgency=high → page on-call - Incident urgency=low → email-only, no page ``` ## Best Practices - **Prefer read-only API keys** for agents that only need to look up incidents and on-call info - **Use a service-account user**, not a personal user, so the API key survives personnel changes - **Rotate tokens periodically** — PagerDuty supports multiple keys, so you can issue a new one before revoking the old ## Custom Build custom integrations with Firetiger using HTTP APIs, guarded email webhooks, and MCP servers. ### HTTP Connections HTTP connections enable agents to interact with RESTful APIs and web services. **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/settings/connections` ## Connection Parameters An HTTP connection requires the following configuration: ### Required Parameters | Parameter | Type | Description | |-----------|------|-------------| | `base_url` | string | Base URL including scheme, host, and optional base path (e.g., `https://api.example.com/v1`) | | `allowed_routes` | string[] | List of route patterns that agents can access (see Route Patterns below) | ### Optional Parameters | Parameter | Type | Description | Default | |-----------|------|-------------|---------| | `headers` | map | Non-auth headers to include in all requests (e.g. `Content-Type`, `X-Tenant-ID`) | None | | `max_response_size_bytes` | int64 | Maximum response body size in bytes | 10MB (10485760) | | `timeout_seconds` | int32 | Request timeout in seconds | 30 seconds | ### Important Notes - **HTTPS Required**: The `base_url` must use HTTPS (not HTTP) for security. HTTP URLs will be rejected. - **Maximum Limits**: - `max_response_size_bytes` cannot exceed 10MB (10485760 bytes) - `timeout_seconds` cannot exceed 300 seconds (5 minutes) ## Route Patterns The `allowed_routes` parameter controls which endpoints agents can access. Uses [http.ServeMux syntax](https://pkg.go.dev/net/http#ServeMux). **Pattern types**: ```json "allowed_routes": [ "GET /api/status", // Exact match "GET /api/users/", // Prefix match (trailing slash) "GET /api/users/{id}", // Wildcard (single segment) "GET /files/{path...}" // Wildcard (remaining segments) ] ``` Supported methods: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS` ## Authentication Authentication is configured via the `auth` oneof — set exactly one of the following methods. ### OAuth Client Credentials Automatically obtains and refreshes an OAuth 2.0 access token using the client credentials grant. The token is injected as an `Authorization: Bearer` header on each request. ```json "oauth_client_credentials": { "token_url": "https://auth.example.com/oauth/token", "client_id": "my-client-id", "client_secret": "my-client-secret", "scopes": "api.read api.write", "extra_params": { "audience": "https://api.example.com" } } ``` ### Bearer Token A static Bearer token sent as `Authorization: Bearer `. ```json "bearer_token": { "token": "sk-your-token" } ``` ### Basic Auth HTTP Basic authentication with username and password. ```json "basic_auth": { "username": "user", "password": "pass" } ``` ### Static Headers Raw auth headers included in every request. Use this when the API requires a custom auth header format. ```json "static_headers": { "headers": { "Authorization": "Bearer sk-your-token", "X-API-Key": "your-key" } } ``` Non-auth `headers` (in the top-level connection details) can be used alongside any auth method for headers like `Content-Type` or `X-Tenant-ID`. ## Webhook Signing If `webhook_signing_secret` is configured, Firetiger signs outbound HTTP requests that include a request body and adds the signature in the `X-Webhook-Signature` header. The header value uses this format: ```text sha256= ``` The digest is computed as HMAC-SHA256 over the exact raw HTTP request body using the configured signing secret. ### Validation Steps On your receiving service: 1. Read the raw request body bytes exactly as received 2. Compute `HMAC-SHA256(secret, raw_body)` 3. Hex-encode the digest 4. Compare it to the value after the `sha256=` prefix in `X-Webhook-Signature` 5. Reject the request if the values do not match ### Example Validation ```python import hashlib import hmac secret = b"your-signing-secret" raw_body = request_body_bytes received = request.headers["X-Webhook-Signature"] expected = "sha256=" + hmac.new(secret, raw_body, hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, received): raise ValueError("invalid webhook signature") ``` ### Important Notes - Use the raw request body bytes, not a re-serialized JSON object - Compare signatures with a constant-time comparison function when available - Rotate the signing secret if you suspect it has been exposed ## Description Field Document the API endpoints and usage patterns. **Example**: ``` Databricks API for cluster management. Endpoints: - GET /api/2.0/clusters/list - List all clusters - GET /api/2.0/clusters/get?cluster_id= - Get cluster details - POST /api/2.0/clusters/start - Start cluster (body: {"cluster_id": "..."}) - POST /api/2.0/clusters/terminate - Stop cluster (body: {"cluster_id": "..."}) Response format: JSON Rate limit: 30 req/min ``` ## Example Connection ```json { "display_name": "Databricks API", "description": "Databricks API for cluster management...", "connection_details": { "http": { "base_url": "https://api.databricks.com", "allowed_routes": [ "GET /api/2.0/clusters/list", "GET /api/2.0/clusters/get", "POST /api/2.0/clusters/start" ], "bearer_token": { "token": "dapi123..." }, "max_response_size_bytes": 5242880, "timeout_seconds": 30 } } } ``` ## Best Practices - **Least privilege** - Only include necessary routes in `allowed_routes` - **Document endpoints** - List available endpoints and response formats in description - **Include rate limits** - Document API rate limits in the description - **HTTPS required** - All connections must use HTTPS - **Prefer OAuth** - Use OAuth Client Credentials when the API supports it for automatic token refresh - **Use webhook signing** when the destination verifies HMAC signatures ### Send Email via Webhook `Send Email via Webhook` connections are a guarded variant of HTTP connections for customer email delivery. These connections are used by the customer email review flow: 1. `draft_customer_email` creates a draft and pauses for review 2. Firetiger waits for explicit approval in the UI 3. `send_customer_email` delivers the approved payload to the configured webhook **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/settings/connections` ## Connection Parameters An email-webhook connection requires the following configuration: ### Required Parameters | Parameter | Type | Description | |-----------|------|-------------| | `base_url` | string | Full webhook URL including scheme, host, and path (e.g. `https://api.example.com/customer-email`) | | `allowed_routes` | string[] | Fixed to `["POST /"]` for customer email delivery | ### Optional Parameters | Parameter | Type | Description | Default | |-----------|------|-------------|---------| | `headers` | map | Non-auth headers to include in every request | None | | `max_response_size_bytes` | int64 | Maximum response body size in bytes | 10MB (10485760) | | `timeout_seconds` | int32 | Request timeout in seconds | 30 seconds | | `webhook_signing_secret` | string | Optional HMAC signing secret for outbound webhook requests | None | | `slack_connection_name` | string | Slack connection to notify when a customer email is waiting for review | None | | `slack_channel` | string | Slack channel to notify when a customer email is waiting for review | None | ### Important Notes - **HTTPS Required**: The `base_url` must use HTTPS. - **Fixed endpoint**: This connection type always sends `POST` requests to the configured webhook URL. - **Slack is optional**: If `slack_connection_name` and `slack_channel` are omitted, customer email drafting and sending still work normally. ## Authentication Authentication is configured via the `auth` oneof — set exactly one of the following methods. ### OAuth Client Credentials Automatically obtains and refreshes an OAuth 2.0 access token using the client credentials grant. The token is injected as an `Authorization: Bearer` header on each request. ```json "oauth_client_credentials": { "token_url": "https://auth.example.com/oauth/token", "client_id": "my-client-id", "client_secret": "my-client-secret", "scopes": "email.send", "extra_params": { "audience": "https://api.example.com" } } ``` ### Bearer Token A static Bearer token sent as `Authorization: Bearer `. ```json "bearer_token": { "token": "sk-your-token" } ``` ### Basic Auth HTTP Basic authentication with username and password. ```json "basic_auth": { "username": "user", "password": "pass" } ``` ### Static Headers Raw auth headers included in every request. ```json "static_headers": { "headers": { "Authorization": "Bearer sk-your-token", "X-API-Key": "your-key" } } ``` Non-auth `headers` (in the top-level connection details) can be used alongside any auth method for headers like `Content-Type` or `X-Tenant-ID`. ## Request Payload Approved customer emails are delivered as a fixed JSON payload: ```json { "customer": { "id": "tenant-123", "name": "Acme Corp" }, "email": { "subject": "Issue update", "body": "We are aware of the issue and are investigating." } } ``` The webhook transport details come from the connection. The approved email content comes from the guarded approval artifact created during `draft_customer_email`. ## Optional Slack Notifications When both `slack_connection_name` and `slack_channel` are configured, Firetiger posts a Slack message when a customer email is waiting for review. - Notification is sent only for the first pending draft in the Issues Expert session - Slack delivery is best-effort and does not block customer email review - The Slack message links back to the relevant issue and the Firetiger review surface ## Webhook Signing If `webhook_signing_secret` is configured, Firetiger signs outbound email webhook requests and includes the signature in the `X-Webhook-Signature` header. The header value uses this format: ```text sha256= ``` The digest is computed as HMAC-SHA256 over the exact raw HTTP request body using the configured signing secret. ### Validation Steps On your receiving service: 1. Read the raw request body bytes exactly as received 2. Compute `HMAC-SHA256(secret, raw_body)` 3. Hex-encode the digest 4. Compare it to the value after the `sha256=` prefix in `X-Webhook-Signature` 5. Reject the request if the values do not match ### Example Validation ```python import hashlib import hmac secret = b"your-signing-secret" raw_body = request_body_bytes received = request.headers["X-Webhook-Signature"] expected = "sha256=" + hmac.new(secret, raw_body, hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, received): raise ValueError("invalid webhook signature") ``` ### Important Notes - Use the raw request body bytes, not a re-serialized JSON object - Compare signatures with a constant-time comparison function when available - Rotate the signing secret if you suspect it has been exposed ## Description Field Document what the webhook expects and who owns it. **Example**: ``` Customer email delivery webhook for incident notifications. Expected request body: - customer.id - customer.name - email.subject - email.body Owner: Support engineering Response format: JSON ``` ## Example Connection ```json { "display_name": "Customer Email Delivery", "description": "Customer email delivery webhook for incident notifications...", "connection_details": { "email_webhook": { "base_url": "https://api.example.com/customer-email", "allowed_routes": [ "POST /" ], "bearer_token": { "token": "sk-live-123" }, "slack_connection_name": "connections/team-slack", "slack_channel": "#customer-emails", "timeout_seconds": 30 } } } ``` ## Best Practices - **Use this instead of generic HTTP** for customer email delivery - **Keep the webhook fixed-purpose** and document the payload shape in the description - **Enable Slack notifications** if you want a review link posted automatically for pending drafts - **Prefer OAuth** when the destination service supports it - **Use webhook signing** when the destination verifies HMAC signatures ### MCP Servers # MCP Server Connections MCP (Model Context Protocol) connections enable agents to access tools from external services like GitHub, Linear, Notion, and more. **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/integrations/mcp` ## What is MCP? The [Model Context Protocol](https://modelcontextprotocol.io/) is an open standard that allows AI agents to securely connect to external tools and data sources. MCP servers expose tools that agents can invoke to perform actions like creating issues, querying databases, or sending notifications. ## Installation 1. Navigate to **Settings > MCP Servers** in the Firetiger UI 2. Click **Connect Server** 3. Either: - Select a **suggested server** (GitHub, Linear, Notion, Sentry, Atlassian, PagerDuty) - Or enter a custom **Server URL** for any MCP-compatible server 4. Click **Connect** to initiate the OAuth flow 5. Authorize Firetiger to access the service in the popup The connection will be created automatically once authorization is complete. ## Suggested Servers Firetiger provides quick-connect options for popular MCP servers: | Service | URL | Description | |---------|-----|-------------| | GitHub Copilot | `https://api.githubcopilot.com/mcp/` | Access issues, PRs, and repositories | | Linear | `https://mcp.linear.app/mcp` | Manage issues and project tracking | | Notion | `https://mcp.notion.com/mcp` | Access pages, databases, and docs | | Sentry | `https://mcp.sentry.dev/sse` | Monitor errors and performance | | Atlassian | `https://mcp.atlassian.com/v1/sse` | Connect Jira, Confluence, and more | | PagerDuty | `https://mcp.pagerduty.com/mcp` | Manage incidents and on-call | ## What Gets Created When you connect an MCP server, Firetiger creates a connection with: | Field | Value | |-------|-------| | `connection_id` | Auto-generated unique identifier | | `server_url` | The MCP server URL you connected to | | `server_name` | Automatically fetched from the server metadata | | `display_name` | Customizable name (defaults to server name) | | `description` | Optional description for agent context | ## Viewing Available Tools After connecting, you can view the tools provided by the MCP server: 1. Navigate to **Settings > MCP Servers** 2. Click on the connection card 3. View the **Available Tools** section 4. Click **Refresh Tools** to update the tool list from the server Each tool displays its name, description, and input schema. ## Authentication When connecting a server, pick the **Authentication Method** that matches how the MCP server authenticates clients: | Method | When to use | What you need | |--------|-------------|---------------| | **OAuth (Dynamic)** | The server supports OAuth discovery and dynamic client registration (RFC 7591). Most hosted MCP servers (GitHub, Linear, Notion, Sentry) work this way. | Nothing — you sign in through the provider's consent page. | | **OAuth (Static)** | The server requires OAuth but doesn't support dynamic registration; you pre-registered an OAuth app in the provider's admin console. | Client ID + client secret from the provider. You still sign in through the provider's consent page. | | **OAuth (Client Credentials)** | The provider issued machine-to-machine credentials for the `client_credentials` grant (RFC 6749 §4.4) — there is no user sign-in at all. Common for gateway products that front MCP servers with service accounts (e.g. Runlayer). | Client ID + client secret, and optionally the token URL and scopes. | | **API Token** | The server accepts a static bearer token (API key, PAT). | The token. | | **None** | The server requires no authentication (internal or localhost servers, usually combined with a network transport). | Nothing. | If your vendor's instructions show a direct POST to a token endpoint with `grant_type=client_credentials`, use **OAuth (Client Credentials)** — the browser-based options will fail with errors like `Invalid client_id`, because the client isn't registered for the authorization-code flow. ### OAuth (Dynamic) and OAuth (Static) Both run a browser authorization flow: Firetiger redirects you to the provider's consent page and stores the resulting tokens. Access tokens are refreshed automatically, and you can revoke access at any time from the connected service's settings. ### OAuth (Client Credentials) No browser step: Firetiger requests an access token directly from the token endpoint using your client ID and secret when you click **Connect**, verifies it against the MCP server, and automatically requests a fresh token whenever the current one expires. - **Token URL** is optional — when left empty, Firetiger discovers the token endpoint from the MCP server's OAuth metadata (RFC 8414). Set it explicitly if your provider gave you one. - **Scopes** are optional, space-separated, and are re-requested on every token renewal. - Bad credentials fail immediately at connection time, so you know right away whether the setup works. ### Credential handling For every method: - Client secrets and tokens are write-only: they are stored encrypted and never returned by the API or UI. - Access tokens are managed and renewed automatically (rotating refresh tokens for the browser OAuth flows; re-minting for client credentials). ## Customization ### Display Name Set a descriptive display name to help identify the connection: ``` Production GitHub - Engineering Org ``` ### Description The description field helps agents understand when and how to use this connection: ``` GitHub connection for the engineering organization. Use for: - Creating and managing issues in product repos - Reviewing pull requests - Accessing repository documentation ``` ## Managing Connections ### Refresh Tools If the MCP server adds new tools, click **Refresh Tools** to update the available tool list. ### Delete Connection To remove an MCP connection: 1. Click on the connection card 2. Click **Delete Connection** 3. Confirm the deletion This revokes Firetiger's access and removes the connection. You can reconnect at any time. ## Custom MCP Servers You can connect any MCP-compatible server by entering its URL. The server must: - Implement the [MCP specification](https://modelcontextprotocol.io/) - Support one of the authentication methods above (OAuth in any of its three flavors, a bearer token, or no auth) - Be accessible from Firetiger's infrastructure, or through a configured [network transport]({% link integrations/networking/tailscale.md %}) for servers on private networks (available for the API Token, None, and OAuth Client Credentials methods) ## Best Practices - **Use descriptive names** - Help agents identify the right connection when multiple are available - **Document tool usage** - Include guidance on which tools to use for different scenarios - **Limit connections** - Only connect services that agents need access to - **Review permissions** - Understand what access each MCP server requests before connecting ### OpenAPI Connections OpenAPI connections let agents interact with any REST API that publishes an OpenAPI (Swagger) specification. Agents can introspect the spec to discover available endpoints and make authenticated requests. **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/settings/connections` ## When to use OpenAPI vs HTTP | | OpenAPI | HTTP | |:--|:--------|:-----| | **Endpoint discovery** | Agents read the spec to find endpoints | You list endpoints in the description | | **Route restrictions** | Requests are validated against the server domain | You configure explicit `allowed_routes` | | **Best for** | APIs with published OpenAPI specs | APIs without specs, or when you need fine-grained route control | ## Connection Parameters ### Required Parameters | Parameter | Type | Description | |-----------|------|-------------| | `spec_url` | string | URL to fetch the OpenAPI specification (JSON or YAML) | ### Optional Parameters | Parameter | Type | Description | |-----------|------|-------------| | `server_url` | string | Base URL of the API server. If omitted, derived from the spec's `servers[0].url` | ## Authentication Authentication is configured via the `auth` oneof — set exactly one of the following methods. These reuse the same auth types as [HTTP connections](http.txt#authentication). ### OAuth Client Credentials Automatically obtains and refreshes an OAuth 2.0 access token using the client credentials grant. ```json "oauth_client_credentials": { "token_url": "https://app.example.com/oauth/token", "client_id": "my-client-id", "client_secret": "my-client-secret", "scopes": "api.all:read" } ``` ### Bearer Token A static Bearer token sent as `Authorization: Bearer `. ```json "bearer_token": { "token": "sk-your-token" } ``` ### Basic Auth HTTP Basic authentication with username and password. ```json "basic_auth": { "username": "user", "password": "pass" } ``` ## Agent Tools OpenAPI connections provide two tools to agents: ### `openapi_schema` Introspects the OpenAPI spec. With no path, returns a summary of all available endpoints. With a path (e.g. `/users`), returns the full schema including parameters, request body, and response schemas. ### `openapi_request` Makes an HTTP request to the API. The agent provides a full URL (e.g. `https://api.example.com/v1/users`), which is validated against the configured server domain to prevent credential leakage. ## Server URL Resolution When `server_url` is omitted, the server URL is resolved from the spec: 1. The spec is fetched from `spec_url` 2. `servers[0].url` is extracted 3. If the server URL is relative (e.g. `/v1`), it is resolved against the spec URL For example, a spec at `https://api.example.com/openapi.json` with `servers: [{url: "/v1"}]` resolves to `https://api.example.com/v1`. ## Example Connection ```json { "display_name": "Vanta API", "description": "Vanta compliance platform API", "connection_details": { "openapi": { "spec_url": "https://firetiger-public.s3.us-west-2.amazonaws.com/connections/vanta/openapi.json", "server_url": "https://api.vanta.com/v1", "oauth_client_credentials": { "token_url": "https://api.vanta.com/oauth/token", "client_id": "my-client-id", "client_secret": "my-client-secret", "scopes": "vanta-api.all:read" } } } } ``` ## Best Practices - **Prefer OAuth** - Use OAuth Client Credentials when the API supports it for automatic token refresh - **Set server_url explicitly** - While it can be derived from the spec, setting it explicitly avoids extra network calls - **Use openapi_schema first** - Agents should introspect the spec before making requests to understand available endpoints and parameters ### gRPC Connections gRPC connections let agents discover and invoke methods on a [gRPC](https://grpc.io/), [ConnectRPC](https://connectrpc.com/), or gRPC-Web service. Discovery uses [gRPC server reflection](https://grpc.io/docs/guides/reflection/), so the connection works with any service that has reflection enabled — no `.proto` file upload required. **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/settings/connections` ## Connection Parameters ### Required Parameters | Parameter | Type | Description | |-----------|------|-------------| | `address` | string | Target address in `host:port` format (e.g., `api.example.com:443`) | ### Optional Parameters | Parameter | Type | Description | Default | |-----------|------|-------------|---------| | `protocol` | enum | Wire protocol for invocation — does **not** affect reflection | `GRPC` | #### Protocol values | Value | Use when | |-------|----------| | `GRPC_PROTOCOL_GRPC` | Server speaks native gRPC over HTTP/2 | | `GRPC_PROTOCOL_CONNECT` | Server is ConnectRPC-only (HTTP/1.1 or HTTP/2) | | `GRPC_PROTOCOL_GRPCWEB` | Server is behind a gRPC-Web proxy or browser-facing | Service discovery always uses native gRPC reflection regardless of this setting — only method invocation honours `protocol`. ## Authentication Set exactly one. Credentials are injected by the fireshell network proxy and agents never construct the auth header themselves. > **Auth injection requires port 443.** The fireshell HTTPS proxy (TPROXY) only intercepts traffic on port 443, so credentials are only attached when `address` ends in `:443`. For any other port (e.g. `:8443`, `:50051`), requests go through unauthenticated and the server typically rejects them. Front non-443 gRPC services with a TLS terminator on port 443, or pick a different connection type. ### Bearer Token ```json "bearer_auth": { "token": "" } ``` ### Basic Auth ```json "basic_auth": { "username": "user", "password": "pass" } ``` ## Tools | Tool | Description | |------|-------------| | `grpc_services` | List all gRPC services and methods exposed via reflection | | `grpc_describe` | Describe a service or method schema (field types and documentation) | | `grpc_request` | Invoke a method with a JSON payload | The typical agent flow is `grpc_services` → `grpc_describe` for the method of interest → `grpc_request` with the JSON payload. ## Example Connection ```json { "display_name": "Internal gRPC API", "description": "Internal gRPC API for the orders service. Useful methods:\n- orders.v1.Orders/GetOrder — fetch an order by ID\n- orders.v1.Orders/ListOrders — paged list, filter by status", "connection_details": { "grpc": { "address": "orders-api.internal:443", "protocol": "GRPC_PROTOCOL_GRPC", "bearer_auth": { "token": "" } } } } ``` ## Best Practices - **Enable reflection on the server** — without it, `grpc_services` and `grpc_describe` can't enumerate methods - **Document the most useful methods in the description** so agents don't have to walk the entire service surface to find them - **Use a service-account credential**, not a personal token — agent traffic should be attributable to a long-lived identity ### GraphQL Connections GraphQL connections expose any HTTPS [GraphQL](https://graphql.org/) endpoint to agents. The agent runs queries via shell `curl` against an injected `$GRAPHQL_URL` env var; auth headers are stitched in by the fireshell network proxy on every request. GraphQL APIs self-document via introspection, so a single connection works with any compliant endpoint — Sourcegraph, GitHub, Linear, Shopify, Hasura, etc. **Recommended**: Create and manage connections via the web UI at `{{ site.ui_url }}/settings/connections` ## Connection Parameters ### Required Parameters | Parameter | Type | Description | |-----------|------|-------------| | `url` | string | GraphQL endpoint URL — must use `https://` (e.g., `https://api.linear.app/graphql`) | The fireshell network proxy drops plain HTTP egress, so an `http://` URL will pass create-time validation but fail at runtime with no auth applied. ## Authentication Set exactly one. Auth is **required** at create time — even for public endpoints, set `none` explicitly so an unset oneof can't yield a silently-unauthenticated connection. ### Bearer Token Standard `Authorization: Bearer `. Covers Linear, GitHub GraphQL, and most modern GraphQL APIs. ```json "bearer": { "token": "" } ``` ### Basic Auth Rare for GraphQL but supported. ```json "basic": { "username": "user", "password": "pass" } ``` ### Static Headers Arbitrary header map. Use this for schemes that don't fit Bearer: | Service | Header | |---------|--------| | Sourcegraph | `Authorization: token ` | | Shopify Admin | `X-Shopify-Access-Token: ` | | Hasura | `X-Hasura-Admin-Secret: ` | ```json "static_headers": { "headers": { "Authorization": "token " } } ``` ### No Authentication For public GraphQL APIs (e.g., ). ```json "none": {} ``` ## Discovery GraphQL APIs self-document via the `__schema` introspection query. Real-world schemas are large (Sourcegraph ~250–400KB SDL, GitHub ~600KB), so agents do **not** dump the full schema at once. Instead they discover in scoped steps: 1. Top-level Query surface (names + descriptions) 2. Top-level Mutation surface (only if writes are needed) 3. Drill into one type at a time via `__type(name: "...")` This keeps the per-session token cost bounded. ## Example Connection ```json { "display_name": "Sourcegraph", "description": "Sourcegraph instance for code search across our repos.\n\nUseful queries:\n- search(query, version: V3) → repo/file/symbol search\n- repository(name) → metadata for a single repo", "connection_details": { "graphql": { "url": "https://6.8.sourcegraph.com/api/graphql", "static_headers": { "headers": { "Authorization": "token " } } } } } ``` ## Description Field GraphQL endpoints return HTTP 200 even when an operation fails — errors are in the JSON body. Document the most useful queries up front so agents don't have to enumerate the schema on every run. ## Best Practices - **Always use HTTPS** — plain HTTP is rejected at runtime - **Pass variables as a separate `variables` object**, not interpolated into the query string — avoids quoting bugs and lets the server validate types - **Constrain results at the GraphQL layer** (`first: 25`, request only the fields you need) rather than truncating large responses client-side - **List the agent-relevant queries** in the description so agents can skip introspection on common workflows ### Web Search Connections Web Search connections give agents the `web_search` tool, backed by [Brave Search](https://brave.com/search/api/). Unlike most connection types, there is no per-connection credential to configure — the Brave API key is provided at the deployment level via the `BRAVE_SEARCH_API_KEY` environment variable. Creating the connection is effectively an **org-level opt-in**: it makes the `web_search` tool available to agents in your organization. **Recommended**: Create the connection via the web UI at `{{ site.ui_url }}/settings/connections` ## Connection Parameters `WebSearchConnectionDetails` is intentionally empty. ```json "web_search": {} ``` ## Tools | Tool | Description | |------|-------------| | `web_search` | Search the web via Brave Search and return ranked results | ## Example Connection ```json { "display_name": "Web Search", "description": "Web search via Brave — used for looking up vendor docs, error messages, and recent news during investigations", "connection_details": { "web_search": {} } } ``` ## Description Field The description is the main place to tell agents *when* to reach for web search vs. the rest of their toolbelt. A good description steers agents toward web search for things they can't answer from internal data: ``` Use web search for: - Looking up an unknown error code or library exception - Verifying current behavior of a third-party API - Recent CVEs or vendor incident pages Do NOT use web search for: - Anything answerable from logs, metrics, traces, or codebase tools - PII or customer-specific data ``` ## Best Practices - **Create one Web Search connection per org** — additional ones add no capability since the API key is deployment-level - **Steer agents away from web search for in-house questions** in the description — it's slower, costlier, and noisier than internal tools when the answer is in your own data - **Audit usage in agent transcripts** to catch agents reaching for web search on questions they should be answering from internal context ## Networking Configure network connectivity between Firetiger deployments and your infrastructure. ### AWS VPC Peering VPC peering enables Firetiger agents to connect to resources hosted in your AWS VPC, such as RDS databases, Elasticsearch clusters, or other private services. > VPC peering is only available for [BYOC (Bring Your Own Cloud)](/deploy/aws.txt) deployments. SaaS deployments use public endpoints or [AWS connections](/integrations/infrastructure/aws.txt) instead. ## How It Works A VPC peering connection creates a private network link between the Firetiger deployment VPC and your VPC. Traffic between the two VPCs stays within the AWS network and does not traverse the public internet. ## Prerequisites - A [BYOC AWS deployment](/deploy/aws.txt) provisioned by Firetiger - Your VPC must be in the same AWS region as the Firetiger deployment - VPC CIDR ranges must not overlap (Firetiger deployments typically use `10.0.0.0/16`) ## Setup ### 1. Create the VPC Peering Connection From your AWS account, create a VPC peering connection request: - **Requester VPC**: Your VPC (where your resources live) - **Accepter VPC**: The Firetiger deployment VPC (ID provided by Firetiger) - **Accepter AWS Account ID**: Provided by Firetiger - **Region**: Must match the Firetiger deployment region ### 2. Accept the Peering Connection Firetiger will accept the peering request from the deployment AWS account. ### 3. Enable DNS Resolution (if needed) If your resources use private DNS hostnames (e.g., RDS endpoints like `mydb.abc123.us-east-1.rds.amazonaws.com`), enable DNS resolution on both sides of the peering connection. ### 4. Configure Your Side In **your** AWS account, add: - **Route table entry**: Route the Firetiger VPC CIDR (e.g., `10.0.0.0/16`) to the peering connection - **Security group rule**: Allow inbound traffic from the Firetiger VPC CIDR on the required port (e.g., PostgreSQL 5432, MySQL 3306) ### 5. Provide Peering Details to Firetiger Send the following information to your Firetiger contact: | Field | Example | Description | |---|---|---| | VPC Peering Connection ID | `pcx-034daeb7643728bd9` | The peering connection ID after acceptance | | Your VPC CIDR | `172.30.0.0/16` | The CIDR range of your VPC that needs to be routable | | DNS resolution needed | Yes / No | Whether private DNS hostnames need to resolve across the peering | Firetiger will add the peering route to the deployment's Terraform configuration and apply it. ## Verification Once both sides are configured, test the connection from the Firetiger UI by running an agent that connects to your resource. If the agent can reach the target endpoint, the peering is working correctly. ## Troubleshooting ### Connection to your resource times out (but other agent functions work) - Verify your security group allows inbound traffic from the Firetiger VPC CIDR - Verify your route table has a return route to the Firetiger VPC CIDR via the peering connection - Check that the peering connection status is **Active** in the AWS console ### DNS resolution fails for RDS or other private endpoints - Ensure DNS resolution is enabled on the peering connection (both requester and accepter sides) - This setting is found under the peering connection's DNS tab in the AWS console ### All agent invocations time out This is not a peering issue. Contact Firetiger support - the deployment's NAT gateway route may need attention. ## Important Notes - **Do not modify Firetiger networking directly.** The Firetiger VPC's route tables, subnet associations, and NAT gateways are managed by Terraform. Manually modifying these resources will break Lambda internet connectivity and cause all agent invocations to fail. - Always coordinate through Firetiger to add peering routes to the deployment infrastructure. ### Network Transports Network Transports enable Firetiger to connect to databases and services on private networks that are not directly reachable from the public internet. When you create a [database connection](/integrations/databases/) that targets a host on a private network, you can attach a network transport to route traffic through a secure tunnel instead of attempting a direct connection over the public network. ## How It Works 1. You configure a **network transport** with credentials for a supported networking provider (e.g., Tailscale) 2. You create a **connection** and set its `network_transport` field to reference the transport 3. When an agent queries the connection, Firetiger's proxy server joins the private network on-demand and tunnels the connection traffic through it ## Supported Providers | Provider | Description | | ---------------------------------------------------- | ------------------------------------------------------------------------------------ | | [Tailscale](/integrations/networking/tailscale.txt) | Connect to databases on a Tailscale tailnet using OAuth client credentials | | [SOCKS5](/integrations/networking/socks5.txt) | Route traffic through a SOCKS5 proxy (e.g. a bastion host) fronting a private network | ## Creating a Network Transport Network transports can be created via the UI: <{{ site.ui_url }}/integrations/network-transports> ```bash ftops api network-transports create --id --from-file transport.json ``` See the provider-specific documentation for the required configuration fields. ## Attaching a Transport to a Connection When creating or updating a database connection, set the `network_transport` field to reference the transport by name: ```json { "displayName": "Private Database", "description": "PostgreSQL database on private network", "connectionType": "CONNECTION_TYPE_POSTGRES", "networkTransport": "network-transports/", "connectionDetails": { "postgres": { "host": "db.internal.example.com", "port": 5432, "database": "mydb", "username": "firetiger", "password": "secret", "sslMode": "require" } } } ``` The agent will automatically route traffic through the network transport when querying this connection. #### Tailscale Network Transport Tailscale network transports enable Firetiger agents to connect to databases and services on your [Tailscale](https://tailscale.com) tailnet. This is useful when your data sources are on a private network accessible via Tailscale but not reachable from the public internet. ## How It Works 1. Create a Tailscale OAuth Client for Firetiger: 2. Create a Firetiger Network Transport with these OAuth Client Credentials: <{{ site.ui_url }}/integrations/network-transports> 3. Create a Firetiger Connection that uses this Tailscale Network Transport 4. The Connection's traffic will proxy through an ephemeral tailscale node, with the tailscale tag ACLs you configure The proxy node is ephemeral — it appears in your tailnet only while actively tunneling and is automatically cleaned up. It also joins preauthorized, so it connects immediately even on tailnets that require [device approval](https://tailscale.com/kb/1099/device-approval). This relies on the OAuth client being authorized for the node's tags (see Step 3) — preauthorization only applies to tagged nodes the client is permitted to create. ## Prerequisites - A [Tailscale](https://tailscale.com) account with admin access - The target database or service must be reachable from your tailnet (either running Tailscale directly, or behind a [subnet router](https://tailscale.com/kb/1019/subnets)) - A service running directly on a tailnet host must listen on the tailnet interface — bind it to `0.0.0.0` (or the host's `100.x` Tailscale IP), **not** only `127.0.0.1`/`localhost`, or the proxy node won't be able to reach it ## Setup ### Step 1: Define an ACL Tag Firetiger's proxy node needs an ACL tag to identify itself on your tailnet. Define a tag in your Tailscale ACL policy. 1. Go to [Access Controls](https://login.tailscale.com/admin/acls) in the Tailscale admin console 2. Add a tag to the `tagOwners` section: ```json "tagOwners": { "tag:firetiger": ["autogroup:admin"] } ``` ### Step 2: Grant Network Access In the same ACL policy, grant the tag permission to reach your database or service. Using grants: ```json "grants": [ { "src": ["tag:firetiger"], "dst": ["*"], "ip": ["5432"] } ] ``` This allows the `tag:firetiger` node to connect to any machine on your tailnet on port 5432 (PostgreSQL). `ip` must list the port of the service you're exposing — use the right one for the target (e.g. `5432` for Postgres, `3306` for MySQL, or your HTTP/MCP server's port like `8080`), not just `5432`. Adjust `dst` and `ip` to match your security requirements: - Restrict `dst` to specific machines or tags — a tag (`["tag:databases"]`), a Tailscale IP or CIDR (`["100.x.y.z/32"]`), or a host alias defined in your policy's `hosts` section. Grant `dst` does **not** accept a bare MagicDNS name like `db.example.ts.net`; map it to an alias in `hosts` first (e.g. `"hosts": { "db": "100.x.y.z" }`, then `"dst": ["db"]`). - Restrict `ip` to the specific ports your databases or services listen on ### Step 3: Create an OAuth Client Firetiger uses Tailscale OAuth client credentials to authenticate and join your tailnet. 1. Go to [Settings > Trust credentials](https://login.tailscale.com/admin/settings/oauth) in the Tailscale admin console (Tailscale renamed "OAuth clients" to **Trust credentials**) 2. Click **Generate OAuth client** 3. Configure the client: - **Description**: `firetiger` (or similar) - **Tags**: Select `tag:firetiger` - **Scopes**: Ensure `auth_keys` Write is included (this allows the client to generate auth keys with the selected tags) 4. Click **Generate** 5. Copy the **Client ID** and **Client Secret** — the secret is only shown once. > The OAuth client must have the `tag:firetiger` tag selected. Without it, the proxy cannot generate tagged auth keys and will fail with "requested tags are invalid or not permitted". ### Step 4: Create the Network Transport Create the network transport in Firetiger using the OAuth credentials from the previous step: 1. Navigate to <{{ site.ui_url }}/integrations/network-transports> 2. Create Network Transport > Tailscale 3. Enter your Tailscale OAuth Client connection details: | Field | Required | Description | | ------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | | `oauthClientId` | Yes | OAuth client ID from Step 3 | | `oauthClientSecret` | Yes | OAuth client secret from Step 3 | | `tailnet` | Yes | Your tailnet name (e.g., `example.ts.net`). Find it at [Settings > General](https://login.tailscale.com/admin/settings/general) or run `tailscale status --json | jq -r .MagicDNSSuffix` | | `tags` | Yes | ACL tags to assign to the proxy node. Must be a subset of the tags authorized on the OAuth client | | `hostname` | No | Custom hostname for the proxy node in your tailnet (default: auto-generated) | ### Step 5: Create a Connection Using the Transport 1. Create a Connection via <{{ site.ui_url }}/integrations/connections/new> 2. Select a Connection type (e.g. Postgres, or an MCP server) 3. Under Network Transport, select your Tailscale Network Transport 4. Configure the Connection details The `host` should be the Tailscale hostname (e.g., `db-server`) or Tailscale IP address (e.g., `100.x.y.z`) of the machine running your database. If the database is behind a subnet router, use the private IP address that the subnet router advertises. For HTTP-based connections such as MCP servers, the server URL points at the same Tailscale hostname/IP and the service's port — e.g. `http://my-host.example.ts.net:8080/`. Both `http://` and `https://` server URLs work over a transport. Click Save + Test to verify that the connection is working. ## Troubleshooting ### "requested tags are invalid or not permitted" The OAuth client does not have the specified tags authorized. Go to [Settings > Trust credentials](https://login.tailscale.com/admin/settings/oauth), find your client, and verify it has `tag:firetiger` selected. ### "tailnet not found" The `tailnet` field has the wrong value. Find your tailnet name at [Settings > General](https://login.tailscale.com/admin/settings/general) or run: ```bash tailscale status --json | jq -r .MagicDNSSuffix ``` ### "tailnet-owned auth key must have tags set" The network transport was created without the `tags` field. Update it to include tags by updating the Network Transport ### Proxy node stuck waiting for approval in the Tailscale admin console Firetiger generates preauthorized auth keys, so the ephemeral proxy node should join without manual approval even when [device approval](https://tailscale.com/kb/1099/device-approval) is enabled. If a node still appears unapproved, the OAuth client is likely not authorized for the node's tags — Tailscale only honors preauthorization for tags the client is permitted to create. Verify the OAuth client has `tag:firetiger` (or your configured tag) selected in [Settings > Trust credentials](https://login.tailscale.com/admin/settings/oauth). ### Connection times out after transport is established - Verify the database host is reachable from your tailnet (try `tailscale ping ` from another tailnet node) - Check that your ACL grants allow the `tag:firetiger` tag to reach the database host and port - If the database is behind a subnet router, verify the subnet routes are approved in the Tailscale admin console #### SOCKS5 Network Transport SOCKS5 network transports enable Firetiger agents to connect to databases and services that sit behind a SOCKS5 proxy — typically a bastion host or jump box that fronts a private network. This is useful when your data sources are not reachable from the public internet but are reachable from a proxy you already operate. ## How It Works 1. You run a SOCKS5 proxy (e.g., on a bastion host) that can reach your private database or service 2. You create a Firetiger Network Transport with the proxy's host, port, and optional credentials 3. You create a Firetiger Connection that uses this SOCKS5 Network Transport 4. When an agent queries the connection, Firetiger's proxy opens a connection to your SOCKS5 server, which dials the target on its behalf Because Firetiger only talks to the SOCKS5 proxy, the target host only needs to be reachable from the proxy — not from Firetiger directly. Unlike the Tailscale transport, SOCKS5 is stateless: there is no ephemeral node to provision, so connections do not pay a tailnet cold-start cost. ## Prerequisites - A running SOCKS5 (RFC 1928) proxy reachable from Firetiger's network transport proxy - The target database or service must be reachable from the SOCKS5 proxy - If the proxy requires authentication, a username and password (RFC 1929 username/password authentication is supported) ## Setup ### Step 1: Run a SOCKS5 Proxy Stand up a SOCKS5 proxy on a host that can reach your private targets **and** is itself reachable from Firetiger. A standalone SOCKS5 server such as [Dante](https://www.inet.no/dante/) running on a bastion host is a common choice. An SSH dynamic forward (`ssh -D`) also speaks SOCKS5, but note that it opens the listener on the machine where you run `ssh` — not on the remote host — and tunnels traffic through the SSH connection. Run it on a host Firetiger can reach (not your laptop): ```bash # run on a Firetiger-reachable host; tunnels out through `bastion` ssh -D 0.0.0.0:1080 user@bastion ``` > The proxy must be reachable from Firetiger. Bind it to an interface and address that Firetiger's network transport proxy can connect to (not only `127.0.0.1`), and restrict access with a firewall or username/password auth. ### Step 2: Create the Network Transport Create the network transport in Firetiger using the proxy's connection details: 1. Navigate to <{{ site.ui_url }}/integrations/network-transports> 2. Create Network Transport > SOCKS5 Proxy 3. Enter your SOCKS5 proxy connection details: | Field | Required | Description | | ------------- | -------- | -------------------------------------------------------------------------------------------------------------- | | `displayName` | Yes | A human-readable name for the transport | | `host` | Yes | SOCKS5 proxy hostname or IP, reachable from Firetiger | | `port` | Yes | SOCKS5 proxy port (e.g., `1080`) | | `username` | No | Username for SOCKS5 username/password authentication (RFC 1929) | | `password` | No | Password for SOCKS5 authentication. Stored in a secrets manager; omitted from create/update/list responses but returned by `Get` (like `tailscale.oauthClientSecret`) | You can also create the transport via the API: ```bash ftops api network-transports create --id --from-file transport.json ``` ```json { "displayName": "Bastion SOCKS5", "socks5": { "host": "bastion.internal.example.com", "port": 1080, "username": "firetiger", "password": "secret" } } ``` ### Step 3: Create a Connection Using the Transport 1. Create a Connection via <{{ site.ui_url }}/integrations/connections/new> 2. Select a Connection type (e.g. Postgres, or an MCP server) 3. Under Network Transport, select your SOCKS5 Network Transport 4. Configure the Connection details The `host` should be the address of the target **as the SOCKS5 proxy resolves it** — for example, the private IP or internal DNS name reachable from the bastion (such as `db.internal.example.com`). The proxy performs the final dial, so the target does not need to be reachable from Firetiger directly. For HTTP-based connections such as MCP servers, the server URL points at the same target host and the service's port — e.g. `http://db.internal.example.com:8080/`. Both `http://` and `https://` server URLs work over a transport. Click Save + Test to verify that the connection is working. ## Troubleshooting ### Connection refused when establishing the transport Firetiger could not reach the SOCKS5 proxy itself. Verify that `host` and `port` are correct and that the proxy is reachable from Firetiger's network (not bound only to `127.0.0.1`, and not blocked by a firewall). ### Authentication failed The proxy requires username/password authentication and the supplied credentials are missing or incorrect. Update the network transport's `username` and `password`. If you rotate the password on the proxy, update it on the transport as well. ### Transport establishes but the connection times out The SOCKS5 proxy connected, but it could not reach the target host and port. - Verify the target is reachable **from the proxy host** (e.g., `nc -vz db.internal.example.com 5432` from the bastion) - Confirm the connection's `host`/`port` use an address the proxy can resolve and route to (a private IP or internal DNS name), not a public endpoint - Check that the proxy's egress firewall allows the target host and port --- # Deployment Options This section covers deploying and managing Firetiger in your infrastructure. ## Guides - [SaaS](saas.txt) - Firetiger's default hosted deployment option - [BYOC: AWS](aws.txt) - Deploy Firetiger to your AWS account - [BYOC: GCP](gcp.txt) - Deploy Firetiger to Google Cloud ## SaaS # Firetiger SaaS Firetiger SaaS is the default hosted option. Firetiger manages all infrastructure so you can start ingesting and querying telemetry data immediately. Get started at [firetiger.com](https://firetiger.com). ## BYOC: AWS # AWS Cross-Account Access Setup Guide This guide will walk you through the process of setting up cross-account access between your AWS account and the Firetiger AWS account (975050257559). This allows Firetiger to access resources in your AWS account in a secure and controlled manner. ## Account Requirements ### Dedicated sub-account Provision a standalone AWS sub-account in your AWS Organization for Firetiger. This account should not host unrelated workloads. ### No out-of-band modifications After granting Firetiger the deployment role, avoid modifying account-wide settings or Firetiger-managed resources without coordination. Contact your Firetiger Solutions Engineer to discuss changes. ### Baseline settings - AWS CloudTrail enabled for management events across all regions ## Creating Your AWS Account Follow this link to create an account on the AWS console: Click "Add an AWS account", then follow the steps to create a new account to deploy Firetiger in: ## Setting Up Cross-Account Access We've created a CloudFormation template to automatically provision the required permissions in your AWS account. **Step 1:** Save the following as `firetiger-bootstrap.json`: ```json { "Resources": { "CrossAccountAccessForFiretiger": { "Type": "AWS::IAM::Role", "DeletionPolicy": "Retain", "Properties": { "RoleName": "CrossAccountAccessForFiretiger", "Description": "Allows full administrative access from the Firetiger account", "ManagedPolicyArns": ["arn:aws:iam::aws:policy/AdministratorAccess"], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": ["arn:aws:iam::975050257559:root"] }, "Action": ["sts:AssumeRole"] } ] } } } }, "Outputs": { "RoleARN": { "Description": "ARN of the CrossAccountAccessForFiretiger role", "Value": { "Fn::GetAtt": ["CrossAccountAccessForFiretiger", "Arn"] } } } } ``` **Step 2:** Apply the CloudFormation template: ```bash aws cloudformation create-stack --stack-name firetiger-bootstrap \ --template-body file://firetiger-bootstrap.json \ --capabilities CAPABILITY_NAMED_IAM ``` **Step 3:** Wait for the stack to complete: ```bash aws cloudformation wait stack-create-complete --stack-name firetiger-bootstrap ``` **Step 4:** Retrieve the Role ARN: ```bash aws cloudformation describe-stacks --stack-name firetiger-bootstrap \ --query 'Stacks[0].Outputs[?OutputKey==`RoleARN`].OutputValue' \ --output text ``` Share this Role ARN with your Firetiger Solutions Engineer to complete the setup. ## What Firetiger will use this role for For your information, here's what we'll set up on our account (975050257559) to make this work: 1. Create an IAM policy that allows assuming your role 2. Attach this policy to the appropriate IAM roles in our account 3. Configure our systems to use these credentials to assume your role ### Example of the Firetiger IAM Policy used to assume your role We create a policy in our account that looks like this: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "arn:aws:iam::YOUR_ACCOUNT_ID:role/CrossAccountAccessForFiretiger" } ] } ``` We then attach this policy to a deployment IAM Role used to provision resources into your account. ## Setting up Anthropic LLM access Firetiger uses Anthropic Claude models via Amazon Bedrock. Before these models can be invoked, you must complete a one-time setup in the AWS account that runs Firetiger. ### 1. Complete the Anthropic First Time Use (FTU) form Open the [Amazon Bedrock console](https://console.aws.amazon.com/bedrock/) → **Model catalog** → select any Anthropic Claude model. If this is the first time using Anthropic models in the account, you'll be prompted to fill out the FTU form. Use your company's name, website, and write "LLM access for Firetiger AI products" in the use case field. Access is granted immediately after submission. If the form does not appear, the FTU has already been completed for this account (or inherited from the AWS Organization management account) and no further action is needed for this step. ### 2. Required models Firetiger needs access to these Anthropic models, routed through regional cross-region inference profiles (`us.*`, `eu.*`, or `apac.*` depending on the deployment region): - **Claude Sonnet 4.6** — main model - **Claude Haiku 4.5** — light model - **Claude Opus 4.6** — heavy model (used only when explicitly enabled per stack) Because routing stays within a single geo, prompt caches are reused efficiently. Each regional profile fans out across several AWS regions in that geo (e.g. `us.*` covers `us-east-1`, `us-east-2`, `us-west-2`), so model access must be granted in every region the profile may route to — not only the primary deployment region. After the FTU form is complete, Bedrock auto-subscribes to each model in each region on first invocation (may take up to 15 minutes). See [Amazon's model access documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.txt) for more details. ## Access Policy The following principles govern how Firetiger employees access your AWS account under the Managed BYOC model: 1. Purpose of the role: The deployment role is used by Firetiger automation to deploy and operate Firetiger resources in your account. 2. Account ownership: Your AWS account is owned and managed by you, the customer. Firetiger does not take ownership of your account. 3. Consent: Firetiger employees may assume the deployment role only with explicit customer consent for the specific access window and purpose. 4. Authentication and authorization: Firetiger employee access to AWS resources is managed via AWS SSO, with enforced multi‑factor authentication for all logins. 5. Auditability: All account activity is auditable via AWS CloudTrail. [CloudTrail should be enabled by default](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/view-cloudtrail-events.txt) for new AWS Accounts. The customer is responsible for configuring it to meet organizational requirements. For more details on Firetiger's Access Policy and other controls, please visit or contact us ## BYOC: GCP # Deploy Firetiger to Google Cloud This document describes how to deploy Firetiger to a GCP project in your GCP organization. During this process, you will: 1. Create a new GCP Project configured with a Billing Account 2. Enable required service APIs within the project 3. Grant Firetiger permission to deploy the Firetiger stack in your new project # Prerequisites 1. You, the Firetiger customer, have access to the Google Cloud Console with permission to: - Create and Manage Projects (ie `roles/resourcemanager.projectCreator`) - Associate a project with a GCP Billing Account (ie `roles/billing.user`) - Enable APIs within the new project - Manage IAM permissions within the new project 2. [Google Cloud CLI installed (`gcloud`)](https://cloud.google.com/sdk/docs/install) # Setup First, we’ll ensure we have a GCP Project and that it is associated with a billing account: 1. Create a new project (if not using an existing one): ```bash # Create new project (optional - can use existing) export GCP_PROJECT_ID="firetiger-$company" gcloud projects create $GCP_PROJECT_ID --name="Firetiger" # Set the working project gcloud config set project $GCP_PROJECT_ID ``` 1. Associate the project with a Billing Account First, choose the relevant Billing Account for your GCP Organization. You can see your billing accounts by running: ```protobuf gcloud billing accounts list ``` Then, configure your billing account ID and link it to the new project ```bash export BILLING_ACCOUNT_ID="your-billing-account-id" gcloud billing projects link $GCP_PROJECT_ID --billing-account $BILLING_ACCOUNT_ID ``` 1. Enable Required Services in Your Project: ```bash # Enable services required by Firetiger gcloud --project $GCP_PROJECT_ID services enable \ aiplatform.googleapis.com \ alloydb.googleapis.com \ artifactregistry.googleapis.com \ bigquery.googleapis.com \ certificatemanager.googleapis.com \ cloudbuild.googleapis.com \ cloudfunctions.googleapis.com \ cloudresourcemanager.googleapis.com \ cloudscheduler.googleapis.com \ compute.googleapis.com \ container.googleapis.com \ dns.googleapis.com \ eventarc.googleapis.com \ iam.googleapis.com \ logging.googleapis.com \ monitoring.googleapis.com \ pubsub.googleapis.com \ run.googleapis.com \ secretmanager.googleapis.com \ servicenetworking.googleapis.com \ sqladmin.googleapis.com \ storage.googleapis.com \ vpcaccess.googleapis.com ``` 1. Provision Google-managed service agents that Firetiger's deploy depends on Some Google-managed service agents are not auto-created when their parent API is enabled. Run these one-shot commands after the API enable step, before Firetiger runs terraform: ```bash # Provision the Eventarc service agent (service-@gcp-sa-eventarc.iam.gserviceaccount.com). # Without this, the bucket-IAM binding for the datafile-optimizer Cloud Function # trigger fails with "Service account ... does not exist". gcloud --quiet beta services identity create \ --service=eventarc.googleapis.com \ --project=$GCP_PROJECT_ID # Provision the GCS service identity used for KMS / Pub/Sub integration. gcloud storage service-agent --project=$GCP_PROJECT_ID ``` 1. Grant Firetiger Permission to Deploy the Firetiger Stack to your project (via Service Account) The `domain:firetiger.com` `roles/editor` binding below lets individual Firetiger engineers run `terraform plan`/`apply` locally against your project; the `deployer@firetiger-control-plane` bindings are what the CI deployer uses. Both are required. ```bash export FIRETIGER_SA="deployer@firetiger-control-plane.iam.gserviceaccount.com" # Grant required permissions to the Firetiger Project gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \ --member="domain:firetiger.com" \ --role="roles/editor" gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \ --member="serviceAccount:$FIRETIGER_SA" \ --role="roles/editor" gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \ --member="serviceAccount:$FIRETIGER_SA" \ --role="roles/resourcemanager.projectIamAdmin" gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \ --member="serviceAccount:$FIRETIGER_SA" \ --role="roles/secretmanager.admin" gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \ --member="serviceAccount:$FIRETIGER_SA" \ --role="roles/run.admin" gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \ --member="serviceAccount:$FIRETIGER_SA" \ --role="roles/artifactregistry.admin" gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \ --member="serviceAccount:$FIRETIGER_SA" \ --role="roles/pubsub.admin" gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \ --member="serviceAccount:$FIRETIGER_SA" \ --role="roles/storage.admin" gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \ --member="serviceAccount:$FIRETIGER_SA" \ --role="roles/serviceusage.serviceUsageAdmin" gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \ --member="serviceAccount:$FIRETIGER_SA" \ --role="roles/iam.serviceAccountUser" gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \ --member="serviceAccount:$FIRETIGER_SA" \ --role="roles/logging.configWriter" gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \ --member="serviceAccount:$FIRETIGER_SA" \ --role="roles/bigquery.admin" gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \ --member="serviceAccount:$FIRETIGER_SA" \ --role="roles/cloudfunctions.admin" # Required so terraform can create the service-networking VPC peering # that AlloyDB needs. Without this, AlloyDB instance creation fails with # "NETWORK_NOT_PEERED" because roles/editor cannot call services.addPeering. gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \ --member="serviceAccount:$FIRETIGER_SA" \ --role="roles/servicenetworking.networksAdmin" # Required for VPC, global IP, Cloud NAT, and certificate-manager operations # that roles/editor does not cover. gcloud projects add-iam-policy-binding $GCP_PROJECT_ID \ --member="serviceAccount:$FIRETIGER_SA" \ --role="roles/compute.networkAdmin" ``` 1. Share your new GCP Project ID with your Firetiger point of contact ## Setting up Anthropic LLM access Firetiger uses Anthropic Claude models via Vertex AI. You must enable these models in your project's Model Garden before they can be invoked. 1. Open the [Vertex AI Model Garden](https://console.cloud.google.com/vertex-ai/model-garden) in your project 2. Search for each of the following Anthropic Claude models and click **Enable** on each: - **Claude Sonnet 4.6** - **Claude Haiku 4.5** - **Claude Opus 4.6** 3. Accept the terms/EULA when prompted See [Google's Model Garden documentation](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/model-garden/use-models) for more details. # Next Steps Firetiger will set up resources in that project. They’ll add a bucket that will hold data, services that ingest and compact data, and credentials for access. They’ll give you connection info, which will include URLs for ingesting and querying data as well as credential sets. Firetiger can ingest data in multiple ways. Refer to the following documentation for instructions on how to send data to Firetiger: [Firetiger Integrations](../integrations/) # Additional Resources [How to Create Public Google Cloud Run Services When Domain Restricted Sharing is Enabled](https://www.notion.so/How-to-Create-Public-Google-Cloud-Run-Services-When-Domain-Restricted-Sharing-is-Enabled-1a570c7133b4800ea86bdf0a2446f29d?pvs=21) --- # Account Management Manage your Firetiger organization settings, authentication, and access controls. ## SSO # Single Sign-On (SSO) Configure SSO to allow your team to sign in to Firetiger using your organization's identity provider via SAML. ## Supported Providers - **[Google Workspace]({% link sso/google-workspace.md %})** - Set up SAML SSO with Google Workspace ### Google Workspace # Setup Google Workspace SSO This guide walks through configuring Google Workspace as a SAML identity provider for Firetiger. You will create a custom SAML app in the Google Admin Console and provide the resulting configuration to Firetiger. ## Prerequisites - Google Workspace admin access - Firetiger deployment with SSO enabled ## Step 1: Create a Custom SAML App 1. Sign in to the [Google Admin Console](https://admin.google.com) 2. In the left sidebar, go to **Apps** > **Web and mobile apps** 3. Click **Add app** > **Add custom SAML app** 4. Enter an **App name** (e.g., "Firetiger") and optionally upload an icon 5. Click **Continue** ## Step 2: Download Identity Provider Metadata On the **Google Identity Provider details** screen: 1. Click **Download Metadata** to download the IdP metadata XML file 2. Save this file -- you will provide it to Firetiger in a later step 3. Click **Continue** Alternatively, you can manually copy the **SSO URL**, **Entity ID**, and **Certificate** values from this screen. ## Step 3: Configure Service Provider Details Enter the values provided by Firetiger: | Field | Value | | ------------- | --------------------- | | **ACS URL** | Provided by Firetiger | | **Entity ID** | Provided by Firetiger | Under **Name ID**: 1. Set the **Name ID format** to **EMAIL** 2. Set the **Name ID** to **Basic Information > Primary email** Click **Continue**. ## Step 4: Configure Attribute Mapping In the **Attributes** section, click **Add mapping** to create the following mappings: | Google Directory Attribute | App Attribute | | -------------------------- | ------------- | | Primary email | `mail` | | First name | `firstName` | | Last name | `lastName` | Click **Finish**. ## Step 5: Enable the App for Users After creating the app, you will be on the app detail page: 1. In the **User access** section, click **OFF for everyone** 2. On the **Service status** page, select **ON for everyone** (or select specific organizational units) 3. Click **Save** Changes may take up to 24 hours to propagate, though they typically apply within a few minutes. ## Step 6: Provide Configuration to Firetiger Send the following to Firetiger to complete the setup: - The **IdP metadata XML file** downloaded in Step 2, or the individual **SSO URL**, **Entity ID**, and **Certificate** values Once Firetiger configures the connection, users in your Google Workspace organization will be able to sign in via SSO. ## API Keys API keys are programmatic access credentials for the Firetiger API. They're used to authenticate API calls like triggering [Agents](../concepts/agents.txt) via webhook, listing resources, or integrating with external systems. You can manage your API keys at **Settings > API keys**: ## Creating an API key Click **+ Create API key** to create a new credential. You'll be asked to provide: - **Name** — a label to help you remember what this key is for (e.g. `ci-deploy-trigger`, `datadog-forwarder`). - **Access level** — either **Read-only** or **Read-write**. Use read-only keys when possible to limit the blast radius of a leaked credential. After creation, the secret value is shown once. Copy it and store it securely — you won't be able to see it again. ## Access levels | Level | Can do | | :--- | :--- | | **Read-only** | List resources, read agent results | | **Read-write** | Everything above, plus trigger agents via webhook, create and modify resources | ## Using an API key API keys authenticate with HTTP Basic auth. When you create a key, the dialog provides a username, password, and a pre-computed `Authorization` header value you can use directly: ``` Authorization: Basic ``` For example, to trigger an agent via webhook: ```bash curl -X POST "{{ site.api_url }}/firetiger.agents.v1.AgentsService/TriggerAgent" \ -u "username:password" \ -H "Content-Type: application/json" \ -d '{"name": "agents/your-agent-id", "message": "deploy completed"}' ``` ## Revoking an API key Click the trash icon next to any key to revoke it. Revocation is immediate — any requests using that key will start failing right away. --- # API Reference This is the reference documentation for the Firetiger REST API. The API follows [Google's API Improvement Proposals](https://google.aip.dev/) (AIP) conventions. ## Base URL Your deployment's API base URL is: ``` {{ site.api_url }} ``` ## Authentication API requests are authenticated with [API keys](../account-management/api_keys.txt) using HTTP Basic auth. When you create a key, the UI provides a username and password you can use directly. ```bash curl -X POST "{{ site.api_url }}/firetiger.issues.v1.IssuesService/ListIssues" \ -u "$API_KEY_USERNAME:$API_KEY_PASSWORD" \ -H "Content-Type: application/json" \ -d '{}' ``` API keys come in two access levels: | Level | Description | |:------|:------------| | **Read-only** | List and get resources, plus invoke triggers | | **Read-write** | All read-only access, plus create, update, and delete resources | ## Calling convention All API methods use HTTP `POST` with a JSON request body. The URL pattern is: ``` POST /{package}.{Service}/{Method} ``` Every request must include the header `Content-Type: application/json`. Requests accept both `snake_case` and `camelCase` field names. Responses always use `camelCase`. ## Resource names Resources are identified by a `name` field following the pattern `{collection}/{id}`. For example: - `issues/iss-auth-timeout` - `agents/my-agent` - `agents/my-agent/sessions/s-123` Nested resources include their parent's name as a prefix. ## Standard methods Most resources support some or all of these standard methods: | Method | Description | Key request fields | |:-------|:------------|:-------------------| | **Create** | Create a new resource | `{resource_id}`, `{resource}` object | | **Get** | Retrieve a single resource | `name` | | **List** | List resources with pagination | `page_size`, `page_token`, `filter`, `order_by` | | **Update** | Modify an existing resource | `{resource}` object with `name` set, `update_mask` | | **Delete** | Soft-delete a resource | `name` | ## Pagination List methods return results in pages. **Request fields** | Field | Description | |:------|:------------| | `page_size` | Maximum number of results per page (server may return fewer) | | `page_token` | Token from a previous `next_page_token` to fetch the next page | **Response fields** | Field | Description | |:------|:------------| | `next_page_token` | Token to pass as `page_token` in the next request. Empty when there are no more results. | ```bash # First page curl -X POST "{{ site.api_url }}/firetiger.issues.v1.IssuesService/ListIssues" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"page_size": 10}' # Next page (using next_page_token from previous response) curl -X POST "{{ site.api_url }}/firetiger.issues.v1.IssuesService/ListIssues" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"page_size": 10, "page_token": "..."}' ``` ## Filtering List methods accept a `filter` parameter for filtering results. The syntax is based on [AIP-160](https://google.aip.dev/160). ### Operators | Operator | Description | Example | |:---------|:------------|:--------| | `=` | Equals | `state = "ISSUE_STATE_OPEN"` | | `!=` | Not equals | `state != "ISSUE_STATE_RESOLVED"` | | `<` | Less than | `create_time < "2024-06-01T00:00:00Z"` | | `>` | Greater than | `create_time > "2024-01-01T00:00:00Z"` | | `<=` | Less than or equal | `create_time <= "2024-06-30T23:59:59Z"` | | `>=` | Greater than or equal | `create_time >= "2024-01-01T00:00:00Z"` | | `:` | Has / contains (wildcard) | `display_name : "*payments*"` | ### Combining filters Use `AND`, `OR`, and `NOT` to combine expressions: ```json {"filter": "state = \"ISSUE_STATE_OPEN\" AND create_time > \"2024-01-01T00:00:00Z\""} ``` ### Field access Use dot-delimited paths to access nested fields and map keys: - `labels.environment = "production"` -- map value access - `origin.repository = "acme-corp/backend"` -- nested message field See each resource page for the filterable fields specific to that resource. ## Ordering List methods accept an `order_by` parameter to control sort order: ```json {"order_by": "create_time desc"} ``` Use `asc` (default) or `desc` after the field name. Dot-delimited paths work for nested fields (e.g. `origin.pr_number desc`). ## Partial updates Update methods accept an `update_mask` field (a [FieldMask](https://protobuf.dev/reference/protobuf/google.protobuf/#field-mask)) to specify which fields to modify. Only the listed fields are changed; others are left untouched. The value is a comma-separated list of field paths. Dot-delimited paths address nested fields (e.g. `configuration.cron.schedule`). ```json { "issue": { "name": "issues/iss-auth-timeout", "state": "ISSUE_STATE_RESOLVED" }, "update_mask": "state" } ``` If `update_mask` is omitted, all mutable fields in the request are updated. For repeated fields and map fields, the provided value replaces the existing value entirely -- there is no element-level merging. ## Soft delete Delete methods perform a soft delete by default. Soft-deleted resources: - Have a non-null `delete_time` timestamp - Are excluded from List results unless `show_deleted` is set to `true` - Can still be retrieved with Get by name ```json {"show_deleted": true} ``` ## Field behaviors Fields in the [resource type tables](types/) are annotated with behaviors: | Behavior | Meaning | |:---------|:--------| | **OUTPUT_ONLY** | Set by the server. Ignored if included in create/update requests. | | **REQUIRED** | Must be provided in create requests. | | *No annotation* | Optional. Can be set on create and modified with update. | ## Timestamps All timestamps are in [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) format with UTC timezone: ``` "2024-06-15T14:30:00Z" ``` ## Error responses Errors are returned as JSON with an HTTP status code and a `code` string: ```json { "code": "not_found", "message": "issues/does-not-exist not found" } ``` | HTTP Status | Code | Description | |:------------|:-----|:------------| | 400 | `invalid_argument` | The request is malformed or a required field is missing | | 401 | `unauthenticated` | Missing or invalid credentials | | 403 | `permission_denied` | The API key doesn't have access to this method | | 404 | `not_found` | The requested resource doesn't exist | | 409 | `already_exists` | A resource with that ID already exists | | 500 | `internal` | Internal server error | ## Resources ### Read-write These resources support full CRUD operations with a read-write API key: - [Customers](customers.txt) — customer definitions for scoping investigations - [Investigations](investigations.txt) — ad-hoc data investigations - [Notes](notes.txt) — free-form notes attached to resources - [Runbooks](runbooks.txt) — operational runbooks for agents - [Triggers](triggers.txt) — webhook triggers for agent invocation - [Agents](agents.txt) — autonomous agents, sessions, and message read/write - [Agent SLOs](agent-slos.txt) — define and track SLOs for agents - [Indicators](indicators.txt) — reusable ConfitSQL-backed timeseries measurements that attach to Agents, Issues, and Investigations - [Coding Agents](coding-agents.txt) — launch coding-agent sessions (e.g. Cursor) on issues - [Autofix](autofix.txt) — automatically launch a coding agent when an issue becomes Actionable - [Deployments](deployments.txt) — software deployment tracking - [Tags](tags.txt) — organize and filter agents with custom tags - [Notifications](notifications.txt) — real-time event subscriptions - [Impact Report Notifications](impact-report-notifications.txt) — per-user weekly Impact Report Slack delivery preference - [Network Profiles](network-profiles.txt) — named allow-lists of egress domains for the bash tool - [Network Transports](network-transports.txt) — overlay networks (Tailscale) for reaching customer-side private systems - [Slack](slack.txt) — send Slack messages with membership-based channel resolution and idempotent delivery - [Skills Bundles](skills-bundles.txt) — expose a GitHub repository of agentskills.io skills to your agents - [Roles](roles.txt) — define permission bundles and assign them to organization members ### Read-only These resources are managed by the system and available for reading: - [Connections](connections.txt) — configured integrations and their sub-services - [Issues](issues.txt) — system-detected issues and notification policies - [Monitoring Plans](monitoring-plans.txt) — agent monitoring configurations and runs - [Billing](billing.txt) — metered usage for the current period and per-agent historical breakdowns ### Reference - [Types](types/) — all resource types, enums, and shared types ## Agents Agents are automated workflows that monitor your systems and take action on your behalf. Each agent can have multiple sessions, which represent individual execution runs with their own message history and lifecycle. **Service**: `firetiger.nxagent.v2.AgentService` **Resource name patterns**: `agents/{agent_id}` and `agents/{agent_id}/sessions/{session_id}` **Access**: Read-write **Resource types**: [Agent](types/agent.txt), [Session](types/session.txt) ## Example flow Create an agent, send it a message (which auto-creates a session), then read the conversation back. **1. Create an agent** ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/CreateAgent" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "error-monitor", "agent": { "title": "Error Rate Monitor", "prompt": "You monitor production services for elevated error rates.", "connections": [ {"name": "connections/prod-postgres", "enabled_tools": ["TOOL_POSTGRES_QUERY"]}, {"name": "connections/prod-prometheus", "enabled_tools": ["TOOL_PROMQL_QUERY"]} ], "state": "AGENT_STATE_ON" } }' ``` **2. Send a message (auto-creates a session)** Writing to `parent` instead of `session` auto-creates a new session. The default write mode (`WRITE_MODE_CHECKPOINT`) triggers the agent to start processing. ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/Write" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "parent": "agents/error-monitor", "messages": [ { "@type": "type.googleapis.com/firetiger.nxagent.v1.Activity", "user": { "text": {"text": "Check error rates for the payments service over the last hour."} } } ] }' ``` ```json {"session": "agents/error-monitor/sessions/ses-abc123", "sessionLength": "1"} ``` **3. Read messages back** Use the session name from the Write response to read the conversation. The agent's responses will appear as `assistant` activities. ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/Read" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"session": "agents/error-monitor/sessions/ses-abc123", "session_offset": 0}' ``` ```json { "session": "agents/error-monitor/sessions/ses-abc123", "sessionOffset": "0", "sessionLength": "3", "messages": [ { "@type": "type.googleapis.com/firetiger.nxagent.v1.Activity", "user": {"text": {"text": "Check error rates for the payments service over the last hour."}} }, { "@type": "type.googleapis.com/firetiger.nxagent.v1.Activity", "assistant": { "text": {"text": "I'll query Prometheus for the payments service error rate."}, "toolCalls": [{"id": "tc-1", "name": "TOOL_PROMQL_QUERY", "arguments": "{\"query\": \"rate(http_requests_total{service=\\\"payments\\\",status=~\\\"5..\\\"}[1h])\"}"}] } }, { "@type": "type.googleapis.com/firetiger.nxagent.v1.Activity", "user": { "toolResults": [{"toolCallId": "tc-1", "content": "{\"status\":\"success\",\"data\":{\"result\":[{\"value\":\"0.02\"}]}}"}] } } ], "status": "STATUS_EXECUTING" } ``` **Write vs CreateSession**: Use [CreateSession](#createsession) when you need an empty session with metadata (like associated resources). Use [Write](#write) with `parent` to create a session and send a message in one call. ## Methods | Method | Description | |:-------|:------------| | [CreateAgent](#createagent) | Create a new agent | | [GetAgent](#getagent) | Retrieve an agent by name | | [ListAgents](#listagents) | List agents with filtering and pagination | | [UpdateAgent](#updateagent) | Update an existing agent | | [DeleteAgent](#deleteagent) | Soft-delete an agent | | [UndeleteAgent](#undeleteagent) | Restore a soft-deleted agent | | [CreateSession](#createsession) | Create a new session for an agent | | [LaunchBacktest](#launchbacktest) | Spawn a dry-run session at a past timestamp; write-classified tools are stubbed | | [GetSession](#getsession) | Retrieve a session by name | | [UpdateSession](#updatesession) | Update an existing session | | [DeleteSession](#deletesession) | Soft-delete a session | | [ListSessions](#listsessions) | List sessions for an agent or across all agents | | [DescribeSessions](#describesessions) | List sessions with runtime state (status, conclusion, last message) | | [Write](#write) | Send messages to a session | | [Read](#read) | Read messages from a session | | [GetArtifact](#getartifact) | Retrieve an artifact from a session | --- ## CreateAgent Create a new agent. ``` POST /firetiger.nxagent.v2.AgentService/CreateAgent ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `agent_id` | string | Yes | ID for the new agent (alphanumeric, hyphens, underscores; must start with a letter or digit) | | `agent` | [Agent](types/agent.txt) | No | Initial agent configuration (title, description, prompt, connections) | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/CreateAgent" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "agent_id": "my-monitor", "agent": { "title": "Production Error Monitor", "description": "Monitors production logs for error spikes and investigates root causes.", "prompt": "You are a production error monitor. Check for elevated error rates and investigate anomalies.", "connections": [ { "name": "connections/prod-postgres", "enabled_tools": ["TOOL_POSTGRES_QUERY"] } ], "state": "AGENT_STATE_ON" } }' ``` **Response** ```json { "agent": { "name": "agents/my-monitor", "title": "Production Error Monitor", "description": "Monitors production logs for error spikes and investigates root causes.", "prompt": "You are a production error monitor. Check for elevated error rates and investigate anomalies.", "connections": [ { "name": "connections/prod-postgres", "enabledTools": ["TOOL_POSTGRES_QUERY"] } ], "state": "AGENT_STATE_ON", "createdBy": "user_abc123", "createTime": "2024-06-15T10:00:00Z", "updateTime": "2024-06-15T10:00:00Z" } } ``` --- ## GetAgent Retrieve an agent by name. ``` POST /firetiger.nxagent.v2.AgentService/GetAgent ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the agent (`agents/{agent}`) | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/GetAgent" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "agents/my-monitor"}' ``` --- ## ListAgents List agents with optional filtering and pagination. ``` POST /firetiger.nxagent.v2.AgentService/ListAgents ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `page_size` | integer | No | Maximum results per page | | `page_token` | string | No | Token for the next page of results | | `filter` | string | No | [Filter](index.txt#filtering) expression | | `order_by` | string | No | Field to sort by (e.g. `create_time desc`). Supported fields: `create_time`, `update_time` | | `show_deleted` | boolean | No | Include soft-deleted agents | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/ListAgents" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"page_size": 10}' ``` **Response** ```json { "agents": [ { "name": "agents/my-monitor", "title": "Production Error Monitor", "state": "AGENT_STATE_ON", "createTime": "2024-06-15T10:00:00Z", "updateTime": "2024-06-15T10:00:00Z" } ], "nextPageToken": "" } ``` --- ## UpdateAgent Update an existing agent. Use `update_mask` to specify which fields to modify. ``` POST /firetiger.nxagent.v2.AgentService/UpdateAgent ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `agent` | [Agent](types/agent.txt) | Yes | The agent with `name` set and updated fields | | `update_mask` | string | Yes | Comma-separated list of fields to update. Must name at least one field; requests without a mask are rejected with `INVALID_ARGUMENT` (full-resource replacement is not supported). | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/UpdateAgent" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "agent": { "name": "agents/my-monitor", "state": "AGENT_STATE_OFF" }, "update_mask": "state" }' ``` --- ## DeleteAgent Soft-delete an agent. The resource will still be accessible via GetAgent but excluded from ListAgents results unless `show_deleted` is set. ``` POST /firetiger.nxagent.v2.AgentService/DeleteAgent ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the agent to delete (`agents/{agent}`) | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/DeleteAgent" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "agents/my-monitor"}' ``` --- ## UndeleteAgent Restore a soft-deleted agent. ``` POST /firetiger.nxagent.v2.AgentService/UndeleteAgent ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the agent to restore (`agents/{agent}`) | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/UndeleteAgent" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "agents/my-monitor"}' ``` **Response** ```json { "agent": { "name": "agents/my-monitor", "title": "Production Error Monitor", "state": "AGENT_STATE_ON", "createTime": "2024-06-15T10:00:00Z", "updateTime": "2024-06-16T09:00:00Z" } } ``` --- ## CreateSession Create a new session for an agent, optionally with initial messages. ``` POST /firetiger.nxagent.v2.AgentService/CreateSession ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `parent` | string | Yes | Parent agent resource name (`agents/{agent}`) | | `session_id` | string | No | Session ID to use. If not provided, the server generates one. | | `initial_messages` | Any[] | No | Initial messages to write to the session upon creation | | `associated_resources` | string[] | No | Resource names to link to this session | | `incognito` | bool | No | Hide from normal listings; write operations are skipped | | `automated` | bool | No | Flag the session as started by automation (scheduled run, trigger). The bash tool auto-denies any domain-approval request instead of pausing — there's no human to click Approve/Deny. Set by the scheduled-runs and triggers services; interactive callers typically leave this unset. | | `bash_prelude` | string | No | Bash script prepended invisibly to every `bash` tool call in this session. Used for per-session bootstrap (e.g. cloning a repo on first use). The script must be idempotent — it runs before every bash call, not just the first. Empty disables. NXL runtime only. | | `inputs` | map | No | Typed input values for the entry task. Keys match the entry task's `input { }` field names; each value is wrapped in `google.protobuf.Any`. The server validates each value's `type_url` against the agent's declared schema before the session starts. Repeated inputs use the `firetiger.nxagent.v2.AnyList` wrapper. NXL runtime only. | | `dry_run` | bool | No | Run the session in dry-run mode for backtesting. Auto-flips `incognito` to true and stubs all write-classified tools (e.g. `create_issue`, notification sends) so no real issues or outbound messages are produced. Pair with `mock_time` to evaluate behavior as of a past timestamp. | | `title` | string | No | Human-readable display name for the session (shown in chat-history UIs). | | `openaicompat_provider` | string | No | Per-session OpenAI-compatible provider override, e.g. `"deepseek"`, `"together"`, `"baseten"`. When set alongside `openaicompat_model`, the executor routes medium and heavy tasks to this provider instead of the deployment default. Must be set together with `openaicompat_model`. | | `openaicompat_model` | string | No | Model ID for the `openaicompat_provider`, e.g. `"deepseek-chat"`. Required when `openaicompat_provider` is set. | | `openaicompat_reasoning_effort` | string | No | Reasoning effort for models that support it: `"low"`, `"medium"`, or `"high"`. Optional; only meaningful for reasoning-capable models like `deepseek-reasoner`. | ### Typed inputs For NXL-runtime agents whose entry task declares an `input { }` block (e.g. `input { issue: Issue }`), the `inputs` field is the preferred way to supply those values. Each `Any` must wrap the proto message matching the declared type, or — for primitives — the corresponding `google.protobuf.{String,Int64,Double,Bool}Value` wrapper. The server validates types at the boundary and returns a clear error on mismatch. Example with a typed `Issue` input plus a repeated `Customer[]` input: ```json { "parent": "agents/issue-triager", "runtime": "SESSION_RUNTIME_NXL", "inputs": { "issue": { "@type": "type.googleapis.com/firetiger.issues.v2.Issue", "name": "issues/FT-8888", "title": "demo issue" }, "customers": { "@type": "type.googleapis.com/firetiger.nxagent.v2.AnyList", "items": [ {"@type": "type.googleapis.com/firetiger.customers.v2.Customer", "name": "customers/abc"}, {"@type": "type.googleapis.com/firetiger.customers.v2.Customer", "name": "customers/def"} ] } } } ``` Inside the agent's task prompt, these values are addressable by input field name, such as `${issue.name}`, `${issue.title}`, `${customers}`, etc. **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/CreateSession" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "parent": "agents/my-monitor", "session_id": "run-2024-06-15", "associated_resources": ["incidents/inc-001"] }' ``` **Response** ```json { "session": { "name": "agents/my-monitor/sessions/run-2024-06-15", "associatedResources": ["incidents/inc-001"], "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } } ``` --- ## LaunchBacktest Spawn a dry-run session of the supplied agent at a past timestamp. The session runs the agent's normal configured workflow against historical data; `dry_run=true` (auto-incognito) short-circuits all write-classified tools (`create_issue`, notification sends, etc.) so no real issues or outbound messages are produced. Poll the returned session to see what the agent *would have* flagged. ``` POST /v1/agents:launchBacktest ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `agent_name` | string | Yes | Agent to backtest (`agents/{agent}`). Its system prompt and configured tools drive the session. | | `mock_time` | string | Yes | RFC3339 timestamp (UTC) the executor should treat as "now". Must be in the past. | | `tool_call_id` | string | No | Opaque tag stored as `tool-calls/{tool_call_id}` in the new session's `associated_resources`, so the caller can later look the session up by that tool call. Empty disables the tag. | **Example** ```bash curl -X POST "{{ site.api_url }}/v1/agents:launchBacktest" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "agent_name": "agents/my-agent", "mock_time": "2026-04-20T16:40:42Z" }' ``` **Response** ```json { "session": "agents/my-agent/sessions/01H..." } ``` --- ## GetSession Retrieve a session by name. ``` POST /firetiger.nxagent.v2.AgentService/GetSession ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the session (`agents/{agent}/sessions/{session}`) | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/GetSession" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "agents/my-monitor/sessions/run-2024-06-15"}' ``` --- ## UpdateSession Update an existing session. Use `update_mask` to specify which fields to modify. ``` POST /firetiger.nxagent.v2.AgentService/UpdateSession ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `session` | [Session](types/session.txt) | Yes | The session with `name` set and updated fields | | `update_mask` | string | No | Comma-separated list of fields to update. If omitted, all provided fields are updated. | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/UpdateSession" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "session": { "name": "agents/my-monitor/sessions/run-2024-06-15", "associated_resources": ["incidents/inc-001", "objectives/obj-002"] }, "update_mask": "associated_resources" }' ``` --- ## DeleteSession Soft-delete a session. The resource will still be accessible via GetSession but excluded from ListSessions results unless `show_deleted` is set. ``` POST /firetiger.nxagent.v2.AgentService/DeleteSession ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the session to delete (`agents/{agent}/sessions/{session}`) | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/DeleteSession" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "agents/my-monitor/sessions/run-2024-06-15"}' ``` --- ## ListSessions List sessions for an agent (or across all agents) with optional filtering and pagination. Returns basic session metadata without runtime state. ``` POST /firetiger.nxagent.v2.AgentService/ListSessions ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `parent` | string | No | Parent agent resource name (`agents/{agent}`). If empty, lists sessions across all agents. | | `page_size` | integer | No | Maximum results per page | | `page_token` | string | No | Token for the next page of results | | `filter` | string | No | [Filter](index.txt#filtering) expression (e.g. `create_time >= '2024-01-01T00:00:00Z'`) | | `order_by` | string | No | Field to sort by (e.g. `create_time desc`). Supported fields: `create_time`, `update_time` | | `show_deleted` | boolean | No | Include soft-deleted sessions | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/ListSessions" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"parent": "agents/my-monitor", "page_size": 25}' ``` **Response** ```json { "sessions": [ { "name": "agents/my-monitor/sessions/run-2024-06-15", "associatedResources": ["incidents/inc-001"], "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } ], "nextPageToken": "" } ``` --- ## DescribeSessions List sessions with runtime state from the execution engine, including status, session length, last message time, and conclusion. This is a richer alternative to [ListSessions](#listsessions). ``` POST /firetiger.nxagent.v2.AgentService/DescribeSessions ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `parent` | string | No | Parent agent resource name (`agents/{agent}`). If empty, describes sessions across all agents. | | `page_size` | integer | No | Maximum results per page | | `page_token` | string | No | Token for the next page of results | | `filter` | string | No | [Filter](index.txt#filtering) expression | | `order_by` | string | No | Field to sort by (e.g. `create_time desc`). Supported fields: `create_time`, `update_time` | | `show_deleted` | boolean | No | Include soft-deleted sessions | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/DescribeSessions" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"parent": "agents/my-monitor", "page_size": 10}' ``` **Response** Returns a list of [SessionDescription](types/session.txt#session-description) objects: ```json { "sessionDescriptions": [ { "session": { "name": "agents/my-monitor/sessions/run-2024-06-15", "associatedResources": ["incidents/inc-001"], "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" }, "status": "STATUS_WAITING", "sessionLength": 12, "lastMessage": "2024-06-15T15:45:00Z", "conclusion": { "done": { "message": "Investigation complete. Root cause identified as a memory leak in the auth service introduced in v2.3.", "issues": [ { "title": "Auth service memory leak", "description": "Memory usage grows linearly under load, causing OOM kills after ~4 hours of peak traffic." } ] } } } ], "nextPageToken": "" } ``` --- ## Write Send messages to an agent session. Messages are wrapped in [`google.protobuf.Any`](https://protobuf.dev/programming-guides/proto3/#any) and typically contain [Activity](types/activity.txt) protos. The `@type` field on each message tells the server how to decode it — for user messages, use `type.googleapis.com/firetiger.nxagent.v1.Activity`. A checkpoint write (the default) triggers the agent to process the new messages. You can target an existing session by name, or provide a `parent` agent to auto-create a new session. ``` POST /firetiger.nxagent.v2.AgentService/Write ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `session` | string | No | Resource name of the session to write to (`agents/{agent}/sessions/{session}`). Either `session` or `parent` must be set. | | `parent` | string | No | Parent agent resource name (`agents/{agent}`). If set (and `session` is empty), a new session is auto-created. | | `messages` | Any[] | Yes | Messages to write (must not be empty). Each message is a `google.protobuf.Any` with an `@type` field. | | `write_mode` | string | No | How to write: `WRITE_MODE_CHECKPOINT` (default, triggers agent), `WRITE_MODE_PROVISIONAL` (queued silently), or `WRITE_MODE_PROVISIONAL_OR_CHECKPOINT` (provisional if agent is running, checkpoint if idle). | | `artifacts` | Artifact[] | No | References (by `sha256`) to artifacts already uploaded via `PUT /v1/artifacts/{sha256}`. Each entry carries `{name, sha256, content_type, content_encoding}`. The server imports each referenced object into the session's artifact namespace under the given `name` before the messages are appended, so attachments arrive atomically with the message. The CAS object must already exist (returns `FailedPrecondition` otherwise). Each artifact is capped at 64 MiB, with a 128 MiB combined per-request cap. | **Example -- send a user message to a session** ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/Write" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "session": "agents/my-monitor/sessions/run-2024-06-15", "messages": [ { "@type": "type.googleapis.com/firetiger.nxagent.v1.Activity", "user": { "text": {"text": "Check the error rates for the payments service in the last hour."} } } ] }' ``` **Example -- auto-create a session and send a message** ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/Write" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "parent": "agents/my-monitor", "messages": [ { "@type": "type.googleapis.com/firetiger.nxagent.v1.Activity", "user": { "text": {"text": "Investigate elevated 500 errors on the checkout endpoint."} } } ] }' ``` **Response** | Field | Type | Description | |:------|:-----|:------------| | `session` | string | Resource name of the session the write targeted. | | `sessionOffset` | integer | 0-based index at which the server placed the first message of this write. Clients that tail the session via [Notifications](notifications.txt) can use this to reconcile their optimistic copy against the authoritative order. | | `sessionLength` | integer | Total number of messages in the session after this write. | ```json { "session": "agents/my-monitor/sessions/ses-abc123", "sessionOffset": "0", "sessionLength": "1" } ``` --- ## Read Read messages from an agent session starting from a given offset. Returns the full conversation history as a list of `google.protobuf.Any` messages (typically [Activity](types/activity.txt) protos). ``` POST /firetiger.nxagent.v2.AgentService/Read ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `session` | string | Yes | Resource name of the session (`agents/{agent}/sessions/{session}`) | | `session_offset` | integer | No | Start reading from this message index (0-based). Defaults to 0. | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/Read" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "session": "agents/my-monitor/sessions/run-2024-06-15", "session_offset": 0 }' ``` **Response** ```json { "session": "agents/my-monitor/sessions/run-2024-06-15", "sessionOffset": "0", "sessionLength": "3", "messages": [ { "@type": "type.googleapis.com/firetiger.nxagent.v1.Activity", "user": { "text": {"text": "Check the error rates for the payments service."} }, "timestamp": "2024-06-15T14:30:00Z" }, { "@type": "type.googleapis.com/firetiger.nxagent.v1.Activity", "assistant": { "text": {"text": "I'll query the error rates now."}, "toolCalls": [ {"id": "tc-1", "name": "TOOL_PROMQL_QUERY", "arguments": "{\"query\": \"rate(http_requests_total{status=~\\\"5..\\\"}[1h])\"}"} ] }, "timestamp": "2024-06-15T14:30:05Z" }, { "@type": "type.googleapis.com/firetiger.nxagent.v1.Activity", "user": { "toolResults": [ {"toolCallId": "tc-1", "content": "{\"status\":\"success\",\"data\":{\"result\":[{\"value\":\"0.05\"}]}}"} ] }, "timestamp": "2024-06-15T14:30:10Z" } ], "status": "STATUS_WAITING" } ``` --- ## GetArtifact Retrieve an artifact from a session by its SHA-256 hash. Supports byte/line slicing, jq filtering on JSON content, and automatic protobuf→JSON conversion. ``` POST /firetiger.nxagent.v2.AgentService/GetArtifact ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `session` | string | Yes | Resource name of the session (`agents/{agent}/sessions/{session}`) | | `sha256` | string | Yes | SHA-256 hash of the artifact to retrieve | | `byte_offset` | int64 | No | Byte offset to start reading from (default: 0). Cannot be combined with `line_offset`/`line_limit`. | | `byte_limit` | int64 | No | Maximum number of bytes to return (`-1` means read to end). Cannot be combined with `line_offset`/`line_limit`. | | `line_offset` | int64 | No | Line offset to start reading from (default: 0). Cannot be combined with `byte_offset`/`byte_limit`. | | `line_limit` | int64 | No | Maximum number of lines to return (`-1` means read to end). Cannot be combined with `byte_offset`/`byte_limit`. | | `jq` | string | No | JQ filter expression to apply to JSON content. Applied after byte/line slicing. | | `accept` | string | No | Desired content type(s) as a comma-separated MIME list (e.g. `"text/*, application/json"`). Currently supports `application/protobuf` → `application/json` conversion via protojson. When unset, the artifact is returned in its native format. | **Response body** | Field | Type | Description | |:------|:-----|:------------| | `contents` | bytes | The artifact content (possibly sliced, filtered, or converted). | | `content_type` | string | MIME type of the returned content. | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/GetArtifact" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "session": "agents/my-monitor/sessions/run-2024-06-15", "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "jq": ".[:10]" }' ``` ## Connections Connections are integrations with external tools and services. Each connection stores credentials and configuration for a specific external system (databases, SaaS platforms, cloud providers, etc.). **Service**: `firetiger.connections.v1.ConnectionsService` **Resource name pattern**: `connections/{connection_id}` **Access**: Read-only **Resource type**: [Connection](types/connection.txt) Connections are configured through the Firetiger UI. The API provides read-only access to list and inspect your configured connections. ## Example flow List your connections to see what is available, then fetch a specific one to inspect its full configuration. **1. List connections** ```bash curl "{{ site.api_url }}/v1/connections" \ -u "$USERNAME:$PASSWORD" ``` ```json { "connections": [ { "name": "connections/prod-postgres", "displayName": "Production Postgres", "connectionType": "CONNECTION_TYPE_POSTGRES" }, { "name": "connections/staging-http", "displayName": "Staging API", "connectionType": "CONNECTION_TYPE_HTTP" } ] } ``` **2. Get a specific connection** Use the resource name from the list response to fetch full details, including resolved credentials and tool configurations. ```bash curl "{{ site.api_url }}/v1/connections/prod-postgres" \ -u "$USERNAME:$PASSWORD" ``` ```json { "connection": { "name": "connections/prod-postgres", "displayName": "Production Postgres", "description": "Primary production database", "connectionType": "CONNECTION_TYPE_POSTGRES", "connectionDetails": { "postgres": { "host": "db.example.com", "port": 5432, "database": "production", "username": "readonly", "password": "resolved-secret", "sslMode": "require" } }, "toolConfigurations": [ { "tool": "TOOL_POSTGRES_QUERY", "enabled": true } ] } } ``` ## Methods | Method | Description | |:-------|:------------| | [GetConnection](#getconnection) | Retrieve a connection by name | | [ListConnections](#listconnections) | List connections with filtering and pagination | | [ListConnectionTypes](#listconnectiontypes) | List all supported connection types | --- ## GetConnection Retrieve a connection by name. Returns full connection details including credentials fetched from the secrets provider. ``` GET /v1/connections/{connection_id} ``` **Path parameters** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `connection_id` | string | Yes | ID portion of the connection resource name (e.g. `prod-postgres` for `connections/prod-postgres`) | **Example** ```bash curl "{{ site.api_url }}/v1/connections/prod-postgres" \ -u "$USERNAME:$PASSWORD" ``` --- ## ListConnections List connections with optional filtering and pagination. Connection details are not populated in list responses. ``` GET /v1/connections ``` **Query parameters** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `filter` | string | No | [Filter](index.txt#filtering) expression (e.g. `connection_type="postgres"`) | | `order_by` | string | No | Field to sort by | | `page_size` | integer | No | Maximum results per page | | `page_token` | string | No | Token for the next page of results | | `show_deleted` | boolean | No | Include soft-deleted connections | **Example** ```bash curl --get "{{ site.api_url }}/v1/connections" \ -u "$USERNAME:$PASSWORD" \ --data-urlencode 'filter=connection_type="postgres"' \ --data-urlencode 'page_size=25' ``` --- ## ListConnectionTypes List all supported connection types with their metadata and available tools. Returns only user-creatable types. ``` GET /v1/connection-types ``` **Query parameters** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `filter` | string | No | [Filter](index.txt#filtering) expression | | `order_by` | string | No | Sort order | | `page_size` | integer | No | Maximum results per page | | `page_token` | string | No | Token for the next page of results | **Example** ```bash curl "{{ site.api_url }}/v1/connection-types" \ -u "$USERNAME:$PASSWORD" ``` **Response** ```json { "types": [ { "type": "CONNECTION_TYPE_POSTGRES", "displayName": "PostgreSQL", "description": "Query PostgreSQL databases", "availableTools": ["TOOL_POSTGRES_QUERY"] } ] } ``` --- ## Integration OAuth and webhook methods These methods are part of the `firetiger.connections.v1` package and handle OAuth flows and webhook registration for specific integration types. ### StartSlackOAuth Initiate an OAuth flow to connect a Slack workspace. ``` POST /v1/slack-connections:startOAuth ``` ### HandleSlackEvent Internal endpoint used by the Firetiger Integrations Server to forward Slack Events API webhooks to the appropriate deployment. Not intended for direct use; deployments authenticate the call via M2M credentials. ``` POST /v1/slack-connections:handleEvent ``` ### StartGithubOAuth Initiate a GitHub App installation flow for a connection. ``` POST /v1/github-connections:startOAuth ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `connection_name` | string | No | Existing connection name (format: `connections/{connection_id}`) when re-authorizing or expanding scopes on an existing GitHub install. Omit to create a new connection. | | `return_path` | string | No | Relative UI path (must begin with `/`) to redirect to after the install completes. Defaults to `/integrations/connections`. | ### ListRepositories List the repositories a GitHub connection's installation can access. Used to populate a repository picker instead of free-text entry. ``` POST /v1/github-connections:listRepositories ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `connection_name` | string | Yes | The GitHub connection (format: `connections/{connection_id}`) whose installation's repositories to list. | | `page_size` | int32 | No | Maximum repositories to return in one page (AIP-158). Defaults to and is capped at 100; values ≤ 0 use the default. | | `page_token` | string | No | Opaque token from a prior response's `next_page_token` (AIP-158). Empty returns the first page. | **Response body** | Field | Type | Description | |:------|:-----|:------------| | `repositories` | Repository[] | The repositories the installation can access. | | `next_page_token` | string | Token to pass as `page_token` for the next page; empty when there are no more repositories (AIP-158). | Each `Repository` carries `full_name` (`owner/repo`), `default_branch`, and `private`. ### ListRepositoryBranches List the branches of a repository accessible to a connection's installation, plus the repository's default branch. Used to populate a branch picker. ``` POST /v1/github-connections:listBranches ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `connection_name` | string | Yes | The GitHub connection (format: `connections/{connection_id}`) that grants access. | | `repository` | string | Yes | The repository, in `owner/repo` form. | | `page_size` | int32 | No | Maximum branches to return in one page (AIP-158). Defaults to and is capped at 100; values ≤ 0 use the default. | | `page_token` | string | No | Opaque token from a prior response's `next_page_token` (AIP-158). Empty returns the first page. | **Response body** | Field | Type | Description | |:------|:-----|:------------| | `branches` | string[] | Branch names on the repository. | | `default_branch` | string | The repository's default branch (e.g. `main`). Returned on every page. | | `next_page_token` | string | Token to pass as `page_token` for the next page; empty when there are no more branches (AIP-158). | ### StartLinearOAuth Initiate an OAuth flow to connect a Linear workspace. ``` POST /v1/linear-connections:startOAuth ``` ### HandleLinearEvent Internal endpoint used by the Firetiger Integrations Server to forward Linear webhook events to the appropriate deployment. Not intended for direct use; deployments authenticate the call via M2M credentials. ``` POST /v1/linear-connections:handleEvent ``` ### CreateOrganizationInvitation Create a Clerk organization invitation. ``` POST /v1/clerk-connections:createOrganizationInvitation ``` ### RegisterWebhook Register an Incident.io webhook for a connection. ``` POST /v1/connections/{connection}:registerIncidentIoWebhook ``` ### RegisterPagerdutyWebhook Mint a PagerDuty V3 webhook URL for a connection. Persists a rotation-safe token on the connection and returns the URL to paste into the PagerDuty webhook subscription. The signing secret is supplied separately via `UpdateConnection` after the user finishes configuring the PagerDuty side. ``` POST /v1/pagerduty-connections:registerWebhook ``` **Auth.** Requires a Clerk JWT (`Authorization: Bearer `). Basic-auth callers cannot use this endpoint because the integrations server needs a deployment-scoped identity from JWT/M2M claims; the API server forwards the inbound auth header verbatim and Basic credentials do not carry that context. The Firetiger UI calls this method with a user JWT. **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `connection_name` | string | Yes | Existing PagerDuty connection to attach the webhook to. Format: `connections/{connection_id}`. | **Example** ```bash curl -X POST "$BASE_URL/v1/pagerduty-connections:registerWebhook" \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{"connection_name": "connections/abc-123"}' ``` **Response** ```json { "webhookUrl": "https://integrations.firetigerapi.com/pagerduty/webhook/dGhpc0lzQVRlc3RUb2tlbjEyMzQ1Njc4OTA", "webhookToken": "dGhpc0lzQVRlc3RUb2tlbjEyMzQ1Njc4OTA" } ``` ## Customers Customers represent organizations or entities that use your product. Each customer is identified by an `external_id` drawn from your telemetry system (the value used in WHERE clauses to isolate that customer's requests). Firetiger hashes the external ID to produce a URL-safe resource name. **Service**: `firetiger.customers.v2.CustomersService` **Resource name pattern**: `customers/{customer_id}` **Access**: Read-write **Resource type**: [Customer](types/customer.txt) ## Example flow Create a customer, list customers to confirm it exists, then update a field. **1. Create a customer** ```bash curl -X POST "{{ site.api_url }}/firetiger.customers.v2.CustomersService/CreateCustomer" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "customer_id": "acme-corp", "customer": { "external_id": "acme-corp", "display_name": "Acme Corporation", "description": "Enterprise SaaS customer on the Business plan", "runbook": "Check dashboard at https://internal.example.com/acme for SLO status before escalating." } }' ``` ```json { "customer": { "name": "customers/acme-corp", "externalId": "acme-corp", "id": "65a3089e664b", "displayName": "Acme Corporation", "description": "Enterprise SaaS customer on the Business plan", "runbook": "Check dashboard at https://internal.example.com/acme for SLO status before escalating.", "createTime": "2024-08-10T12:00:00Z", "updateTime": "2024-08-10T12:00:00Z" } } ``` **2. List customers** ```bash curl -X POST "{{ site.api_url }}/firetiger.customers.v2.CustomersService/ListCustomers" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{}' ``` ```json { "customers": [ { "name": "customers/acme-corp", "externalId": "acme-corp", "id": "65a3089e664b", "displayName": "Acme Corporation", "description": "Enterprise SaaS customer on the Business plan", "createTime": "2024-08-10T12:00:00Z", "updateTime": "2024-08-10T12:00:00Z" } ], "nextPageToken": "" } ``` **3. Update the customer's description** Use `update_mask` to change only specific fields. Fields not in the mask are left untouched. ```bash curl -X POST "{{ site.api_url }}/firetiger.customers.v2.CustomersService/UpdateCustomer" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "customer": { "name": "customers/acme-corp", "description": "Enterprise SaaS customer, upgraded to Premium plan" }, "update_mask": "description" }' ``` ```json { "customer": { "name": "customers/acme-corp", "externalId": "acme-corp", "id": "65a3089e664b", "displayName": "Acme Corporation", "description": "Enterprise SaaS customer, upgraded to Premium plan", "runbook": "Check dashboard at https://internal.example.com/acme for SLO status before escalating.", "createTime": "2024-08-10T12:00:00Z", "updateTime": "2024-08-10T12:05:00Z" } } ``` ## Methods | Method | Description | |:-------|:------------| | [CreateCustomer](#createcustomer) | Create a new customer | | [GetCustomer](#getcustomer) | Retrieve a customer by name | | [UpdateCustomer](#updatecustomer) | Update an existing customer | | [DeleteCustomer](#deletecustomer) | Soft-delete a customer | | [ListCustomers](#listcustomers) | List customers with filtering and pagination | --- ## CreateCustomer Create a new customer. ``` POST /firetiger.customers.v2.CustomersService/CreateCustomer ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `customer_id` | string | Yes | ID for the new customer (alphanumeric, hyphens, underscores; must start with a letter or digit) | | `customer` | [Customer](types/customer.txt) | Yes | The customer to create (must include `external_id`) | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.customers.v2.CustomersService/CreateCustomer" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "customer_id": "acme-corp", "customer": { "external_id": "acme-corp", "display_name": "Acme Corporation", "description": "Enterprise SaaS customer on the Business plan", "runbook": "Check dashboard at https://internal.example.com/acme for SLO status before escalating." } }' ``` **Response** ```json { "customer": { "name": "customers/acme-corp", "externalId": "acme-corp", "id": "65a3089e664b", "displayName": "Acme Corporation", "description": "Enterprise SaaS customer on the Business plan", "runbook": "Check dashboard at https://internal.example.com/acme for SLO status before escalating.", "createTime": "2024-08-10T12:00:00Z", "updateTime": "2024-08-10T12:00:00Z" } } ``` --- ## GetCustomer Retrieve a customer by name. ``` POST /firetiger.customers.v2.CustomersService/GetCustomer ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the customer | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.customers.v2.CustomersService/GetCustomer" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "customers/acme-corp"}' ``` --- ## UpdateCustomer Update an existing customer. Use `update_mask` to specify which fields to modify. ``` POST /firetiger.customers.v2.CustomersService/UpdateCustomer ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `customer` | [Customer](types/customer.txt) | Yes | The customer with `name` set and updated fields | | `update_mask` | string | No | Comma-separated list of fields to update. If omitted, all provided fields are updated. | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.customers.v2.CustomersService/UpdateCustomer" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "customer": { "name": "customers/acme-corp", "description": "Enterprise SaaS customer, upgraded to Premium plan" }, "update_mask": "description" }' ``` --- ## DeleteCustomer Soft-delete a customer. The resource will still be accessible via Get but excluded from List results unless `show_deleted` is set. ``` POST /firetiger.customers.v2.CustomersService/DeleteCustomer ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the customer to delete | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.customers.v2.CustomersService/DeleteCustomer" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "customers/acme-corp"}' ``` --- ## ListCustomers List customers with optional filtering and pagination. ``` POST /firetiger.customers.v2.CustomersService/ListCustomers ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `filter` | string | No | [Filter](index.txt#filtering) expression | | `order_by` | string | No | Field to sort by (e.g. `create_time desc`) | | `page_size` | integer | No | Maximum results per page | | `page_token` | string | No | Token for the next page of results | | `show_deleted` | boolean | No | Include soft-deleted customers | **Filter examples** - `display_name : '*acme*'` -- substring match on display name - `external_id = 'acme-corp'` -- exact match on external ID - `create_time > '2024-01-01T00:00:00Z'` -- customers created after a date **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.customers.v2.CustomersService/ListCustomers" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"filter": "display_name : \"*Acme*\"", "page_size": 25}' ``` **Response** ```json { "customers": [ { "name": "customers/acme-corp", "externalId": "acme-corp", "id": "65a3089e664b", "displayName": "Acme Corporation", "description": "Enterprise SaaS customer on the Business plan", "createTime": "2024-08-10T12:00:00Z", "updateTime": "2024-08-10T12:00:00Z" } ], "nextPageToken": "" } ``` --- A v1 API (`firetiger.customers.v1.CustomersService`) is also available but deprecated. Use v2 for new integrations. ## Deployments Deployments track software release events across your infrastructure. Each deployment records which code was shipped, to which environment, and its progression through status changes. Deployment monitor evaluations represent automated agent sessions that watch a deployment for anomalies after it lands. ## DeploymentsService **Service**: `firetiger.deployments.v1.DeploymentsService` **Resource name pattern**: `deployments/{deployment_id}` **Access**: Read-write **Resource types**: [Deployment](types/deployment.txt), [Deployment Status Event](types/deployment.txt#deployment-status-event) ## Example flow Create a deployment to record a release event, then list recent deployments. **1. Create a deployment** ```bash curl -X POST "{{ site.api_url }}/firetiger.deployments.v1.DeploymentsService/CreateDeployment" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "deployment_id": "github-12345", "deployment": { "source": "DEPLOYMENT_SOURCE_GITHUB", "labels": { "repository": "acme-corp/backend", "environment": "production" }, "description": "Deploy main to production" } }' ``` ```json { "deployment": { "name": "deployments/github-12345", "source": "DEPLOYMENT_SOURCE_GITHUB", "status": "DEPLOYMENT_STATUS_PENDING", "labels": { "repository": "acme-corp/backend", "environment": "production" }, "description": "Deploy main to production", "createTime": "2025-03-01T10:00:00Z", "updateTime": "2025-03-01T10:00:00Z" } } ``` **2. List recent production deployments** ```bash curl -X POST "{{ site.api_url }}/firetiger.deployments.v1.DeploymentsService/ListDeployments" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "filter": "labels.environment = \"production\"", "order_by": "create_time desc", "page_size": 5 }' ``` ```json { "deployments": [ { "name": "deployments/github-12345", "source": "DEPLOYMENT_SOURCE_GITHUB", "status": "DEPLOYMENT_STATUS_SUCCESS", "labels": { "repository": "acme-corp/backend", "environment": "production" }, "createTime": "2025-03-01T10:00:00Z", "updateTime": "2025-03-01T10:05:00Z" } ], "nextPageToken": "" } ``` **3. Get a specific deployment** ```bash curl -X POST "{{ site.api_url }}/firetiger.deployments.v1.DeploymentsService/GetDeployment" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "deployments/github-12345"}' ``` ```json { "deployment": { "name": "deployments/github-12345", "source": "DEPLOYMENT_SOURCE_GITHUB", "status": "DEPLOYMENT_STATUS_SUCCESS", "labels": { "repository": "acme-corp/backend", "environment": "production" }, "createTime": "2025-03-01T10:00:00Z", "updateTime": "2025-03-01T10:05:00Z", "startTime": "2025-03-01T10:01:00Z", "completeTime": "2025-03-01T10:05:00Z", "externalId": "12345", "externalUrl": "https://github.com/acme-corp/backend/deployments/12345", "description": "Deploy main to production" } } ``` ## Methods | Method | Description | |:-------|:------------| | [CreateDeployment](#createdeployment) | Create a new deployment | | [GetDeployment](#getdeployment) | Retrieve a deployment by name | | [ListDeployments](#listdeployments) | List deployments with filtering and pagination | | [ListDeploymentStatusEvents](#listdeploymentstatusevents) | List the status event changelog for a deployment | | [GetDeploymentEnvironment](#getdeploymentenvironment) | Retrieve a deployment environment by name | | [ListDeploymentEnvironments](#listdeploymentenvironments) | List deployment environments | | [UpdateDeploymentEnvironment](#updatedeploymentenvironment) | Update an environment's monitoring toggle or description | --- ## CreateDeployment Create a new deployment to record a release event. ``` POST /firetiger.deployments.v1.DeploymentsService/CreateDeployment ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `deployment_id` | string | Yes | ID for the new deployment (alphanumeric, hyphens, underscores) | | `deployment` | [Deployment](types/deployment.txt) | Yes | The deployment to create | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.deployments.v1.DeploymentsService/CreateDeployment" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "deployment_id": "github-12345", "deployment": { "source": "DEPLOYMENT_SOURCE_GITHUB", "labels": { "repository": "acme-corp/backend", "environment": "production" }, "description": "Deploy main to production" } }' ``` **Response** ```json { "deployment": { "name": "deployments/github-12345", "source": "DEPLOYMENT_SOURCE_GITHUB", "status": "DEPLOYMENT_STATUS_PENDING", "labels": { "repository": "acme-corp/backend", "environment": "production" }, "description": "Deploy main to production", "createTime": "2025-03-01T10:00:00Z", "updateTime": "2025-03-01T10:00:00Z" } } ``` --- ## GetDeployment Retrieve a deployment by name. ``` POST /firetiger.deployments.v1.DeploymentsService/GetDeployment ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the deployment (`deployments/{id}`) | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.deployments.v1.DeploymentsService/GetDeployment" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "deployments/github-12345"}' ``` **Response** ```json { "deployment": { "name": "deployments/github-12345", "source": "DEPLOYMENT_SOURCE_GITHUB", "status": "DEPLOYMENT_STATUS_SUCCESS", "labels": { "repository": "acme-corp/backend", "environment": "production", "ref": "main", "sha": "abc123def456" }, "createTime": "2025-03-01T10:00:00Z", "updateTime": "2025-03-01T10:05:00Z", "startTime": "2025-03-01T10:01:00Z", "completeTime": "2025-03-01T10:05:00Z", "externalId": "12345", "externalUrl": "https://github.com/acme-corp/backend/deployments/12345", "description": "Deploy main to production" } } ``` --- ## ListDeployments List deployments with optional filtering and pagination. ``` POST /firetiger.deployments.v1.DeploymentsService/ListDeployments ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `filter` | string | No | [Filter](index.txt#filtering) expression | | `order_by` | string | No | Field to sort by (e.g. `create_time desc`) | | `page_size` | integer | No | Maximum results per page | | `page_token` | string | No | Token for the next page of results | | `show_deleted` | boolean | No | Include soft-deleted deployments | **Filter examples** - `labels.environment = "production"` -- deployments to production - `labels.repository = "acme-corp/backend"` -- deployments from a specific repository - `status = "DEPLOYMENT_STATUS_SUCCESS"` -- successful deployments - `create_time >= "2025-01-01T00:00:00Z"` -- deployments after a given date **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.deployments.v1.DeploymentsService/ListDeployments" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "filter": "labels.environment = \"production\"", "order_by": "create_time desc", "page_size": 10 }' ``` **Response** ```json { "deployments": [ { "name": "deployments/github-12345", "source": "DEPLOYMENT_SOURCE_GITHUB", "status": "DEPLOYMENT_STATUS_SUCCESS", "labels": { "repository": "acme-corp/backend", "environment": "production" }, "createTime": "2025-03-01T10:00:00Z", "updateTime": "2025-03-01T10:05:00Z" } ], "nextPageToken": "" } ``` --- ## ListDeploymentStatusEvents List the status event changelog for a deployment, showing how its status changed over time. ``` POST /firetiger.deployments.v1.DeploymentsService/ListDeploymentStatusEvents ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `parent` | string | Yes | Parent deployment resource name (`deployments/{id}`) | | `filter` | string | No | [Filter](index.txt#filtering) expression | | `order_by` | string | No | Field to sort by (e.g. `event_time desc`) | | `page_size` | integer | No | Maximum results per page | | `page_token` | string | No | Token for the next page of results | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.deployments.v1.DeploymentsService/ListDeploymentStatusEvents" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"parent": "deployments/github-12345", "page_size": 25}' ``` **Response** ```json { "statusEvents": [ { "name": "deployments/github-12345/status-events/evt-001", "deployment": "deployments/github-12345", "status": "DEPLOYMENT_STATUS_IN_PROGRESS", "description": "Deployment started", "eventTime": "2025-03-01T10:01:00Z", "createTime": "2025-03-01T10:01:00Z" }, { "name": "deployments/github-12345/status-events/evt-002", "deployment": "deployments/github-12345", "status": "DEPLOYMENT_STATUS_SUCCESS", "description": "Deployment completed successfully", "eventTime": "2025-03-01T10:05:00Z", "createTime": "2025-03-01T10:05:00Z" } ], "nextPageToken": "" } ``` --- ## GetDeploymentEnvironment Retrieve a [DeploymentEnvironment](types/deployment-environment.txt) by name. ``` POST /firetiger.deployments.v1.DeploymentsService/GetDeploymentEnvironment ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name (`deployment-environments/{environment}`) | --- ## ListDeploymentEnvironments List [DeploymentEnvironment](types/deployment-environment.txt) resources, with pagination. ``` POST /firetiger.deployments.v1.DeploymentsService/ListDeploymentEnvironments ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `page_size` | int32 | No | Maximum environments to return | | `page_token` | string | No | Pagination token from a previous response | | `order_by` | string | No | Sort order (e.g. `create_time asc`) | --- ## UpdateDeploymentEnvironment Update a [DeploymentEnvironment](types/deployment-environment.txt). Pass an `update_mask` listing the fields to change — `monitoring_enabled`, `description`, or both. Fields omitted from the mask are left untouched. ``` POST /firetiger.deployments.v1.DeploymentsService/UpdateDeploymentEnvironment ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `deployment_environment` | [DeploymentEnvironment](types/deployment-environment.txt) | Yes | The environment to update, identified by its `name` | | `update_mask` | string | No | Comma-separated field paths to update (defaults to `monitoring_enabled`) | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.deployments.v1.DeploymentsService/UpdateDeploymentEnvironment" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "deployment_environment": { "name": "deployment-environments/production", "description": "Live customer traffic." }, "update_mask": "description" }' ``` **Response** ```json { "deploymentEnvironment": { "name": "deployment-environments/production", "monitoringEnabled": true, "description": "Live customer traffic.", "createTime": "2026-02-13T01:04:29.873715Z", "updateTime": "2026-06-26T22:40:00.000000Z" } } ``` --- ## DeploymentMonitorEvaluationService **Service**: `firetiger.deploy_monitor.v1.DeploymentMonitorEvaluationService` **Resource name pattern**: `deployment-monitor-evaluations/{evaluation_id}` **Access**: Read-write **Resource type**: [Deployment Monitor Evaluation](types/deployment.txt#deployment-monitor-evaluation) Deployment monitor evaluations represent automated agent sessions that watch a deployment for anomalies. When a deployment lands, the deploy-monitor agent starts a monitoring session, observes system behavior over a window of time, and produces a summary with an overall outcome. ## Methods | Method | Description | |:-------|:------------| | [GetDeploymentMonitorEvaluation](#getdeploymentmonitorevaluation) | Retrieve a deployment monitor evaluation by name | | [ListDeploymentMonitorEvaluations](#listdeploymentmonitorevaluations) | List evaluations with filtering and pagination | --- ## GetDeploymentMonitorEvaluation Retrieve a deployment monitor evaluation by name. ``` POST /firetiger.deploy_monitor.v1.DeploymentMonitorEvaluationService/GetDeploymentMonitorEvaluation ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the evaluation (`deployment-monitor-evaluations/{id}`) | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.deploy_monitor.v1.DeploymentMonitorEvaluationService/GetDeploymentMonitorEvaluation" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "deployment-monitor-evaluations/eval-abc123"}' ``` **Response** ```json { "evaluation": { "name": "deployment-monitor-evaluations/eval-abc123", "createTime": "2025-03-01T10:05:00Z", "updateTime": "2025-03-01T10:35:00Z", "sessionName": "agents/deploy-monitor/sessions/sess-xyz789", "status": "DEPLOYMENT_MONITOR_EVALUATION_STATUS_COMPLETED", "githubRunId": "9876543210", "environment": "production", "repository": "acme-corp/backend", "headSha": "abc123def456", "baseSha": "789012ghi345", "prNumber": 42, "deploymentTime": "2025-03-01T10:00:00Z", "summary": "Deployment completed with no anomalies detected. All key metrics remained within expected ranges during the 30-minute monitoring window.", "overallStatus": "successful" } } ``` --- ## ListDeploymentMonitorEvaluations List deployment monitor evaluations with optional filtering and pagination. ``` POST /firetiger.deploy_monitor.v1.DeploymentMonitorEvaluationService/ListDeploymentMonitorEvaluations ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `page_size` | integer | No | Maximum results per page | | `page_token` | string | No | Token for the next page of results | | `filter` | string | No | [Filter](index.txt#filtering) expression | | `order_by` | string | No | Field to sort by (default: `create_time desc`) | | `show_deleted` | boolean | No | Include soft-deleted evaluations | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.deploy_monitor.v1.DeploymentMonitorEvaluationService/ListDeploymentMonitorEvaluations" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "filter": "environment = \"production\"", "order_by": "create_time desc", "page_size": 10 }' ``` **Response** ```json { "evaluations": [ { "name": "deployment-monitor-evaluations/eval-abc123", "createTime": "2025-03-01T10:05:00Z", "updateTime": "2025-03-01T10:35:00Z", "status": "DEPLOYMENT_MONITOR_EVALUATION_STATUS_COMPLETED", "environment": "production", "repository": "acme-corp/backend", "headSha": "abc123def456", "overallStatus": "successful" } ], "nextPageToken": "" } ``` ## Investigations Investigations are agent-driven sessions that analyze and diagnose issues in your system. Each investigation tracks metadata like a display name, description, and execution status. **Service**: `firetiger.investigations.v1.InvestigationService` **Resource name pattern**: `investigations/{investigation_id}` **Access**: Read-write (no delete) **Resource type**: [Investigation](types/investigation.txt) ## Example flow Create an investigation to kick off an agent session, then poll for its status. **1. Create an investigation** The agent's initial prompt is the `content` of the user-text activity in `initial_activities` — that's what the agent reads and acts on. The `description` field is investigation metadata only and is not sent to the agent. You don't need to set `display_name`: when a seed message is present the server generates the title from it. ```bash curl -X POST "{{ site.api_url }}/firetiger.investigations.v1.InvestigationService/CreateInvestigation" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "investigation": {}, "initial_activities": [ { "user": { "text": { "content": "The payments service started returning 500s at 14:00 UTC. Investigate the root cause and affected scope.", "role": "USER" } } } ] }' ``` ```json { "investigation": { "name": "investigations/inv_abc123", "displayName": "Payments service returning 500s", "status": "INVESTIGATION_STATUS_EXECUTING", "createdBy": "user_2xK9mBqHn1pL4vR7wT3eYjZ8aFd", "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } } ``` The `displayName` in the response is the title the server generated from the seed message. **2. Check investigation status** Poll the investigation to see whether the agent has finished its analysis. ```bash curl -X POST "{{ site.api_url }}/firetiger.investigations.v1.InvestigationService/GetInvestigation" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "investigations/inv_abc123"}' ``` ```json { "investigation": { "name": "investigations/inv_abc123", "displayName": "Payments service returning 500s", "status": "INVESTIGATION_STATUS_WAITING", "createdBy": "user_2xK9mBqHn1pL4vR7wT3eYjZ8aFd", "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:35:12Z" } } ``` ## Methods | Method | Description | |:-------|:------------| | [CreateInvestigation](#createinvestigation) | Create a new investigation | | [GetInvestigation](#getinvestigation) | Retrieve an investigation by name | | [ListInvestigations](#listinvestigations) | List investigations with filtering and pagination | | [UpdateInvestigation](#updateinvestigation) | Update an existing investigation | --- ## CreateInvestigation Create a new investigation. The server auto-generates the investigation ID and starts an agent session. The agent's prompt comes from `initial_activities` — seed it with a user-text activity describing what to investigate. If you omit `display_name`, the server auto-titles the investigation from the seed message. ``` POST /firetiger.investigations.v1.InvestigationService/CreateInvestigation ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `investigation` | [Investigation](types/investigation.txt) | Yes | The investigation to create. Only `display_name` and `description` are read from the client (both metadata); other fields are set by the server. `description` is **not** sent to the agent. | | `initial_activities` | [Activity](types/activity.txt)[] | No (but effectively required to give the agent a prompt) | Activities to seed the investigation session with. This is the client input that carries the agent's prompt — put the problem statement in a user-text activity (`user.text.content`), plus any context like log snippets, metric data, or prior analysis. (`artifacts` below can attach files alongside it.) | | `artifacts` | Artifact[] | No | References (by `sha256`) to artifacts already uploaded via `PUT /v1/artifacts/{sha256}`. Each entry carries `{name, sha256, content_type, content_encoding}`. The server imports each referenced object into the new session's artifact namespace under the given `name` before the initial activities are appended, so attachments arrive atomically with the message. The CAS object must already exist (returns `FailedPrecondition` otherwise). Each artifact is capped at 64 MiB, with a 128 MiB combined per-request cap. | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.investigations.v1.InvestigationService/CreateInvestigation" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "investigation": {}, "initial_activities": [ { "user": { "text": { "content": "The payments service started returning 500s at 14:00 UTC. Investigate the root cause and affected scope.", "role": "USER" } } } ] }' ``` **Response** ```json { "investigation": { "name": "investigations/inv_abc123", "displayName": "Payments service returning 500s", "status": "INVESTIGATION_STATUS_EXECUTING", "createdBy": "user_2xK9mBqHn1pL4vR7wT3eYjZ8aFd", "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } } ``` --- ## GetInvestigation Retrieve an investigation by name. ``` POST /firetiger.investigations.v1.InvestigationService/GetInvestigation ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the investigation (`investigations/{id}`) | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.investigations.v1.InvestigationService/GetInvestigation" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "investigations/inv_abc123"}' ``` --- ## ListInvestigations List investigations with optional filtering and pagination. ``` POST /firetiger.investigations.v1.InvestigationService/ListInvestigations ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `filter` | string | No | [Filter](index.txt#filtering) expression (e.g. `status = INVESTIGATION_STATUS_EXECUTING`) | | `order_by` | string | No | Field to sort by. Supported: `create_time`, `update_time`, `display_name`. Append ` desc` or ` asc` for direction. Default: `create_time desc`. | | `page_size` | integer | No | Maximum results per page | | `page_token` | string | No | Token for the next page of results | | `show_deleted` | boolean | No | Include soft-deleted investigations | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.investigations.v1.InvestigationService/ListInvestigations" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"filter": "status = \"INVESTIGATION_STATUS_EXECUTING\"", "page_size": 25}' ``` **Response** ```json { "investigations": [ { "name": "investigations/inv_abc123", "displayName": "Elevated error rate in payments service", "status": "INVESTIGATION_STATUS_EXECUTING", "createdBy": "user_2xK9mBqHn1pL4vR7wT3eYjZ8aFd", "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } ], "nextPageToken": "" } ``` --- ## UpdateInvestigation Update an existing investigation's metadata. Use `update_mask` to specify which fields to modify. ``` POST /firetiger.investigations.v1.InvestigationService/UpdateInvestigation ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `investigation` | [Investigation](types/investigation.txt) | Yes | The investigation with `name` set and updated fields | | `update_mask` | string | No | Comma-separated list of fields to update. Supported: `display_name`, `description`, `status`. If omitted, all provided fields are updated. | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.investigations.v1.InvestigationService/UpdateInvestigation" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "investigation": { "name": "investigations/inv_abc123", "display_name": "Payments 500s - resolved: bad deploy" }, "update_mask": "display_name" }' ``` ## Issues Issues are problems detected automatically by Firetiger's monitoring agents. Each issue represents a distinct problem observed in your system, linked to the agent session that discovered it. Issues are managed by the system and available as read-only resources. This page also covers the issue notification policy, which controls how and where Firetiger sends alerts when new issues are detected. **Services**: `firetiger.issues.v1.IssuesService`, `firetiger.issues.v2.IssueValidationService`, `firetiger.issuenotificationpolicy.v1.IssueNotificationPolicyService` **Resource name patterns**: `issues/{issue_id}` and `issue-notification-policies/{issue_notification_policy_id}` **Access**: Issues are read-only. Issue evidence validation is read-only and does not create or update issues. The notification policy is read-write. **Resource types**: [Issue](types/issue.txt), [ValidateIssueEvidenceResponse](types/issue.txt#validate-issue-evidence-response), [Issue Notification Policy](types/issue.txt#issue-notification-policy) **Filtering notes**: `ListIssues` supports environment-aware filtering via `deployment_environments`, for example `deployment_environments:"deployment-environments/firetiger-cloud"`. ## Example flow List recent issues with a filter, get a specific one, then update the notification policy to route alerts to a new channel. **1. List issues** ```bash curl -X POST "{{ site.api_url }}/firetiger.issues.v1.IssuesService/ListIssues" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"page_size": 10}' ``` ```json { "issues": [ { "name": "issues/iss-db-connection-pool", "displayName": "Database connection pool exhaustion", "description": "The primary database connection pool is consistently hitting its max limit during peak hours.", "source": "deployment-environments/firetiger-cloud", "deploymentEnvironments": [ "deployment-environments/firetiger-cloud" ], "session": "agents/monitor/sessions/s-abc123", "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } ] } ``` **2. Get a specific issue** ```bash curl -X POST "{{ site.api_url }}/firetiger.issues.v1.IssuesService/GetIssue" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "issues/iss-db-connection-pool"}' ``` ```json { "issue": { "name": "issues/iss-db-connection-pool", "displayName": "Database connection pool exhaustion", "description": "The primary database connection pool is consistently hitting its max limit during peak hours, causing request queuing and timeouts for downstream services.", "source": "deployment-environments/firetiger-cloud", "deploymentEnvironments": [ "deployment-environments/firetiger-cloud" ], "session": "agents/monitor/sessions/s-abc123", "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } } ``` **3. Update the notification policy** ```bash curl -X POST "{{ site.api_url }}/firetiger.issuenotificationpolicy.v1.IssueNotificationPolicyService/UpdateIssueNotificationPolicy" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "issue_notification_policy": { "connections": ["connections/slack-oncall", "connections/pagerduty-prod"], "prompt": "Send critical issues to #oncall-alerts in Slack and create a PagerDuty incident." }, "update_mask": "connections,prompt" }' ``` ```json { "issueNotificationPolicy": { "name": "issue-notification-policies/default", "description": "Route critical issues to the on-call Slack channel and PagerDuty", "connections": [ "connections/slack-oncall", "connections/pagerduty-prod" ], "prompt": "Send critical issues to #oncall-alerts in Slack and create a PagerDuty incident.", "state": "ISSUE_NOTIFICATION_POLICY_STATE_ACTIVE", "createTime": "2024-06-10T09:00:00Z", "updateTime": "2024-06-16T10:15:00Z" } } ``` ## Methods | Method | Service | Description | |:-------|:--------|:------------| | [GetIssue](#getissue) | IssuesService | Retrieve an issue by name | | [ListIssues](#listissues) | IssuesService | List issues with filtering and pagination | | [ValidateIssueEvidence](#validateissueevidence) | IssueValidationService | Validate that proposed issues are supported by supplied evidence | | [GetIssueNotificationPolicy](#getissuenotificationpolicy) | IssueNotificationPolicyService | Retrieve the current notification policy | | [UpdateIssueNotificationPolicy](#updateissuenotificationpolicy) | IssueNotificationPolicyService | Update the notification policy | --- ## GetIssue Retrieve an issue by name. ``` POST /firetiger.issues.v1.IssuesService/GetIssue ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the issue (`issues/{id}`) | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.issues.v1.IssuesService/GetIssue" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "issues/iss-db-connection-pool"}' ``` **Response** ```json { "issue": { "name": "issues/iss-db-connection-pool", "displayName": "Database connection pool exhaustion", "description": "The primary database connection pool is consistently hitting its max limit during peak hours, causing request queuing and timeouts for downstream services.", "session": "agents/monitor/sessions/s-abc123", "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } } ``` --- ## ListIssues List issues with optional filtering and pagination. ``` POST /firetiger.issues.v1.IssuesService/ListIssues ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `filter` | string | No | [Filter](index.txt#filtering) expression | | `order_by` | string | No | Field to sort by (e.g. `create_time desc`) | | `page_size` | integer | No | Maximum results per page | | `page_token` | string | No | Token for the next page of results | | `show_deleted` | boolean | No | Include soft-deleted issues | **Example filters** - `deployment_environments:"deployment-environments/firetiger-cloud"` -- issues affecting a specific deployment environment - `workflow_state = 'ISSUE_WORKFLOW_STATE_ACTIONABLE' AND deployment_environments:"deployment-environments/firetiger-cloud"` -- actionable issues scoped to one environment - `services:"services/api-gateway"` -- issues belonging to a specific Service - `workflow_state != 'ISSUE_WORKFLOW_STATE_CLOSED' AND services:"services/api-gateway"` -- open issues for a Service (how the triage agent dedups) - `objectives:"objectives/api-server-latency"` -- issues belonging to a specific Objective (set on objective-breach escalations) - `assignee = "users/user-2abc"` -- issues whose responsible user is a specific user (auto-assigned at creation) **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.issues.v1.IssuesService/ListIssues" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"page_size": 25}' ``` **Response** ```json { "issues": [ { "name": "issues/iss-db-connection-pool", "displayName": "Database connection pool exhaustion", "description": "The primary database connection pool is consistently hitting its max limit during peak hours, causing request queuing and timeouts for downstream services.", "source": "deployment-environments/firetiger-cloud", "deploymentEnvironments": [ "deployment-environments/firetiger-cloud" ], "session": "agents/monitor/sessions/s-abc123", "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } ], "nextPageToken": "" } ``` --- ## ValidateIssueEvidence Validate that proposed issues are supported by concrete evidence before a caller creates or escalates them. This method is advisory: it does not create, update, or delete issues. ``` POST /firetiger.issues.v2.IssueValidationService/ValidateIssueEvidence POST /v2/issues:validateEvidence ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `issues` | [Issue](types/issue.txt)[] | Yes | Proposed issues to validate. Issues may be unsaved and do not need `name` set yet. Send 1 to 10 issues. | | `evidence_context` | string | Yes | Evidence and transcript context the validator should use when judging whether the proposed issues are supported. Maximum 15,000 characters. | | `source_session` | string | No | Agent session that produced the evidence (`agents/{agent}/sessions/{session}`) | **Decision semantics** - `ISSUE_EVIDENCE_VALIDATION_DECISION_PASS`: every proposed issue is supported by the supplied evidence. - `ISSUE_EVIDENCE_VALIDATION_DECISION_FAIL`: at least one proposed issue lacks sufficient evidence. Callers that enforce validation should block creation on this decision. - `ISSUE_EVIDENCE_VALIDATION_DECISION_SKIPPED`: validation was not configured or could not complete. `passes` is true for this fail-open decision, so callers that need audit detail should inspect `decision` as well as `passes`. **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.issues.v2.IssueValidationService/ValidateIssueEvidence" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "issues": [ { "title": "Checkout API errors increased", "description": "Checkout API 500 responses increased in production after the deploy.", "details": "Query at 2026-06-05T18:00:00Z returned 124 500 responses in 5 minutes versus 3 in the same window last week." } ], "evidence_context": "Query at 2026-06-05T18:00:00Z returned 124 500 responses in 5 minutes versus 3 in the same window last week.", "source_session": "agents/change-monitor/sessions/session-123" }' ``` **Response** ```json { "decision": "ISSUE_EVIDENCE_VALIDATION_DECISION_PASS", "passes": true, "feedback": "Issue evidence validation passed.", "findings": [], "validatorVersion": "issue-evidence-v1" } ``` --- ## GetIssueNotificationPolicy Retrieve the current issue notification policy. The notification policy is a singleton resource that controls how Firetiger routes alerts when issues are detected. It defines which connections (e.g. Slack, PagerDuty) receive notifications and includes a prompt that guides the notification agent's behavior. ``` POST /firetiger.issuenotificationpolicy.v1.IssueNotificationPolicyService/GetIssueNotificationPolicy ``` **Request body** This method takes no parameters. Send an empty JSON object. **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.issuenotificationpolicy.v1.IssueNotificationPolicyService/GetIssueNotificationPolicy" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{}' ``` **Response** ```json { "issueNotificationPolicy": { "name": "issue-notification-policies/default", "description": "Route critical issues to the on-call Slack channel and PagerDuty", "connections": [ "connections/slack-oncall", "connections/pagerduty-prod" ], "prompt": "Send critical issues to #oncall-alerts in Slack and create a PagerDuty incident. For non-critical issues, post to #issues in Slack only.", "state": "ISSUE_NOTIFICATION_POLICY_STATE_ACTIVE", "createTime": "2024-06-10T09:00:00Z", "updateTime": "2024-06-15T14:30:00Z" } } ``` --- ## UpdateIssueNotificationPolicy Update the issue notification policy. Use `update_mask` to specify which fields to modify. ``` POST /firetiger.issuenotificationpolicy.v1.IssueNotificationPolicyService/UpdateIssueNotificationPolicy ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `issue_notification_policy` | [IssueNotificationPolicy](types/issue.txt#issue-notification-policy) | Yes | The policy with `name` set and updated fields | | `update_mask` | string | No | Comma-separated list of fields to update. If omitted, all provided fields are updated. | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.issuenotificationpolicy.v1.IssueNotificationPolicyService/UpdateIssueNotificationPolicy" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "issue_notification_policy": { "name": "issue-notification-policies/default", "connections": [ "connections/slack-oncall", "connections/pagerduty-prod", "connections/email-eng-leads" ], "prompt": "Send critical issues to #oncall-alerts in Slack and create a PagerDuty incident. For non-critical issues, post to #issues in Slack and email engineering leads." }, "update_mask": "connections,prompt" }' ``` **Response** ```json { "issueNotificationPolicy": { "name": "issue-notification-policies/default", "description": "Route critical issues to the on-call Slack channel and PagerDuty", "connections": [ "connections/slack-oncall", "connections/pagerduty-prod", "connections/email-eng-leads" ], "prompt": "Send critical issues to #oncall-alerts in Slack and create a PagerDuty incident. For non-critical issues, post to #issues in Slack and email engineering leads.", "state": "ISSUE_NOTIFICATION_POLICY_STATE_ACTIVE", "createTime": "2024-06-10T09:00:00Z", "updateTime": "2024-06-16T10:15:00Z" } } ``` ## Monitoring Plans Monitoring plans track deployment risk for pull requests. When a PR is merged and deployed, Firetiger monitors the deployment for unintended effects based on a plan written by an agent during code review. This page covers three services: **MonitoringPlanService** for plan lifecycle and status updates, **ChangeMonitorNotificationsService** for personal Change Monitor notification setup, and **MonitoringRunService** (legacy) for backward-compatible run access. **Services**: `firetiger.monitoring_plans.v1.MonitoringPlanService`, `firetiger.monitoring_plans.v1.ChangeMonitorNotificationsService`, `firetiger.monitoring_plans.v1.MonitoringRunService` **Resource name patterns**: `monitoring-plans/{plan_id}` and `monitoring-plans/{plan_id}/runs/{run_id}` **Access**: Read-write for status updates, read-only for externally managed monitoring state **Resource types**: [Monitoring Plan](types/monitoring-plan.txt), [Monitoring Run](types/monitoring-plan.txt#monitoring-run) ## Example flow List recent monitoring plans, then fetch details for a specific one. **1. List monitoring plans** ```bash curl -X POST "{{ site.api_url }}/firetiger.monitoring_plans.v1.MonitoringPlanService/ListMonitoringPlans" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"page_size": 5, "order_by": "create_time desc"}' ``` ```json { "monitoringPlans": [ { "name": "monitoring-plans/plan-abc123", "origin": { "repository": "acme-corp/backend", "prNumber": 42, "prTitle": "Fix auth service timeout handling", "prAuthorGithubLogin": "engineer", "prAuthorGithubUserId": "1234567" }, "planSummary": "Fixes auth service timeout handling to prevent cascading failures during peak traffic.", "intendedEffects": [ "Auth service p99 latency stays under 500ms at peak (24h baseline 480ms).", "Cascading 503s attributed to auth timeouts drop to zero." ], "risks": [ "Masked failures: watch auth 5xx rate; alert if > 1% sustained for 5m." ], "createTime": "2024-06-15T14:30:00Z" } ], "nextPageToken": "" } ``` **2. Get plan details** ```bash curl -X POST "{{ site.api_url }}/firetiger.monitoring_plans.v1.MonitoringPlanService/GetMonitoringPlan" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "monitoring-plans/plan-abc123"}' ``` ```json { "monitoringPlan": { "name": "monitoring-plans/plan-abc123", "origin": { "repository": "acme-corp/backend", "prNumber": 42, "prUrl": "https://github.com/acme-corp/backend/pull/42", "headSha": "a1b2c3d4e5f6", "prTitle": "Fix auth service timeout handling", "prAuthorGithubLogin": "engineer", "prAuthorGithubUserId": "1234567" }, "activation": { "mergeSha": "f6e5d4c3b2a1", "environments": ["production"] }, "planContent": "## Intended Effect\nFixes timeout handling in auth service...", "planSummary": "Fixes auth service timeout handling to prevent cascading failures during peak traffic.", "intendedEffects": [ "Auth service p99 latency stays under 500ms at peak (24h baseline 480ms).", "Cascading 503s attributed to auth timeouts drop to zero." ], "risks": [ "Masked failures: watch auth 5xx rate; alert if > 1% sustained for 5m." ], "notificationChannel": "#deploy-alerts", "authorSession": "agents/plan-author/sessions/sess-xyz", "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T15:00:00Z", "deployments": [ { "environment": "production", "deployment": "deployments/dep-789", "deployTime": "2024-06-15T16:00:00Z", "intendedEffectConfirmed": true, "outcome": "MONITORING_OUTCOME_NO_ISSUE", "completeTime": "2024-06-15T17:30:00Z" } ], "lastCheckTime": "2024-06-15T17:30:00Z" } } ``` ## Methods | Method | Service | Description | |:-------|:--------|:------------| | [GetMonitoringPlan](#getmonitoringplan) | MonitoringPlanService | Retrieve a monitoring plan by name | | [ListMonitoringPlans](#listmonitoringplans) | MonitoringPlanService | List monitoring plans with filtering and pagination | | [PostPlanComment](#postplancomment) | MonitoringPlanService | Post a monitoring update to the originating pull request and configured user DMs | | [PostInProgressComment](#postinprogresscomment) | MonitoringPlanService | Post the "working on a monitoring plan" placeholder comment and record it for in-place finalization | | [ActivateFromDeployment](#activatefromdeployment) | MonitoringPlanService | Correlate a successful deployment to merged monitoring plans and activate the ones whose change shipped (deployment-hook driven) | | [ActivatePlanFromDeployments](#activateplanfromdeployments) | MonitoringPlanService | Activate one already-merged plan against its repository's current deployments (the dual of ActivateFromDeployment; plan-creation driven) | | [RecordPullRequestFixLink](#recordpullrequestfixlink) | MonitoringPlanService | Persist a PR-to-issue fix link and reconcile it against existing deployments | | [RecordPullRequestUpdated](#recordpullrequestupdated) | MonitoringPlanService | Extract closing-keyword issue refs from current PR text and persist them as fix links | | [GetMyChangeMonitorSetup](#getmychangemonitorsetup) | ChangeMonitorNotificationsService | Read the current user's Change Monitor notification setup | | [UpdateMyChangeMonitorNotificationPreference](#updatemychangemonitornotificationpreference) | ChangeMonitorNotificationsService | Update the current user's Change Monitor Slack DM toggle | | [VerifyMyChangeMonitorSlackDM](#verifymychangemonitorslackdm) | ChangeMonitorNotificationsService | Send a test Slack DM and store the verified Slack destination | | [GetMonitoringRun](#getmonitoringrun) | MonitoringRunService | Retrieve a monitoring run by name | | [ListMonitoringRuns](#listmonitoringruns) | MonitoringRunService | List monitoring runs for a plan | --- ## GetMonitoringPlan Retrieve a monitoring plan by name. ``` POST /firetiger.monitoring_plans.v1.MonitoringPlanService/GetMonitoringPlan ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the monitoring plan | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.monitoring_plans.v1.MonitoringPlanService/GetMonitoringPlan" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "monitoring-plans/plan-abc123"}' ``` **Response** ```json { "monitoringPlan": { "name": "monitoring-plans/plan-abc123", "origin": { "repository": "acme-corp/backend", "prNumber": 42, "prUrl": "https://github.com/acme-corp/backend/pull/42", "headSha": "a1b2c3d4e5f6", "prTitle": "Fix auth service timeout handling", "prAuthorGithubLogin": "engineer", "prAuthorGithubUserId": "1234567" }, "activation": { "mergeSha": "f6e5d4c3b2a1", "environments": ["production"] }, "planContent": "## Intended Effect\nFixes timeout handling in auth service...", "planSummary": "Fixes auth service timeout handling to prevent cascading failures during peak traffic.", "intendedEffects": [ "Auth service p99 latency stays under 500ms at peak (24h baseline 480ms).", "Cascading 503s attributed to auth timeouts drop to zero." ], "risks": [ "Masked failures: watch auth 5xx rate; alert if > 1% sustained for 5m." ], "notificationChannel": "#deploy-alerts", "authorSession": "agents/plan-author/sessions/sess-xyz", "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T15:00:00Z", "deployments": [ { "environment": "production", "deployment": "deployments/dep-789", "deployTime": "2024-06-15T16:00:00Z", "intendedEffectConfirmed": true, "outcome": "MONITORING_OUTCOME_NO_ISSUE", "completeTime": "2024-06-15T17:30:00Z" } ], "lastCheckTime": "2024-06-15T17:30:00Z" } } ``` --- ## ListMonitoringPlans List monitoring plans with optional filtering and pagination. ``` POST /firetiger.monitoring_plans.v1.MonitoringPlanService/ListMonitoringPlans ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `filter` | string | No | [Filter](index.txt#filtering) expression | | `order_by` | string | No | Field to sort by (e.g. `create_time desc`) | | `page_size` | integer | No | Maximum results per page | | `page_token` | string | No | Token for the next page of results | | `show_deleted` | boolean | No | Include soft-deleted monitoring plans | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.monitoring_plans.v1.MonitoringPlanService/ListMonitoringPlans" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"page_size": 10, "order_by": "create_time desc"}' ``` **Response** ```json { "monitoringPlans": [ { "name": "monitoring-plans/plan-abc123", "origin": { "repository": "acme-corp/backend", "prNumber": 42, "prUrl": "https://github.com/acme-corp/backend/pull/42", "prTitle": "Fix auth service timeout handling", "prAuthorGithubLogin": "engineer", "prAuthorGithubUserId": "1234567" }, "planSummary": "Fixes auth service timeout handling to prevent cascading failures during peak traffic.", "intendedEffects": [ "Auth service p99 latency stays under 500ms at peak (24h baseline 480ms).", "Cascading 503s attributed to auth timeouts drop to zero." ], "risks": [ "Masked failures: watch auth 5xx rate; alert if > 1% sustained for 5m." ], "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T15:00:00Z" } ], "nextPageToken": "" } ``` --- ## PostPlanComment Post a Markdown monitoring update to the pull request associated with a monitoring plan. If the PR author has linked GitHub and Slack and enabled Change Monitor DMs, Firetiger also sends the same update to their Slack DM. GitHub comment posting and Slack delivery are best-effort and independent. ``` POST /v1/monitoring-plans/{plan_id}:postComment ``` **Path parameters** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `plan_id` | string | Yes | ID portion of the monitoring plan resource name | **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `comment_body` | string | Yes | Markdown body to post on the pull request | **Example** ```bash curl -X POST "{{ site.api_url }}/v1/monitoring-plans/plan-abc123:postComment" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "comment_body": "Post-deployment verification completed. No anomalies were detected." }' ``` **Response** ```json { "commentUrl": "https://github.com/acme-corp/backend/pull/42#issuecomment-1234567890" } ``` --- ## PostInProgressComment Post the initial "Firetiger is working on a monitoring plan for this PR" placeholder comment on the pull request associated with a monitoring plan, and record its comment ID on the plan (`origin.pr_in_progress_comment_id`). When the plan is later published, Firetiger edits that same comment in place into "Firetiger has created a monitoring plan for this PR" so the PR carries one comment that transitions state. The comment body and change-monitor link are generated server-side. Best-effort: returns an empty `commentUrl` (and records nothing) when GitHub posting is unavailable. ``` POST /v1/monitoring-plans/{plan_id}:postInProgressComment ``` **Path parameters** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `plan_id` | string | Yes | ID portion of the monitoring plan resource name | **Example** ```bash curl -X POST "{{ site.api_url }}/v1/monitoring-plans/plan-abc123:postInProgressComment" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{}' ``` **Response** ```json { "commentUrl": "https://github.com/acme-corp/backend/pull/42#issuecomment-1234567890" } ``` --- ## ActivateFromDeployment Correlate a successful deployment to the merged monitoring plans whose change shipped in it, and activate those plans (append a per-environment `MonitoredDeployment`). This RPC is driven by Firetiger's deployment hook, not typically called by end users. Activation resolves which plans shipped by comparing the deployment's commit range: `base_sha` is the previous deployment's processed SHA (the per-environment watermark) and `deployed_sha` is the new tip. Plans whose `activation.merge_sha` falls in that range are activated. When `base_sha` is empty (the first deployment of a repository/environment), Firetiger falls back to walking the deployed SHA's commit history. ``` POST /firetiger.monitoring_plans.v1.MonitoringPlanService/ActivateFromDeployment ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `repository` | string | Yes | Repository in `owner/repo` format | | `environment` | string | Yes | Deployment environment (e.g. `production`) | | `deployed_sha` | string | Yes | The newly deployed commit SHA | | `base_sha` | string | No | Previous processed deployment SHA for this `(repository, environment)` — the forward-delta watermark. Empty falls back to a history walk | | `installation_id` | int64 | No | GitHub App installation ID used to authorize the commit comparison | | `github_deployment_id` | int64 | No | GitHub deployment ID, used to look up the corresponding Deployment resource | | `deployment_name` | string | No | Deployment resource name (e.g. `deployments/dep-789`) when the GitHub deployment ID is unavailable | **Response** ```json { "activatedPlans": [ { "name": "monitoring-plans/plan-abc123", "activation": { "mergeSha": "f6e5d4c3b2a1", "environments": ["production"] } } ] } ``` --- ## ActivatePlanFromDeployments Activate a single already-merged monitoring plan against the current deployments of its repository — the dual of [ActivateFromDeployment](#activatefromdeployment). This RPC is driven by plan-creation flows, not typically called by end users. `ActivateFromDeployment` correlates *a deployment* to the plans whose change it shipped. But a plan can be created *after* its PR already merged and deployed (e.g. authoring a monitor for an already-merged PR); that merge is behind every later deployment's commit range, so the deployment-driven path never sees it. This RPC closes that gap: for each environment with a recent successful deployment of the plan's repository, if the plan's `merge_sha` is an ancestor of that environment's latest deployed SHA and the plan isn't already activated there, it appends a `MonitoredDeployment`. Idempotent. ``` POST /firetiger.monitoring_plans.v1.MonitoringPlanService/ActivatePlanFromDeployments ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the monitoring plan to activate (`monitoring-plans/{plan}`) | **Response** ```json { "activatedPlans": [ { "name": "monitoring-plans/plan-abc123", "activation": { "mergeSha": "f6e5d4c3b2a1", "environments": ["production"] } } ] } ``` --- ## RecordPullRequestFixLink Persist that a pull request fixes a specific issue. Firetiger records the PR on the issue, ensures the PR has a monitoring plan, links the issue to that plan, and replays any deployments already recorded on the plan so the issue can move to `ISSUE_WORKFLOW_STATE_VERIFYING_FIX` immediately. ``` POST /v1/monitoring-plans:recordPullRequestFixLink ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `pr_url` | string | Yes | Full GitHub pull request URL | | `issue_name` | string | Yes | Issue resource name, for example `issues/FT-42` | | `installation_id` | int64 | No | GitHub App installation ID to use if Firetiger must create a monitoring plan for the PR | **Example** ```bash curl -X POST "{{ site.api_url }}/v1/monitoring-plans:recordPullRequestFixLink" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "pr_url": "https://github.com/acme-corp/backend/pull/42", "issue_name": "issues/FT-42" }' ``` **Response** ```json { "monitoringPlan": { "name": "monitoring-plans/plan-abc123", "origin": { "repository": "acme-corp/backend", "prNumber": 42, "prUrl": "https://github.com/acme-corp/backend/pull/42" }, "relatedResources": ["issues/FT-42"] }, "linkedIssues": ["issues/FT-42"], "promotedIssues": ["issues/FT-42"] } ``` --- ## RecordPullRequestUpdated Record the current pull request title and body and persist any issue refs introduced by a GitHub-style closing keyword, such as `Fixes FT-42`, `Closes: FT-42`, or `Resolves https://app.example.com/issues/FT-42`. Bare mentions like `Related to FT-42` are ignored. This RPC is typically called from GitHub pull request webhooks. For each extracted issue, Firetiger performs the same reconciliation as [RecordPullRequestFixLink](#recordpullrequestfixlink): it records the PR on the issue, ensures the PR has a monitoring plan, links the issue to the plan, and replays existing deployments for possible `VERIFYING_FIX` promotion. ``` POST /v1/monitoring-plans:recordPullRequestUpdated ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `pr_url` | string | Yes | Full GitHub pull request URL | | `title` | string | No | Current PR title | | `body` | string | No | Current PR body | | `installation_id` | int64 | No | GitHub App installation ID to use if Firetiger must create a monitoring plan for the PR | **Example** ```bash curl -X POST "{{ site.api_url }}/v1/monitoring-plans:recordPullRequestUpdated" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "pr_url": "https://github.com/acme-corp/backend/pull/42", "title": "Fix auth service timeout handling", "body": "Fixes FT-42. Related to FT-7." }' ``` **Response** ```json { "monitoringPlan": { "name": "monitoring-plans/plan-abc123", "origin": { "repository": "acme-corp/backend", "prNumber": 42, "prUrl": "https://github.com/acme-corp/backend/pull/42" }, "relatedResources": ["issues/FT-42"] }, "linkedIssues": ["issues/FT-42"], "promotedIssues": ["issues/FT-42"] } ``` --- ## GetMyChangeMonitorSetup Read the current user's personal Change Monitor notification setup. This includes their GitHub identity link, Slack identity link, installed Slack app connection, and notification preference. ``` GET /v1/users/me/change-monitor-setup ``` **Example** ```bash curl "{{ site.api_url }}/v1/users/me/change-monitor-setup" \ -u "$USERNAME:$PASSWORD" ``` **Response** ```json { "preference": { "name": "change-monitor-notification-preferences/user-123", "enabled": true, "slackDm": { "slackConnection": "connections/slack-test", "slackExternalIdentity": "external-identities/slack-123" } }, "status": { "githubIdentityLinked": true, "slackIdentityLinked": true, "slackDmVerified": true } } ``` --- ## UpdateMyChangeMonitorNotificationPreference Update the current user's Change Monitor notification settings: the Slack DM toggle (`enabled`). When Slack DMs are enabled, every Change Monitor notification is delivered to the verified Slack DM destination. Weekly Impact Report Slack delivery is controlled separately — see [Impact Report Notifications](impact-report-notifications.txt). ``` PATCH /v1/users/me/change-monitor-notification-preference ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `enabled` | boolean | No | Whether Change Monitor Slack DM notifications are enabled | | `update_mask` | string | No | Comma-separated field mask naming the fields to write. The only supported path is `enabled`. Defaults to `enabled` | **Example** (enable Change Monitor Slack DMs) ```bash curl -X PATCH "{{ site.api_url }}/v1/users/me/change-monitor-notification-preference" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "enabled": true, "update_mask": "enabled" }' ``` **Response** ```json { "preference": { "enabled": true, "slackDm": { "slackConnection": "connections/slack-test", "slackExternalIdentity": "external-identities/slack-123" } } } ``` --- ## VerifyMyChangeMonitorSlackDM Send a test DM to the current user's linked Slack account and store that Slack connection as the verified destination for Change Monitor notifications. ``` POST /v1/users/me/change-monitor-notification-preference:verify-slack-dm ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `slack_connection` | string | Yes | Slack app connection resource name to verify | **Example** ```bash curl -X POST "{{ site.api_url }}/v1/users/me/change-monitor-notification-preference:verify-slack-dm" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"slack_connection": "connections/slack-test"}' ``` **Response** ```json { "preference": { "enabled": true, "slackDestination": { "slackConnection": "connections/slack-test", "slackExternalIdentity": "users/user-123/external-identities/slack" } } } ``` --- ## GetMonitoringRun > MonitoringRunService is a legacy service. New monitoring state is stored directly on MonitoringPlan.deployments. The run endpoints are retained for backward compatibility. Retrieve a monitoring run by name. ``` POST /firetiger.monitoring_plans.v1.MonitoringRunService/GetMonitoringRun ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the monitoring run | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.monitoring_plans.v1.MonitoringRunService/GetMonitoringRun" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "monitoring-plans/plan-abc123/runs/run-prod-001"}' ``` **Response** ```json { "monitoringRun": { "name": "monitoring-plans/plan-abc123/runs/run-prod-001", "environment": "production", "deployment": "deployments/dep-789", "status": "MONITORING_RUN_STATUS_COMPLETED", "outcome": "MONITORING_RUN_OUTCOME_NO_ISSUE", "intendedEffectConfirmed": true, "activateTime": "2024-06-15T16:00:00Z", "completeTime": "2024-06-15T17:30:00Z", "lastCheckTime": "2024-06-15T17:30:00Z", "createTime": "2024-06-15T16:00:00Z", "updateTime": "2024-06-15T17:30:00Z" } } ``` --- ## ListMonitoringRuns > MonitoringRunService is a legacy service. New monitoring state is stored directly on MonitoringPlan.deployments. The run endpoints are retained for backward compatibility. List monitoring runs for a plan with optional filtering and pagination. ``` POST /firetiger.monitoring_plans.v1.MonitoringRunService/ListMonitoringRuns ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `parent` | string | Yes | Parent monitoring plan resource name | | `filter` | string | No | [Filter](index.txt#filtering) expression | | `order_by` | string | No | Field to sort by (e.g. `create_time desc`) | | `page_size` | integer | No | Maximum results per page | | `page_token` | string | No | Token for the next page of results | | `show_deleted` | boolean | No | Include soft-deleted monitoring runs | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.monitoring_plans.v1.MonitoringRunService/ListMonitoringRuns" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"parent": "monitoring-plans/plan-abc123", "page_size": 10}' ``` **Response** ```json { "monitoringRuns": [ { "name": "monitoring-plans/plan-abc123/runs/run-prod-001", "environment": "production", "deployment": "deployments/dep-789", "status": "MONITORING_RUN_STATUS_COMPLETED", "outcome": "MONITORING_RUN_OUTCOME_NO_ISSUE", "intendedEffectConfirmed": true, "activateTime": "2024-06-15T16:00:00Z", "completeTime": "2024-06-15T17:30:00Z", "createTime": "2024-06-15T16:00:00Z", "updateTime": "2024-06-15T17:30:00Z" } ], "nextPageToken": "" } ``` ## Coding Agents A **coding agent** is an external service that can read a Firetiger issue and propose a fix (typically as a pull request). Each configured agent shows up as an entry in the issue detail page's `Fix` dropdown. For the end-to-end workflow and the Cursor setup walkthrough, see [Fixing issues with coding agents](../guides/fixing-issues-with-coding-agents.txt) and the [Cursor integration](../integrations/developer-tools/cursor.txt) page. This page is the programmatic reference for callers of the service. **Service**: `firetiger.coding_agents.v1.CodingAgentsService` **Resource name pattern**: `coding-agents/{coding_agent_id}` **Access**: Read + sessions A CodingAgent is a typed view over a [Connection](connections.txt) whose type is in the coding-agent subset. Which specific types are available depends on the deployment — consult [`ListConnectionTypes`](connections.txt#listconnectiontypes) for the set enabled on your stack. The `{coding_agent_id}` segment is the same id as the underlying `connections/{id}` record. Creating or deleting a coding agent goes through the [Connections](connections.txt) API; this service is read-only for agents and exposes session management on top. Sessions are **passthrough** — `GetSession` proxies to the external provider on every call, and `LaunchSession` kicks off a new session there. Sessions are not persisted by Firetiger. ## Methods | Method | Description | |:-------|:------------| | [GetCodingAgent](#getcodingagent) | Retrieve a coding agent by name | | [ListCodingAgents](#listcodingagents) | List configured coding agents | | [LaunchSession](#launchsession) | Launch a new session against an issue | | [GetSession](#getsession) | Read a session's current state (proxied to the provider) | --- ## GetCodingAgent ``` GET /v1/coding-agents/{coding_agent_id} ``` **Path parameters** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `coding_agent_id` | string | Yes | ID portion of the resource name (e.g. `cursor-prod`) | Returns `not_found` when the underlying connection exists but is not a coding-agent type (e.g. it's an HTTP connection) — a connection id can't be promoted to a coding-agent name on its own. --- ## ListCodingAgents ``` GET /v1/codingAgents ``` Lists every configured coding-agent instance visible to the caller. Agent types that are disabled by a deployment-level feature flag are filtered out. **Query parameters** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `page_size` | integer | No | Maximum results per page | | `page_token` | string | No | Token for the next page of results | | `order_by` | string | No | Sort order | | `filter` | string | No | Reserved for future use; non-empty filters are currently rejected with `unimplemented`. | --- ## LaunchSession ``` POST /v1/coding-agents/{coding_agent_id}/sessions:launch ``` Starts a new session on the external provider. The returned `CodingAgentSession` carries the provider's external id and a browser-openable `session_url` that callers (e.g. the UI's Fix dropdown) can redirect to. For backward compatibility with the "View Cursor Agent" chip, the server also appends the session URL to `issue.links` on a best-effort basis. **Path parameters** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `coding_agent_id` | string | Yes | ID of the coding agent that will launch the session | **Body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `issue` | string | Yes | Resource name of the issue to fix (e.g. `issues/FT-42`) | | `actor_email` | string | No | Email address of the human who triggered the launch (e.g. clicked **Fix** in the Firetiger UI). Forwarded to providers that support per-session user attribution: surfaced to Inspect as `on_behalf_of` so PR / commit authorship and audit trails name the actual user instead of the API key owner. The user must already have logged into the provider at least once. Other providers ignore this field. | --- ## GetSession ``` GET /v1/coding-agents/{coding_agent_id}/sessions/{session_id} ``` Proxies to the external provider for fresh state. Status is normalized onto `CodingAgentSessionStatus` (`PENDING`, `RUNNING`, `FINISHED`, `FAILED`). --- ## CodingAgentType The `type` field on a `CodingAgent` (returned by `GetCodingAgent` / `ListCodingAgents`) identifies which external provider backs it. Each value maps 1:1 to a [`ConnectionType`](types/connection.txt#connection-type). Treat this as an open set — tolerate values you don't recognize rather than erroring. | Value | Backing connection | Description | |:------|:-------------------|:------------| | `CODING_AGENT_TYPE_CURSOR` | `CONNECTION_TYPE_CURSOR` | Cursor cloud agents | | `CODING_AGENT_TYPE_INSPECT` | `CONNECTION_TYPE_INSPECT` | Ramp's internal Inspect platform (feature-flagged per deployment) | | `CODING_AGENT_TYPE_TEMBO` | `CONNECTION_TYPE_TEMBO` | Tembo ad-hoc tasks or automations | | `CODING_AGENT_TYPE_REPLICAS` | `CONNECTION_TYPE_REPLICAS` | Replicas sandboxed background agents | | `CODING_AGENT_TYPE_CODER` | `CONNECTION_TYPE_CODER` | Self-hosted Coder Task (workspace running an embedded coding agent) | | `CODING_AGENT_TYPE_DEVIN` | `CONNECTION_TYPE_DEVIN` | Devin (Cognition) cloud coding agent, launched via Devin's v3 organization-scoped API | --- ## Related - [Connections](connections.txt) — underlying integration records (coding-agent types are a subset) - [Issues](issues.txt) — the resources a coding-agent session operates on ## Notes Notes are free-form records for capturing observations, tool outputs, and context. Each note can optionally reference the tool that produced it, along with the arguments used. **Service**: `firetiger.notes.v1.NotesService` **Resource name pattern**: `notes/{note_id}` **Access**: Read-write **Resource type**: [Note](types/note.txt) ## Example flow Create a couple of notes to capture tool outputs, then list them. **1. Create a note from a health check tool** ```bash curl -X POST "{{ site.api_url }}/firetiger.notes.v1.NotesService/CreateNote" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "note_id": "deploy-check-2024-06-15", "note": { "display_name": "Post-deploy health check", "notes": "All endpoints returning 200. P99 latency stable at 120ms.", "description": "Routine check after v2.4 rollout", "tool_name": "health_check", "tool_args": {"environment": "production", "timeout_seconds": 30} } }' ``` ```json { "note": { "name": "notes/deploy-check-2024-06-15", "displayName": "Post-deploy health check", "notes": "All endpoints returning 200. P99 latency stable at 120ms.", "description": "Routine check after v2.4 rollout", "toolName": "health_check", "toolArgs": {"environment": "production", "timeoutSeconds": 30}, "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } } ``` **2. Create a second note from a query tool** ```bash curl -X POST "{{ site.api_url }}/firetiger.notes.v1.NotesService/CreateNote" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "note_id": "error-spike-analysis", "note": { "display_name": "Error spike analysis", "notes": "Spike in 5xx errors between 14:00-14:15 UTC traced to a bad config push. Rolled back at 14:12.", "tool_name": "log_query", "tool_args": {"query": "status >= 500", "time_range": "1h"} } }' ``` ```json { "note": { "name": "notes/error-spike-analysis", "displayName": "Error spike analysis", "notes": "Spike in 5xx errors between 14:00-14:15 UTC traced to a bad config push. Rolled back at 14:12.", "toolName": "log_query", "toolArgs": {"query": "status >= 500", "timeRange": "1h"}, "createTime": "2024-06-15T14:35:00Z", "updateTime": "2024-06-15T14:35:00Z" } } ``` **3. List notes filtered by tool** ```bash curl -X POST "{{ site.api_url }}/firetiger.notes.v1.NotesService/ListNotes" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"filter": "tool_name = \"health_check\"", "page_size": 25}' ``` ```json { "notes": [ { "name": "notes/deploy-check-2024-06-15", "displayName": "Post-deploy health check", "notes": "All endpoints returning 200. P99 latency stable at 120ms.", "description": "Routine check after v2.4 rollout", "toolName": "health_check", "toolArgs": {"environment": "production", "timeoutSeconds": 30}, "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } ], "nextPageToken": "" } ``` ## Methods | Method | Description | |:-------|:------------| | [CreateNote](#createnote) | Create a new note | | [GetNote](#getnote) | Retrieve a note by name | | [UpdateNote](#updatenote) | Update an existing note | | [DeleteNote](#deletenote) | Soft-delete a note | | [ListNotes](#listnotes) | List notes with filtering and pagination | --- ## CreateNote Create a new note. ``` POST /firetiger.notes.v1.NotesService/CreateNote ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `note_id` | string | Yes | ID for the new note (alphanumeric, hyphens, underscores) | | `note` | [Note](types/note.txt) | Yes | The note to create | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.notes.v1.NotesService/CreateNote" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "note_id": "deploy-check-2024-06-15", "note": { "display_name": "Post-deploy health check", "notes": "All endpoints returning 200. P99 latency stable at 120ms.", "description": "Routine check after v2.4 rollout", "tool_name": "health_check", "tool_args": {"environment": "production", "timeout_seconds": 30} } }' ``` **Response** ```json { "note": { "name": "notes/deploy-check-2024-06-15", "displayName": "Post-deploy health check", "notes": "All endpoints returning 200. P99 latency stable at 120ms.", "description": "Routine check after v2.4 rollout", "toolName": "health_check", "toolArgs": {"environment": "production", "timeoutSeconds": 30}, "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } } ``` --- ## GetNote Retrieve a note by name. ``` POST /firetiger.notes.v1.NotesService/GetNote ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the note | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.notes.v1.NotesService/GetNote" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "notes/deploy-check-2024-06-15"}' ``` --- ## UpdateNote Update an existing note. Use `update_mask` to specify which fields to modify. ``` POST /firetiger.notes.v1.NotesService/UpdateNote ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `note` | [Note](types/note.txt) | Yes | The note with `name` set and updated fields | | `update_mask` | string | No | Comma-separated list of fields to update. If omitted, all provided fields are updated. | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.notes.v1.NotesService/UpdateNote" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "note": { "name": "notes/deploy-check-2024-06-15", "notes": "All endpoints returning 200. P99 latency stable at 120ms. Confirmed no error rate increase after 1 hour." }, "update_mask": "notes" }' ``` --- ## DeleteNote Soft-delete a note. The resource will still be accessible via Get but excluded from List results unless `show_deleted` is set. ``` POST /firetiger.notes.v1.NotesService/DeleteNote ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the note to delete | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.notes.v1.NotesService/DeleteNote" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "notes/deploy-check-2024-06-15"}' ``` --- ## ListNotes List notes with optional filtering and pagination. ``` POST /firetiger.notes.v1.NotesService/ListNotes ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `filter` | string | No | [Filter](index.txt#filtering) expression | | `order_by` | string | No | Field to sort by (e.g. `create_time desc`) | | `page_size` | integer | No | Maximum results per page | | `page_token` | string | No | Token for the next page of results | | `show_deleted` | boolean | No | Include soft-deleted notes | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.notes.v1.NotesService/ListNotes" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"filter": "tool_name = \"health_check\"", "page_size": 25}' ``` **Response** ```json { "notes": [ { "name": "notes/deploy-check-2024-06-15", "displayName": "Post-deploy health check", "notes": "All endpoints returning 200. P99 latency stable at 120ms.", "description": "Routine check after v2.4 rollout", "toolName": "health_check", "toolArgs": {"environment": "production", "timeoutSeconds": 30}, "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } ], "nextPageToken": "" } ``` ## Tags Tags let you organize and filter agents. There are two types of tags: - **User tags** — Created and managed by users with custom colors - **System tags** — Created by Firetiger (prefixed with `firetiger:`), always displayed in grey, read-only **Service**: `firetiger.tags.v1.TagsService` **Resource name pattern**: `tags/{tag_id}` **Access**: Read-write (user tags only; system tags are read-only) **Resource type**: [Tag](types/tag.txt) ## Example flow Create a tag, assign it to an agent, then list tags. **1. Create a tag** ```bash curl -X POST "{{ site.api_url }}/firetiger.tags.v1.TagsService/CreateTag" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "tag_id": "production", "tag": { "display_name": "Production", "description": "Agents monitoring production systems", "color": "#5E6AD2" } }' ``` ```json { "tag": { "name": "tags/production", "displayName": "Production", "description": "Agents monitoring production systems", "color": "#5E6AD2", "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } } ``` **2. Assign tag to an agent** ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/UpdateAgent" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "agent": { "name": "agents/my-agent", "tags": ["tags/production"] }, "update_mask": "tags" }' ``` **3. List all tags** ```bash curl -X POST "{{ site.api_url }}/firetiger.tags.v1.TagsService/ListTags" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{}' ``` ```json { "tags": [ { "name": "tags/production", "displayName": "Production", "description": "Agents monitoring production systems", "color": "#5E6AD2", "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } ] } ``` ## Methods | Method | Description | |:-------|:------------| | [CreateTag](#createtag) | Create a new tag | | [GetTag](#gettag) | Retrieve a tag by name | | [UpdateTag](#updatetag) | Update an existing tag | | [DeleteTag](#deletetag) | Soft-delete a tag | | [ListTags](#listtags) | List tags with filtering and pagination | --- ## CreateTag Create a new user tag. Cannot create system tags (those with `firetiger:` prefix). ``` POST /firetiger.tags.v1.TagsService/CreateTag ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `tag_id` | string | Yes | ID for the new tag (alphanumeric, hyphens, underscores, colons) | | `tag` | [Tag](types/tag.txt) | Yes | The tag to create | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.tags.v1.TagsService/CreateTag" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "tag_id": "deploy-monitoring", "tag": { "display_name": "Deployment Monitoring", "color": "#10B981" } }' ``` **Response** ```json { "tag": { "name": "tags/deploy-monitoring", "displayName": "Deployment Monitoring", "color": "#10B981", "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } } ``` --- ## GetTag Retrieve a tag by name. ``` POST /firetiger.tags.v1.TagsService/GetTag ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the tag | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.tags.v1.TagsService/GetTag" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "tags/production"}' ``` --- ## UpdateTag Update an existing tag. Use `update_mask` to specify which fields to modify. Cannot update system tags. ``` POST /firetiger.tags.v1.TagsService/UpdateTag ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `tag` | [Tag](types/tag.txt) | Yes | The tag with `name` set and updated fields | | `update_mask` | string | No | Comma-separated list of fields to update. If omitted, all provided fields are updated. | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.tags.v1.TagsService/UpdateTag" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "tag": { "name": "tags/production", "color": "#EF4444" }, "update_mask": "color" }' ``` --- ## DeleteTag Soft-delete a tag. The resource will still be accessible via Get but excluded from List results unless `show_deleted` is set. Cannot delete system tags. ``` POST /firetiger.tags.v1.TagsService/DeleteTag ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the tag to delete | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.tags.v1.TagsService/DeleteTag" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "tags/production"}' ``` --- ## ListTags List tags with optional filtering and pagination. ``` POST /firetiger.tags.v1.TagsService/ListTags ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `filter` | string | No | [Filter](index.txt#filtering) expression (e.g., `system = true`) | | `order_by` | string | No | Field to sort by (e.g., `create_time desc`) | | `page_size` | integer | No | Maximum results per page | | `page_token` | string | No | Token for the next page of results | | `show_deleted` | boolean | No | Include soft-deleted tags | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.tags.v1.TagsService/ListTags" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"filter": "system = false", "page_size": 25}' ``` **Response** ```json { "tags": [ { "name": "tags/production", "displayName": "Production", "color": "#5E6AD2", "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } ], "nextPageToken": "" } ``` ## Runbooks Runbooks are structured operating procedures that Firetiger agents follow when investigating or resolving issues. Each runbook contains instructional text and a set of connections with specific tools that the agent is allowed to use while executing the runbook. **Service**: `firetiger.runbooks.v1.RunbooksService` **Resource name pattern**: `runbooks/{runbook_id}` **Access**: Read-write **Resource type**: [Runbook](types/runbook.txt) ## Example flow Create a runbook that pairs connections with specific tools, then list all runbooks. **1. Create a runbook with connections and tools** ```bash curl -X POST "{{ site.api_url }}/firetiger.runbooks.v1.RunbooksService/CreateRunbook" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "runbook_id": "high-error-rate-triage", "runbook": { "display_name": "High Error Rate Triage", "description": "Steps for triaging elevated error rates on production services", "text": "1. Query the database for recent deployments.\n2. Check HTTP error rates via Prometheus.\n3. If a recent deploy correlates, escalate to the owning team.", "connections": [ { "name": "connections/prod-postgres", "enabled_tools": ["TOOL_POSTGRES_QUERY"] }, { "name": "connections/prod-prometheus", "enabled_tools": ["TOOL_PROMQL_QUERY", "TOOL_PROMQL_QUERY_RANGE"] } ] } }' ``` ```json { "runbook": { "name": "runbooks/high-error-rate-triage", "displayName": "High Error Rate Triage", "description": "Steps for triaging elevated error rates on production services", "text": "1. Query the database for recent deployments.\n2. Check HTTP error rates via Prometheus.\n3. If a recent deploy correlates, escalate to the owning team.", "connections": [ { "name": "connections/prod-postgres", "enabledTools": ["TOOL_POSTGRES_QUERY"] }, { "name": "connections/prod-prometheus", "enabledTools": ["TOOL_PROMQL_QUERY", "TOOL_PROMQL_QUERY_RANGE"] } ], "createTime": "2024-08-10T09:00:00Z", "updateTime": "2024-08-10T09:00:00Z" } } ``` **2. Create a second runbook** ```bash curl -X POST "{{ site.api_url }}/firetiger.runbooks.v1.RunbooksService/CreateRunbook" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "runbook_id": "deployment-rollback", "runbook": { "display_name": "Deployment Rollback", "description": "Procedure for rolling back a bad deployment", "text": "1. Identify the failing deployment via CI/CD.\n2. Trigger rollback to the last known-good version.\n3. Verify error rates return to baseline." } }' ``` ```json { "runbook": { "name": "runbooks/deployment-rollback", "displayName": "Deployment Rollback", "description": "Procedure for rolling back a bad deployment", "text": "1. Identify the failing deployment via CI/CD.\n2. Trigger rollback to the last known-good version.\n3. Verify error rates return to baseline.", "createTime": "2024-08-10T09:01:00Z", "updateTime": "2024-08-10T09:01:00Z" } } ``` **3. List all runbooks** ```bash curl -X POST "{{ site.api_url }}/firetiger.runbooks.v1.RunbooksService/ListRunbooks" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{}' ``` ```json { "runbooks": [ { "name": "runbooks/high-error-rate-triage", "displayName": "High Error Rate Triage", "description": "Steps for triaging elevated error rates on production services", "createTime": "2024-08-10T09:00:00Z", "updateTime": "2024-08-10T09:00:00Z" }, { "name": "runbooks/deployment-rollback", "displayName": "Deployment Rollback", "description": "Procedure for rolling back a bad deployment", "createTime": "2024-08-10T09:01:00Z", "updateTime": "2024-08-10T09:01:00Z" } ], "nextPageToken": "" } ``` ## Methods | Method | Description | |:-------|:------------| | [CreateRunbook](#createrunbook) | Create a new runbook | | [GetRunbook](#getrunbook) | Retrieve a runbook by name | | [UpdateRunbook](#updaterunbook) | Update an existing runbook | | [DeleteRunbook](#deleterunbook) | Soft-delete a runbook | | [ListRunbooks](#listrunbooks) | List runbooks with filtering and pagination | --- ## CreateRunbook Create a new runbook. ``` POST /firetiger.runbooks.v1.RunbooksService/CreateRunbook ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `runbook_id` | string | Yes | ID for the new runbook (alphanumeric, hyphens, underscores) | | `runbook` | [Runbook](types/runbook.txt) | Yes | The runbook to create | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.runbooks.v1.RunbooksService/CreateRunbook" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "runbook_id": "high-error-rate-triage", "runbook": { "display_name": "High Error Rate Triage", "description": "Steps for triaging elevated error rates on production services", "text": "1. Query the database for recent deployments.\n2. Check HTTP error rates via Prometheus.\n3. If a recent deploy correlates, escalate to the owning team.", "connections": [ { "name": "connections/prod-postgres", "enabled_tools": ["TOOL_POSTGRES_QUERY"] }, { "name": "connections/prod-prometheus", "enabled_tools": ["TOOL_PROMQL_QUERY", "TOOL_PROMQL_QUERY_RANGE"] } ] } }' ``` **Response** ```json { "runbook": { "name": "runbooks/high-error-rate-triage", "displayName": "High Error Rate Triage", "description": "Steps for triaging elevated error rates on production services", "text": "1. Query the database for recent deployments.\n2. Check HTTP error rates via Prometheus.\n3. If a recent deploy correlates, escalate to the owning team.", "connections": [ { "name": "connections/prod-postgres", "enabledTools": ["TOOL_POSTGRES_QUERY"] }, { "name": "connections/prod-prometheus", "enabledTools": ["TOOL_PROMQL_QUERY", "TOOL_PROMQL_QUERY_RANGE"] } ], "createTime": "2024-08-10T09:00:00Z", "updateTime": "2024-08-10T09:00:00Z" } } ``` --- ## GetRunbook Retrieve a runbook by name. ``` POST /firetiger.runbooks.v1.RunbooksService/GetRunbook ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the runbook | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.runbooks.v1.RunbooksService/GetRunbook" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "runbooks/high-error-rate-triage"}' ``` --- ## UpdateRunbook Update an existing runbook. Use `update_mask` to specify which fields to modify. ``` POST /firetiger.runbooks.v1.RunbooksService/UpdateRunbook ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `runbook` | [Runbook](types/runbook.txt) | Yes | The runbook with `name` set and updated fields | | `update_mask` | string | No | Comma-separated list of fields to update. If omitted, all provided fields are updated. | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.runbooks.v1.RunbooksService/UpdateRunbook" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "runbook": { "name": "runbooks/high-error-rate-triage", "description": "Updated triage steps for elevated error rates, now includes log correlation" }, "update_mask": "description" }' ``` --- ## DeleteRunbook Soft-delete a runbook. The resource will still be accessible via Get but excluded from List results unless `show_deleted` is set. ``` POST /firetiger.runbooks.v1.RunbooksService/DeleteRunbook ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the runbook to delete | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.runbooks.v1.RunbooksService/DeleteRunbook" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "runbooks/high-error-rate-triage"}' ``` --- ## ListRunbooks List runbooks with optional filtering and pagination. ``` POST /firetiger.runbooks.v1.RunbooksService/ListRunbooks ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `filter` | string | No | [Filter](index.txt#filtering) expression | | `order_by` | string | No | Field to sort by (e.g. `create_time desc`) | | `page_size` | integer | No | Maximum results per page | | `page_token` | string | No | Token for the next page of results | | `show_deleted` | boolean | No | Include soft-deleted runbooks | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.runbooks.v1.RunbooksService/ListRunbooks" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"page_size": 25}' ``` **Response** ```json { "runbooks": [ { "name": "runbooks/high-error-rate-triage", "displayName": "High Error Rate Triage", "description": "Steps for triaging elevated error rates on production services", "createTime": "2024-08-10T09:00:00Z", "updateTime": "2024-08-10T09:00:00Z" } ], "nextPageToken": "" } ``` ## Triggers Triggers define how and when agent sessions are created. A trigger is bound to a specific agent and can fire on a cron schedule, after a deployment, when a matching row is ingested, when a Slack message is posted, when the agent is @mentioned in Slack, or only when manually invoked. Disabled cron triggers are skipped by the scheduler, but manual triggers can always be invoked regardless of the `enabled` flag. **Service**: `firetiger.triggers.v1.TriggersService` **Resource name pattern**: `triggers/{trigger_id}` **Access**: Read-write **Resource type**: [Trigger](types/trigger.txt) ## Example flow Create a manual trigger linked to an agent, then invoke it to kick off a session. **1. Create a manual trigger** ```bash curl -X POST "{{ site.api_url }}/firetiger.triggers.v1.TriggersService/CreateTrigger" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "trigger_id": "incident-runbook", "trigger": { "display_name": "Incident Runbook", "description": "Manually invoked to run an incident response playbook", "agent": "agents/incident-responder", "configuration": { "manual": {} }, "enabled": true } }' ``` ```json { "trigger": { "name": "triggers/incident-runbook", "displayName": "Incident Runbook", "description": "Manually invoked to run an incident response playbook", "agent": "agents/incident-responder", "configuration": { "manual": {} }, "enabled": true, "createTime": "2024-08-01T12:00:00Z", "updateTime": "2024-08-01T12:00:00Z" } } ``` **2. Invoke the trigger** Pass a `message` that becomes the initial user activity in the new agent session. The response includes the trigger and the name of the newly created session. ```bash curl -X POST "{{ site.api_url }}/firetiger.triggers.v1.TriggersService/InvokeTrigger" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "name": "triggers/incident-runbook", "message": "There is an ongoing incident affecting checkout. Run the incident response playbook." }' ``` ```json { "trigger": { "name": "triggers/incident-runbook", "displayName": "Incident Runbook", "description": "Manually invoked to run an incident response playbook", "agent": "agents/incident-responder", "configuration": { "manual": {} }, "enabled": true, "createTime": "2024-08-01T12:00:00Z", "updateTime": "2024-08-01T12:00:00Z" }, "session": "agents/incident-responder/sessions/ses-abc123" } ``` **3. Check the session** Use the session name from the InvokeTrigger response to read messages via the [Agent Service](agents.txt#read). ```bash curl -X POST "{{ site.api_url }}/firetiger.nxagent.v2.AgentService/Read" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"session": "agents/incident-responder/sessions/ses-abc123", "session_offset": 0}' ``` ## Methods | Method | Description | |:-------|:------------| | [CreateTrigger](#createtrigger) | Create a new trigger | | [GetTrigger](#gettrigger) | Retrieve a trigger by name | | [UpdateTrigger](#updatetrigger) | Update an existing trigger | | [DeleteTrigger](#deletetrigger) | Soft-delete a trigger | | [ListTriggers](#listtriggers) | List triggers with filtering and pagination | | [InvokeTrigger](#invoketrigger) | Manually invoke a trigger to create an agent session | --- ## CreateTrigger Create a new trigger. ``` POST /firetiger.triggers.v1.TriggersService/CreateTrigger ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `trigger_id` | string | No | ID for the new trigger (alphanumeric, hyphens, underscores). Auto-generated if omitted. | | `trigger` | [Trigger](types/trigger.txt) | Yes | The trigger to create | **Example -- cron trigger** ```bash curl -X POST "{{ site.api_url }}/firetiger.triggers.v1.TriggersService/CreateTrigger" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "trigger_id": "daily-review", "trigger": { "display_name": "Daily Review", "description": "Runs a scheduled review every morning at 9 AM Eastern", "agent": "agents/reviewer", "configuration": { "cron": { "schedule": "0 9 * * *", "timezone": "America/New_York" } }, "enabled": true } }' ``` **Example -- manual trigger** ```bash curl -X POST "{{ site.api_url }}/firetiger.triggers.v1.TriggersService/CreateTrigger" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "trigger_id": "incident-runbook", "trigger": { "display_name": "Incident Runbook", "description": "Manually invoked to run an incident response playbook", "agent": "agents/incident-responder", "configuration": { "manual": {} }, "enabled": true } }' ``` **Example -- data trigger** ```bash curl -X POST "{{ site.api_url }}/firetiger.triggers.v1.TriggersService/CreateTrigger" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "trigger_id": "billing-error-watcher", "trigger": { "display_name": "Billing Error Watcher", "description": "Fires when an error log matches billing-related keywords", "agent": "agents/billing-responder", "configuration": { "row": { "table_name": "opentelemetry/logs/api-server", "predicate": "severity = '\''ERROR'\'' AND body LIKE '\''%billing%'\''", "cooldown": "900s" } }, "enabled": true } }' ``` **Example -- post-deploy trigger** ```bash curl -X POST "{{ site.api_url }}/firetiger.triggers.v1.TriggersService/CreateTrigger" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "trigger_id": "post-deploy-smoke-test", "trigger": { "display_name": "Post-Deploy Smoke Test", "description": "Runs smoke tests 5 minutes after the release SHA lands in production", "agent": "agents/smoke-tester", "configuration": { "post_deploy": { "repository": "acme/backend", "environment": "production", "sha": "abc123def456", "delay": "300s" } }, "enabled": true } }' ``` **Example -- Slack message posted trigger** Fires when any message is posted to one of the listed channels. `channels` must be channels the Firetiger bot is a member of (it only receives events from channels it has been invited to). `include_thread_replies` defaults to `true` when omitted. ```bash curl -X POST "{{ site.api_url }}/firetiger.triggers.v1.TriggersService/CreateTrigger" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "trigger_id": "on-call-listener", "trigger": { "display_name": "On-Call Channel Listener", "description": "Investigates every message posted in #on-call", "agent": "agents/on-call", "configuration": { "slack_message_posted": { "slack_connection": "connections/slack-workspace", "channels": ["#on-call"], "include_thread_replies": true } }, "enabled": true } }' ``` **Example -- Slack agent mentioned trigger** Fires when a [SlackHandle](slack-handles.txt) is `@`-mentioned. The handle itself (including the backing Slack user group) is a separate resource managed by [SlackHandlesService](slack-handles.txt); the trigger references it via `slack_handle` and defines the agent + channel scope that responds to mentions. ```bash curl -X POST "{{ site.api_url }}/firetiger.triggers.v1.TriggersService/CreateTrigger" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "trigger_id": "on-call-mention", "trigger": { "display_name": "On-Call Mention", "description": "Triggers the on-call agent when @on-call-bot is @mentioned", "agent": "agents/on-call", "configuration": { "slack_agent_mentioned": { "slack_handle": "connections/slack-workspace/slack-handles/on-call-bot", "channels": ["#on-call"] } }, "enabled": true } }' ``` Note: a message that mentions both `@firetiger` and a custom Slack handle in one message fires only the custom handle's trigger — the default `@firetiger` investigation response is suppressed to avoid duplicate replies. **Response** ```json { "trigger": { "name": "triggers/daily-review", "displayName": "Daily Review", "description": "Runs a scheduled review every morning at 9 AM Eastern", "agent": "agents/reviewer", "configuration": { "cron": { "schedule": "0 9 * * *", "timezone": "America/New_York" } }, "enabled": true, "createTime": "2024-08-01T12:00:00Z", "updateTime": "2024-08-01T12:00:00Z" } } ``` --- ## GetTrigger Retrieve a trigger by name. ``` POST /firetiger.triggers.v1.TriggersService/GetTrigger ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the trigger | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.triggers.v1.TriggersService/GetTrigger" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "triggers/daily-review"}' ``` --- ## UpdateTrigger Update an existing trigger. Use `update_mask` to specify which fields to modify. ``` POST /firetiger.triggers.v1.TriggersService/UpdateTrigger ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `trigger` | [Trigger](types/trigger.txt) | Yes | The trigger with `name` set and updated fields | | `update_mask` | string | No | Comma-separated list of fields to update. If omitted, all provided fields are updated. | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.triggers.v1.TriggersService/UpdateTrigger" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "trigger": { "name": "triggers/daily-review", "enabled": false }, "update_mask": "enabled" }' ``` --- ## DeleteTrigger Soft-delete a trigger. The resource will still be accessible via Get but excluded from List results unless `show_deleted` is set. ``` POST /firetiger.triggers.v1.TriggersService/DeleteTrigger ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the trigger to delete | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.triggers.v1.TriggersService/DeleteTrigger" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "triggers/daily-review"}' ``` --- ## ListTriggers List triggers with optional filtering and pagination. ``` POST /firetiger.triggers.v1.TriggersService/ListTriggers ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `filter` | string | No | [Filter](index.txt#filtering) expression (e.g. `enabled = true`, `agent = "agents/my-agent"`, `associated_resources:"services/api-gateway"` for triggers scoped to a Service) | | `order_by` | string | No | Field to sort by (e.g. `display_name`, `create_time desc`) | | `page_size` | integer | No | Maximum results per page | | `page_token` | string | No | Token for the next page of results | | `show_deleted` | boolean | No | Include soft-deleted triggers | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.triggers.v1.TriggersService/ListTriggers" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"filter": "enabled = true", "page_size": 25}' ``` **Response** ```json { "triggers": [ { "name": "triggers/daily-review", "displayName": "Daily Review", "agent": "agents/reviewer", "configuration": { "cron": { "schedule": "0 9 * * *", "timezone": "America/New_York" } }, "enabled": true, "createTime": "2024-08-01T12:00:00Z", "updateTime": "2024-08-01T12:00:00Z" } ], "nextPageToken": "" } ``` --- ## InvokeTrigger Manually invoke a trigger, immediately creating a new agent session. This works for any trigger type -- cron, manual, post-deploy, row, or Slack -- and works regardless of the `enabled` flag. The `invoke_payload` oneof determines how the agent session is seeded: * `message` (string, tag 2) — plain text; becomes the initial system activity. Used by cron, manual, row-trigger, and webhook invocations. * `slack` ([SlackInvokeContext](#slackinvokecontext), tag 3) — populated only by the server-internal Slack event dispatcher after validating the event against the trigger's configured channel scope. The server synthesizes a [SlackMentionActivity](types/activity.txt#slack-mention-activity) from the context (with a server-resolved `channel_id`) and uses it as the seed. External callers should not populate this variant directly — scope validation rejects out-of-scope channels and pairs of `channel_name` / `channel_id` are only trustable when the server resolves the ID itself. ``` POST /firetiger.triggers.v1.TriggersService/InvokeTrigger ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the trigger to invoke | | `message` | string | One-of | Plain text seed for the session. Use for cron / manual / row / webhook invocations. | | `slack` | [SlackInvokeContext](#slackinvokecontext) | One-of | Slack event context. Populated only by the server-internal Slack dispatcher. | | `idempotency_key` | string | No | Short-lived dedupe key stamped onto the seed activity. When the Slack dispatcher retries the same event, `InvokeTrigger` returns the prior session instead of creating a duplicate. Lookup scans recent sessions scoped to the trigger's agent within a short window (currently 15 minutes) and compares the seed activity's `idempotency_key`; no separate dedupe table is used. | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.triggers.v1.TriggersService/InvokeTrigger" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "name": "triggers/incident-runbook", "message": "There is an ongoing incident affecting checkout. Run the incident response playbook." }' ``` **Response** ```json { "trigger": { "name": "triggers/incident-runbook", "displayName": "Incident Runbook", "description": "Manually invoked to run an incident response playbook", "agent": "agents/incident-responder", "configuration": { "manual": {} }, "enabled": true, "createTime": "2024-08-01T12:00:00Z", "updateTime": "2024-08-05T09:15:00Z" }, "session": "agents/incident-responder/sessions/ses-abc123" } ``` --- ## SlackInvokeContext Server-internal payload for the `slack` arm of `InvokeTriggerRequest.invoke_payload`. Populated by the Slack event dispatcher after a verified Slack webhook (`message.channels` or `app_mention`) matches an enabled `SlackMessagePostedTriggerConfig` or `SlackAgentMentionedTriggerConfig`. External callers should not populate this variant — scope validation is written assuming the dispatcher is the producer. | Field | Type | Description | |:------|:-----|:------------| | `slack_connection` | string | The Slack connection under which the event arrived (`connections/{id}`). Must match the trigger's configured `slack_connection`. | | `team_id` | string | Slack workspace team id from the event envelope. | | `enterprise_id` | string | Slack enterprise id, when the install is on Enterprise Grid. | | `channel_name` | string | Channel name with or without a leading `#`. Required. Normalized to `#channel` before comparison; validated against the trigger's configured channels. | | `thread_ts` | string | Thread timestamp for in-thread events. Empty for top-level events — the server defaults `thread_ts` to `event_ts` in that case so the agent's first reply opens a new thread rather than posting at channel root. | | `event_ts` | string | Slack event timestamp. Also used as the message ts when stamping the seed activity. | | `user_id` | string | Slack user id of the event originator. | | `text` | string | Message text with bot and user-group mentions stripped. | | `permalink` | string | Optional Slack permalink to the originating message. | `channel_id` is intentionally **not** a caller-supplied field. The server resolves the channel id from the validated `channel_name` via the bot's channel membership (`users.conversations`) against the connection's bot token, and stamps the resolved id onto the seed `SlackMentionActivity`. Accepting an untrusted `channel_id` would let a caller pair a valid name with an out-of-scope id and drive agent replies to the wrong channel. ### Scope enforcement `InvokeTrigger` rejects Slack payloads in the following cases: | Condition | Code | |:----------|:-----| | The trigger is not a `SlackMessagePosted` or `SlackAgentMentioned` variant | `INVALID_ARGUMENT` | | `slack_connection` is set and does not match the trigger's configured connection | `PERMISSION_DENIED` | | `channel_name` is missing | `INVALID_ARGUMENT` | | `channel_name` is not in the trigger's configured `channels` list (when non-empty) | `PERMISSION_DENIED` | | The resolved channel cannot be found in the bot's membership (`users.conversations`) | `NOT_FOUND` | ## Agent SLOs Agent SLOs let you define measurable health targets for your monitoring agents and track scored evaluations over time. Each SLO has an optional numeric target and direction (at-or-above or at-or-below), and agents record a score at the end of every monitoring session. Firetiger computes a `healthy` field from the most recent score compared to the target. **Service**: `firetiger.agentslos.v1.AgentSlosService` **Resource name patterns**: `agents/{agent_id}/slos/{slo_id}` and `agents/{agent_id}/slos/{slo_id}/records/{record_id}` **Access**: Read-write ## Example flow Define an SLO on an agent, record an evaluation, then check the agent's health status. **1. Create an SLO** ```bash curl -X POST "{{ site.api_url }}/firetiger.agentslos.v1.AgentSlosService/CreateAgentSlo" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "parent": "agents/cache-monitor", "agent_slo_id": "cache-hit-ratio", "agent_slo": { "display_name": "Cache Hit Ratio", "unit": "%", "target_value": 95.0, "target_direction": "SLO_TARGET_DIRECTION_AT_OR_ABOVE" } }' ``` ```json { "agent_slo": { "name": "agents/cache-monitor/slos/cache-hit-ratio", "display_name": "Cache Hit Ratio", "unit": "%", "target_value": 95.0, "target_direction": "SLO_TARGET_DIRECTION_AT_OR_ABOVE" } } ``` **2. Record an evaluation** Agents call this at the end of each monitoring session. ```bash curl -X POST "{{ site.api_url }}/firetiger.agentslos.v1.AgentSlosService/RecordSloEvaluation" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "slo_name": "agents/cache-monitor/slos/cache-hit-ratio", "score": 97.3, "notes": "Cache hit ratio within normal range after morning traffic spike." }' ``` ```json { "agent_slo": { "name": "agents/cache-monitor/slos/cache-hit-ratio", "display_name": "Cache Hit Ratio", "unit": "%", "target_value": 95.0, "target_direction": "SLO_TARGET_DIRECTION_AT_OR_ABOVE" }, "agent_slo_record": { "name": "agents/cache-monitor/slos/cache-hit-ratio/records/rec-abc123", "score": 97.3, "notes": "Cache hit ratio within normal range after morning traffic spike." } } ``` **3. List SLOs with health status** After at least one evaluation, `healthy` is computed server-side from the most recent score. ```bash curl -X POST "{{ site.api_url }}/firetiger.agentslos.v1.AgentSlosService/ListAgentSlos" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"parent": "agents/cache-monitor"}' ``` ```json { "agent_slos": [ { "name": "agents/cache-monitor/slos/cache-hit-ratio", "display_name": "Cache Hit Ratio", "unit": "%", "target_value": 95.0, "target_direction": "SLO_TARGET_DIRECTION_AT_OR_ABOVE", "healthy": true, "recent_records": [ {"name": "agents/cache-monitor/slos/cache-hit-ratio/records/rec-abc123", "score": 97.3} ] } ] } ``` ## Methods | Method | Description | |:-------|:------------| | [CreateAgentSlo](#createagentslo) | Define a new SLO for an agent | | [GetAgentSlo](#getagentslo) | Retrieve an SLO by name | | [UpdateAgentSlo](#updateagentslo) | Update an existing SLO | | [DeleteAgentSlo](#deleteagentslo) | Delete an SLO and all its records | | [ListAgentSlos](#listagentslos) | List SLOs for an agent with recent records and health | | [ListAgentSloRecords](#listagentslorecords) | List evaluation records for an SLO | | [RecordSloEvaluation](#recordsloevaluation) | Record a scored evaluation for an SLO | --- ## CreateAgentSlo Define a new SLO for an agent. ``` POST /firetiger.agentslos.v1.AgentSlosService/CreateAgentSlo ``` REST alternative: ``` POST /v1/agents/{agent}/slos ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `parent` | string | Yes | Agent resource name (e.g. `agents/my-agent`) | | `agent_slo_id` | string | No | ID for the SLO. Auto-generated if omitted | | `agent_slo.display_name` | string | Yes | Human-readable name (e.g. `"Cache Hit Ratio"`) | | `agent_slo.description` | string | No | Longer description of what this SLO measures | | `agent_slo.unit` | string | No | Unit suffix for display (e.g. `"%"`, `"ms"`) | | `agent_slo.target_value` | double | Yes | Numeric threshold for health computation | | `agent_slo.target_direction` | string | No | `SLO_TARGET_DIRECTION_AT_OR_ABOVE` (default) or `SLO_TARGET_DIRECTION_AT_OR_BELOW` | ```bash curl -X POST "{{ site.api_url }}/firetiger.agentslos.v1.AgentSlosService/CreateAgentSlo" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "parent": "agents/my-agent", "agent_slo_id": "error-rate", "agent_slo": { "display_name": "Error Rate", "unit": "%", "target_value": 1.0, "target_direction": "SLO_TARGET_DIRECTION_AT_OR_BELOW" } }' ``` --- ## GetAgentSlo Retrieve an SLO by name, including recent records and health status. ``` POST /firetiger.agentslos.v1.AgentSlosService/GetAgentSlo ``` REST alternative: ``` GET /v1/agents/{agent}/slos/{slo} ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | SLO resource name (e.g. `agents/my-agent/slos/error-rate`) | ```bash curl -X POST "{{ site.api_url }}/firetiger.agentslos.v1.AgentSlosService/GetAgentSlo" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "agents/my-agent/slos/error-rate"}' ``` --- ## UpdateAgentSlo Update an existing SLO. Only fields specified in `update_mask` are changed. ``` POST /firetiger.agentslos.v1.AgentSlosService/UpdateAgentSlo ``` REST alternative: ``` PATCH /v1/agents/{agent}/slos/{slo} ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `agent_slo.name` | string | Yes | SLO resource name | | `agent_slo.*` | — | No | Fields to update | | `update_mask` | string | No | Comma-separated field paths. All mutable fields updated if omitted | ```bash curl -X POST "{{ site.api_url }}/firetiger.agentslos.v1.AgentSlosService/UpdateAgentSlo" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "agent_slo": { "name": "agents/my-agent/slos/error-rate", "target_value": 0.5 }, "update_mask": "target_value" }' ``` --- ## DeleteAgentSlo Delete an SLO and all its evaluation records. ``` POST /firetiger.agentslos.v1.AgentSlosService/DeleteAgentSlo ``` REST alternative: ``` DELETE /v1/agents/{agent}/slos/{slo} ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | SLO resource name | ```bash curl -X POST "{{ site.api_url }}/firetiger.agentslos.v1.AgentSlosService/DeleteAgentSlo" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "agents/my-agent/slos/error-rate"}' ``` --- ## ListAgentSlos List all SLOs for an agent. Each SLO includes up to 24 recent evaluation records and a computed `healthy` field (omitted when no target is configured). ``` POST /firetiger.agentslos.v1.AgentSlosService/ListAgentSlos ``` REST alternative: ``` GET /v1/agents/{agent}/slos ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `parent` | string | Yes | Agent resource name (e.g. `agents/my-agent`) | | `page_size` | int | No | Maximum number of SLOs to return | | `page_token` | string | No | Pagination token from a previous response | ```bash curl -X POST "{{ site.api_url }}/firetiger.agentslos.v1.AgentSlosService/ListAgentSlos" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"parent": "agents/my-agent"}' ``` --- ## ListAgentSloRecords List evaluation records for an SLO in chronological order. ``` POST /firetiger.agentslos.v1.AgentSlosService/ListAgentSloRecords ``` REST alternative: ``` GET /v1/agents/{agent}/slos/{slo}/records ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `parent` | string | Yes | SLO resource name (e.g. `agents/my-agent/slos/error-rate`) | | `page_size` | int | No | Maximum number of records to return | | `page_token` | string | No | Pagination token from a previous response | | `order_by` | string | No | Sort order. Default: `create_time asc` | ```bash curl -X POST "{{ site.api_url }}/firetiger.agentslos.v1.AgentSlosService/ListAgentSloRecords" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"parent": "agents/my-agent/slos/error-rate", "page_size": 50}' ``` --- ## RecordSloEvaluation Record a scored evaluation for an SLO. Agents call this once per SLO at the end of each monitoring session. Returns the updated SLO and the newly created record. ``` POST /firetiger.agentslos.v1.AgentSlosService/RecordSloEvaluation ``` REST alternative: ``` POST /v1/agents/{agent}/slos/{slo}:record ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `slo_name` | string | Yes | SLO resource name (e.g. `agents/my-agent/slos/error-rate`) | | `score` | double | Yes | The measured value. Must be a finite number (NaN and Inf are rejected) | | `notes` | string | No | Explanation of the score and any notable observations | | `session_name` | string | No | Session that produced this evaluation (e.g. `agents/my-agent/sessions/ses-abc`) | ```bash curl -X POST "{{ site.api_url }}/firetiger.agentslos.v1.AgentSlosService/RecordSloEvaluation" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "slo_name": "agents/my-agent/slos/error-rate", "score": 0.3, "notes": "Error rate dropped after the hotfix deployment at 14:20 UTC." }' ``` ## Auth The Auth API returns the identity Firetiger attached to the current request. It is primarily useful for SDK, integration, and local workload-auth smoke tests. **Service**: `firetiger.auth.v1.AuthService` **Access**: Authenticated ## Methods | Method | Description | |:-------|:------------| | [GetAuth](#getauth) | Return the current request identity and sanitized auth evidence | | [MintAgentSessionToken](#mintagentsessiontoken) | Mint a short-lived agent session token from a workload proof | --- ## GetAuth Return the actor, effective subject, organization, optional session, and sanitized evidence for the current request. ``` GET /v1/auth ``` **Example** ```bash curl "{{ site.api_url }}/v1/auth" \ -u "$USERNAME:$PASSWORD" ``` **Response** ```json { "identity": { "actor": "users/user_123", "subject": "users/user_123", "organization": "org_123", "session": "" }, "evidence": { "authenticator": "basic", "provider": "", "externalIdentity": "" } } ``` **Response fields** | Field | Type | Description | |:------|:-----|:------------| | `identity.actor` | string | Authenticated credential source, such as `users/{id}` or `service-accounts/{id}` | | `identity.subject` | string | Effective Firetiger entity the request acts as, such as `users/{id}` or `agents/{id}` | | `identity.organization` | string | Organization ID used to scope the request | | `identity.session` | string | Optional agent session resource attached to the request | | `evidence.authenticator` | string | Authenticator that accepted the request | | `evidence.provider` | string | Workload provider, when applicable | | `evidence.externalIdentity` | string | Sanitized external identity, when applicable | --- ## MintAgentSessionToken Mint a short-lived, signed **agent session token** for an agent session. An agent authenticates this call with its raw workload proof (`Authorization: Firetiger-Workload `), which proves only the service identity — it carries no organization. The server **derives the organization authoritatively from the named session** (the agent never asserts its own org) and returns a signed token the agent then presents as `Authorization: Firetiger-Session ` on every other request. This endpoint is the only place a raw workload proof is accepted; all data routes require the minted session token. ``` POST /v1/auth/session-token ``` **Example** ```bash curl "{{ site.api_url }}/v1/auth/session-token" \ -H "Authorization: Firetiger-Workload $PROOF" \ -H "Content-Type: application/json" \ -d '{"session": "agents/change-monitor/sessions/01J..."}' ``` **Request fields** | Field | Type | Description | |:------|:-----|:------------| | `session` | string | Agent session resource (`agents/{agent}/sessions/{id}`) to mint a token for. The caller's effective subject and organization are derived from this session. | **Response** ```json { "token": "", "expiresAt": "2026-05-30T12:30:00Z" } ``` **Response fields** | Field | Type | Description | |:------|:-----|:------------| | `token` | string | The minted `Firetiger-Session` token to present on subsequent requests | | `expiresAt` | string (RFC 3339) | When the token expires; refresh before it lapses | ## Notifications The NotificationService provides real-time event delivery via server-sent events (SSE). Clients subscribe to a stream and receive notifications as they are published. **Service**: `firetiger.notifications.v1.NotificationService` **Access**: Read-write ## Methods | Method | Description | |:-------|:------------| | [Subscribe](#subscribe) | Open a server-streaming subscription for real-time notifications | | [Publish](#publish) | Publish a notification to all active subscribers | --- ## Subscribe Open a long-lived server-streaming connection to receive notifications in real time. The server sends events as they arrive; the stream stays open until the client disconnects or the server closes it. ``` POST /firetiger.notifications.v1.NotificationService/Subscribe ``` REST alternative (SSE stream): ``` POST /v1/notifications:subscribe ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `organization_id` | string | No | Filter notifications to a specific organization. Defaults to the authenticated caller's organization. | ```bash curl -X POST "{{ site.api_url }}/firetiger.notifications.v1.NotificationService/Subscribe" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{}' ``` --- ## Publish Publish a notification. All active subscribers receive the event. ``` POST /firetiger.notifications.v1.NotificationService/Publish ``` REST alternative: ``` POST /v1/notifications:publish ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `notification` | object | Yes | The notification to publish | ```bash curl -X POST "{{ site.api_url }}/firetiger.notifications.v1.NotificationService/Publish" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"notification": {"payload": "{}"}}' ``` --- ## Agent session stream payloads Agent sessions publish several payload types on the topic `agents/{agent}/sessions/{session}`. Subscribe to that topic to tail a session in real time; each `Notification.value` is a `google.protobuf.Any` carrying one of the types below. | Type URL | Purpose | |:---------|:--------| | `type.googleapis.com/firetiger.nxagent.v1.ActivityNotification` | A committed session message, wrapped with its 0-based `sessionOffset` so clients can reconcile against the authoritative ordering returned by [Read](agents.txt#read). The inner `activity` is a `google.protobuf.Any` holding a [`firetiger.nxagent.v1.Activity`](types/activity.txt). | | `type.googleapis.com/firetiger.nxagent.v1.Activity` | The same activity as above, without the offset wrapper. Emitted for back-compat with subscribers that do not track offsets. | | `type.googleapis.com/firetiger.nxagent.v1.AssistantThinking` | Sent once per LLM call, immediately before the first `AssistantOutput`. Signals that the model has begun generating a response but no tokens have arrived yet. | | `type.googleapis.com/firetiger.nxagent.v1.AssistantOutput` | Streams LLM text deltas during response generation. Each delta carries a monotonic `seq` so clients can stitch them in order; `seq` resets per assistant turn and starts at 0. | | `type.googleapis.com/firetiger.nxagent.v1.ToolOutput` | Streams incremental output during tool execution. Addressed by `tool_call_id`; may be text, JSON bytes, or OTLP traces. | `ActivityNotification` is the authoritative shape — clients that need ordered reconciliation should filter for it and ignore the bare `Activity` notifications. The streaming payloads (`AssistantThinking`, `AssistantOutput`, `ToolOutput`) are ephemeral and are not replayed on reconnect; use [Read](agents.txt#read) to recover state after a gap. ## Types Reference documentation for all resource types, enums, and shared types used across the Firetiger API. ### Agent | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`agents/{id}`) | | `title` | string | | Human-readable title for the agent | | `description` | string | | Description of what this agent does | | `prompt` | string | | Initial prompt used to start agent sessions | | `connections` | [AgentConnection](#agent-connection)[] | | Connections and their enabled tools for this agent | | `mcp_connections` | [MCPConnection](#mcp-connection)[] | | MCP server connections enabled for this agent. Only listed connections are loaded at runtime. | | `skills` | [Skill](#skill)[] | OUTPUT_ONLY | Skills available to this agent's fireshell sessions. Populated by the server from the skills bucket on every `GetAgent` call; source of truth is the `name` / `description` user-metadata stamped on each `SKILL.md` object at deploy time. | | `state` | [AgentState](#agent-state) | | The operational state of the agent | | `tags` | string[] | | Tags assigned to this agent (`tags/{tag_id}`). Cannot include system tags. | | `network_profile` | string | | Network profile whose allow-list gates this agent's fireshell egress (`network-profiles/{id}`). When unset, resolves to `network-profiles/default` at runtime. See [Network Profiles](../network-profiles.txt). | | `plan_session` | string | | The session that planned this agent, if created by an agent session (`agents/{agent}/sessions/{session}`) | | `system` | boolean | OUTPUT_ONLY | Whether this is a system agent (auto-created, not user-managed) | | `created_by` | string | OUTPUT_ONLY | The ID of the user who created this agent | | `create_time` | timestamp | OUTPUT_ONLY | When the agent was created | | `update_time` | timestamp | OUTPUT_ONLY | When the agent was last modified | | `delete_time` | timestamp | OUTPUT_ONLY | When the agent was soft-deleted (null if active) | | `expire_time` | timestamp | | Optional expiration time. After this time, the agent will be automatically archived. | **Example** ```json { "name": "agents/error-rate-monitor", "title": "Error Rate Monitor", "description": "Monitors services for abnormal error rates and escalation patterns.", "prompt": "You are a monitoring agent for production services. Your job is to detect abnormal errors...", "connections": [ { "name": "connections/prod-postgres", "enabledTools": ["TOOL_POSTGRES_QUERY"] }, { "name": "connections/prod-prometheus", "enabledTools": ["TOOL_PROMQL_QUERY", "TOOL_PROMQL_QUERY_RANGE"] } ], "mcpConnections": [ { "name": "mcp-connections/internal-tools" } ], "tags": ["tags/production", "tags/monitoring"], "state": "AGENT_STATE_ON", "system": false, "skills": [ {"name": "querying-postgres", "description": "Query a Postgres database using psql and discover schemas before writing real queries."}, {"name": "querying-prometheus-metrics", "description": "Resolve label/metric names via metadata tools before writing PromQL; bound windows; prefer rate/increase on counters."} ], "createTime": "2026-02-21T20:37:33.623697Z", "updateTime": "2026-02-21T20:40:06.610686Z" } ``` ## Agent State | Value | Description | |:------|:------------| | `AGENT_STATE_UNSPECIFIED` | Default value, not used | | `AGENT_STATE_ON` | Agent is active and running | | `AGENT_STATE_OFF` | Agent is disabled | | `AGENT_STATE_REVIEW_REQUIRED` | Agent requires human review before activation | | `AGENT_STATE_RECOMMENDED` | Agent is recommended but not yet activated | ## Agent Connection | Field | Type | Description | |:------|:-----|:------------| | `name` | string | Resource name of the connection (`connections/{connection}`) | | `enabled_tools` | [Tool](connection.txt#tool)[] | List of tools enabled for this agent from this connection. If empty, all tools configured on the connection are enabled. | ## MCP Connection | Field | Type | Description | |:------|:-----|:------------| | `name` | string | Resource name of the MCP connection (`mcp-connections/{mcp_connection}`) | | `server_url` | string | REQUIRED. URL of the external MCP server. Both `http://` and `https://` are supported, over a network transport or directly. | | `network_transport` | string | Optional. Resource name of a [NetworkTransport](../network-transports.md) (`network-transports/{network_transport}`) to route this connection through when the MCP server is reachable only over a private overlay network (e.g. a Tailscale tailnet). Leave empty to connect over the public internet. Settable on the direct-create auth modes (`bearer_token`, `no_auth`, and `oauth_client_credentials`); browser-flow OAuth connections connect over the public internet. | | `bearer_token` | [BearerTokenConfig](#bearer-token-config) | API token / PAT auth (oneof `auth`). Create directly via `CreateMCPConnection`. | | `dynamic_oauth` | DynamicOAuthConfig | OAuth with dynamic client registration, RFC 7591 (oneof `auth`). Created via `InitiateDynamicOAuth`. | | `static_oauth` | StaticOAuthConfig | OAuth with a pre-registered client ID/secret (oneof `auth`). Created via `InitiateStaticOAuth`. | | `oauth_client_credentials` | [OAuthClientCredentialsConfig](#oauth-client-credentials-config) | Machine-to-machine OAuth via the `client_credentials` grant, RFC 6749 §4.4 (oneof `auth`). No browser authorization step: the server mints the access token from the token endpoint at creation and re-mints it on expiry. Create directly via `CreateMCPConnection`. | | `no_auth` | [NoAuthConfig](#no-auth-config) | No authentication — no credentials are sent (oneof `auth`). For internal/localhost servers that require no auth. Create directly via `CreateMCPConnection`. | | `refresh_status` | [google.rpc.Status](https://cloud.google.com/apis/design/errors#error_model) | OUTPUT_ONLY. Last refresh error from the credential-refresh cron or request-path refresh, if any. Cleared on a successful refresh. When the carried `ErrorInfo.reason` is `CONNECTION_FAILURE` (e.g. revoked refresh token), the cron skips this row to avoid retrying a permanently-invalid token. Transient reasons (`INTERNAL_ERROR`) do not suppress retries. | Authentication is configured via the `auth` oneof — set exactly one of `bearer_token`, `dynamic_oauth`, `static_oauth`, `oauth_client_credentials`, or `no_auth`. `CreateMCPConnection` accepts the modes that need no browser flow (`bearer_token`, `no_auth`, and `oauth_client_credentials` — for the latter the access token is minted server-side during creation); the browser-flow OAuth modes are established through the `InitiateDynamicOAuth` / `InitiateStaticOAuth` flows. #### Bearer Token Config | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `token` | string | INPUT_ONLY | API token or personal access token sent as `Authorization: Bearer `. | #### OAuth Client Credentials Config For MCP servers whose OAuth client is registered for the machine-to-machine `client_credentials` grant (RFC 6749 §4.4) — no user sign-in exists, so the browser-flow modes don't apply. The access token is minted from the token endpoint at creation time and automatically re-minted from the stored client ID/secret when it expires. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `client_id` | string | REQUIRED | OAuth client ID. | | `client_secret` | string | INPUT_ONLY | OAuth client secret. Never returned in responses. | | `token_url` | string | | Token endpoint URL (e.g. `https://example.com/api/v1/oauth/token`). Optional — when empty, the endpoint is discovered from the MCP server's OAuth authorization server metadata (RFC 8414). | | `scopes` | string[] | | OAuth scopes to request. Optional — when empty, the scopes advertised by the server's protected-resource metadata (RFC 9728) are requested. | To change these later, `UpdateMCPConnection` with `oauth_client_credentials` in the `update_mask`: the new settings are used to mint and verify a replacement access token before anything is persisted, and any field left unset keeps its stored value (so the client secret only needs re-sending when it actually changes). The same applies to `bearer_token` for rotating an API token. Switching a connection's auth mode is not supported — delete it and create a new one. #### No Auth Config Empty message. Selecting `no_auth` makes the connection send no `Authorization` header — for MCP servers that require no authentication (e.g. internal or localhost servers, often combined with a `network_transport`). ## Skill A piece of per-connection / per-subsystem documentation mounted read-only into the agent's fireshell chamber under `/run/skills/`. One `Skill` corresponds to one `SKILL.md` file on the deployment's skills bucket. The list is returned as an `OUTPUT_ONLY` field on `Agent` — clients read it but do not set it. | Field | Type | Description | |:------|:-----|:------------| | `name` | string | Short name (matches the `SKILL.md` frontmatter `name:`). Also the skill's directory slug under `/run/skills/`; callers that need the in-chamber file path construct it as `/run/skills//SKILL.md`. | | `description` | string | One-line description (matches the `SKILL.md` frontmatter `description:`). | ### Session | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`agents/{agent}/sessions/{session}`) | | `associated_resources` | string[] | | Resource names linked to this session (e.g. `objectives/abc`, `incidents/xyz`) | | `incognito` | bool | | Hidden from normal listings; write operations (issues, notes, slack) are skipped | | `automated` | bool | | Started by automation (scheduled runs, triggers). The bash tool auto-denies any domain-approval request instead of pausing — there's no human to click Approve/Deny | | `title` | string | | Human-readable title for chat history and session pickers | | `created_by` | string | OUTPUT_ONLY | ID of the user who started this session; empty for system-created automation and credentials-based callers without a user identity | | `nxl_plan_sha256` | string | OUTPUT_ONLY | SHA-256 of the compiled NXLPlan that bootstrapped this session. Empty for non-NXL sessions and historical NXL sessions created before this field was stamped | | `create_time` | timestamp | OUTPUT_ONLY | When the session was created | | `update_time` | timestamp | OUTPUT_ONLY | When the session was last modified | | `delete_time` | timestamp | OUTPUT_ONLY | When the session was soft-deleted (null if active) | **Example** ```json { "name": "agents/objectives-evaluator/sessions/ses-00ab12cd34ef", "associatedResources": [ "objectives/obj-daily-uptime", "objectives/obj-daily-uptime/evaluations/eval-56gh78ij" ], "createTime": "2026-02-13T01:02:36.105551Z", "updateTime": "2026-02-13T01:02:36.105551Z" } ``` ## Session Description A richer, read-only view of a session including runtime state from the execution engine. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `session` | [Session](#session) | OUTPUT_ONLY | The session being described | | `status` | [Session Status](#session-status) | OUTPUT_ONLY | Current execution status of the session | | `session_length` | integer | OUTPUT_ONLY | Number of messages in the session | | `last_message` | timestamp | OUTPUT_ONLY | Timestamp of the last message in the session | | `conclusion` | [SessionConclusion](#session-conclusion) | OUTPUT_ONLY | Conclusion of the session, if the agent has concluded | | `trigger` | [Trigger](#trigger) | OUTPUT_ONLY | The trigger that started this session, if any. Resolved at read time from `session.associated_resources`. Reflects the trigger's current configuration, not its state at fire time. Absent for manual / non-trigger-driven sessions. | ## Session Status | Value | Description | |:------|:------------| | `STATUS_UNSPECIFIED` | Session state is undefined (session has no objects) | | `STATUS_EXECUTING` | Agent is currently executing | | `STATUS_WAITING` | Agent is waiting (paused or idle) | ## Session Conclusion The conclusion of a completed session. Exactly one of `done` or `abort` will be set. | Field | Type | Description | |:------|:-----|:------------| | `done` | [Done](#done) | The agent completed its task | | `abort` | [Abort](#abort) | The agent aborted before completing | ### Done | Field | Type | Description | |:------|:-----|:------------| | `message` | string | Summary of what the agent accomplished | | `issues` | [ConclusionIssue](#conclusion-issue)[] | Issues discovered during the session | ### Abort | Field | Type | Description | |:------|:-----|:------------| | `message` | string | Explanation of why the agent aborted | ### Conclusion Issue An issue discovered by the agent during a session. | Field | Type | Description | |:------|:-----|:------------| | `title` | string | Short title for the issue | | `description` | string | Detailed description of the issue | ## Trigger A trigger fires sessions automatically — on a cron schedule, when a Slack message is posted, after a deploy, when a row matches a predicate, etc. When a trigger fires a session, that session's `SessionDescription.trigger` carries the trigger resource so consumers know what kicked the session off. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`triggers/{trigger}`) | | `display_name` | string | | Human-readable name shown in the UI | | `description` | string | | Optional longer description | | `agent` | string | | Resource name of the agent this trigger targets (`agents/{agent}`) | | `enabled` | bool | | Whether the trigger is currently active | | `configuration` | [TriggerConfiguration](#trigger-configuration) | | Type-specific configuration (cron schedule, Slack channels, etc.) | ### Trigger Configuration Exactly one of the configuration fields is set, identifying the trigger type: | Field | Description | |:------|:------------| | `cron` | Fires on a cron schedule. Carries `schedule` and `timezone`. | | `manual` | User-driven via webhook. Carries optional `webhook_address`. | | `post_deploy` | Fires after a deploy completes. Carries `repository`, `environment`, `sha`, `delay`. | | `row` | Fires when a row matching a predicate appears in a table. Carries `table_name`, `predicate`, `cooldown`. | | `slack_message_posted` | Fires on any message posted to one of the listed channels. | | `slack_agent_mentioned` | Fires when the agent's Slack user-group handle is `@mentioned`. | See `proto/firetiger/triggers/v1/triggers.proto` for the full field-level schema of each configuration variant. ## Artifact | Field | Type | Description | |:------|:-----|:------------| | `contents` | bytes | Raw bytes of the artifact | ### Connection | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`connections/{id}`) | | `connection_type` | [ConnectionType](#connection-type) | REQUIRED | Type of external system (e.g. `CONNECTION_TYPE_POSTGRES`, `CONNECTION_TYPE_SLACK`) | | `display_name` | string | REQUIRED | Human-readable name for the connection | | `description` | string | REQUIRED | Description of what this connection provides | | `create_time` | timestamp | OUTPUT_ONLY | When the connection was created | | `update_time` | timestamp | OUTPUT_ONLY | When the connection was last modified | | `delete_time` | timestamp | OUTPUT_ONLY | When the connection was soft-deleted (null if active) | | `secret_id` | string | | ID of the secret storing full connection details | | `connection_details` | [ConnectionDetails](#connection-details) | REQUIRED | Connection-specific configuration | | `tool_configurations` | [ToolConfiguration](#tool-configuration)[] | | Which tools are enabled for this connection | | `read_only` | boolean | OUTPUT_ONLY | Whether the connection is system-managed and cannot be edited | | `shell_environment` | [ShellEnvironment](#shell-environment) | OUTPUT_ONLY | Shell prompt information (populated on Get only) | | `connection_status` | [Status](#status) | OUTPUT_ONLY | Operational status of credential resolution (nil or OK means healthy) | | `token_expires_at` | timestamp | OUTPUT_ONLY | When the current OAuth access token expires. Set by the server whenever credentials are refreshed; null for connection types that don't use refreshable OAuth tokens. | **Example** ```json { "name": "connections/prod-iceberg", "connectionType": "CONNECTION_TYPE_ICEBERG", "displayName": "Production Iceberg", "description": "Iceberg connection for production data lake", "createTime": "2026-02-07T08:28:56.588670Z", "updateTime": "2026-02-07T08:28:56.588670Z", "secretId": "conn_778f2937dc19466086069ee42d234b0a" } ``` ## Connection Type | Value | Description | |:------|:------------| | `CONNECTION_TYPE_POSTGRES` | PostgreSQL database | | `CONNECTION_TYPE_HTTP` | Generic HTTP API | | `CONNECTION_TYPE_GOOGLE_POSTMASTER` | Google Postmaster Tools | | `CONNECTION_TYPE_PYLON` | Pylon customer support platform | | `CONNECTION_TYPE_GITHUB` | GitHub App installation | | `CONNECTION_TYPE_SLACK` | Slack workspace | | `CONNECTION_TYPE_PROMQL` | PromQL-compatible Prometheus API | | `CONNECTION_TYPE_LINEAR` | Linear project management | | `CONNECTION_TYPE_AWS` | AWS (IAM role assumption or static credentials) | | `CONNECTION_TYPE_GCP` | GCP (service account authentication) | | `CONNECTION_TYPE_CLICKHOUSE` | ClickHouse database | | `CONNECTION_TYPE_MYSQL` | MySQL database | | `CONNECTION_TYPE_ICEBERG` | Apache Iceberg REST catalog | | `CONNECTION_TYPE_DATADOG` | Datadog observability platform | | `CONNECTION_TYPE_INCIDENT_IO` | incident.io webhook integration | | `CONNECTION_TYPE_PAGERDUTY` | PagerDuty incident management | | `CONNECTION_TYPE_OPENAPI` | OpenAPI-described REST API | | `CONNECTION_TYPE_CLERK` | Clerk user management and authentication | | `CONNECTION_TYPE_VANTA` | Vanta compliance and security automation | | `CONNECTION_TYPE_CONVEX` | Convex log stream webhook integration | | `CONNECTION_TYPE_WORKOS` | WorkOS user management, SSO, and directory sync | | `CONNECTION_TYPE_TRINO` | Trino distributed SQL query engine | | `CONNECTION_TYPE_EMAIL_WEBHOOK` | Guarded customer-email delivery to a fixed webhook endpoint | | `CONNECTION_TYPE_GRPC` | gRPC or ConnectRPC service via server reflection | | `CONNECTION_TYPE_CURSOR` | Cursor AI code editor — trigger cloud agents to fix issues | | `CONNECTION_TYPE_ELASTICSEARCH` | Elasticsearch cluster (queried via Elasticsearch SQL) | | `CONNECTION_TYPE_GRAPHQL` | GraphQL endpoint, driven via fireshell `curl` with introspection-based discovery | | `CONNECTION_TYPE_TEMBO` | Tembo — launch coding-agent tasks or trigger automations on issues | | `CONNECTION_TYPE_REPLICAS` | Replicas — launch background coding agents on issues to open pull requests | | `CONNECTION_TYPE_CODER` | Coder — launch a self-hosted Coder Task (workspace running an embedded coding agent) on issues | | `CONNECTION_TYPE_LAUNCHDARKLY` | LaunchDarkly — read-only feature-flag audit/list for Change Monitor correlation | | `CONNECTION_TYPE_DEVIN` | Devin — launch Cognition's cloud coding agent on issues to open pull requests | ## Tool Each connection type supports a set of tools that agents can use. The `enabled_tools` field on [AgentConnection](agent.txt#agent-connection) and [RunbookConnection](runbook.txt#runbook-connection) references values from this enum. | Value | Description | |:------|:------------| | `TOOL_POSTGRES_QUERY` | Execute a read-only SQL query against PostgreSQL | | `TOOL_POSTGRES_WRITE_QUERY` | Execute a read-write SQL query against PostgreSQL | | `TOOL_HTTP_REQUEST` | Make an HTTP request | | `TOOL_GITHUB_SEARCH_CODE` | Search code in GitHub repositories | | `TOOL_GITHUB_GET_FILE` | Get file contents from a GitHub repository | | `TOOL_GITHUB_LIST_DIRECTORY` | List files in a GitHub repository directory | | `TOOL_GITHUB_GET_PR` | Get a GitHub pull request | | `TOOL_GITHUB_GET_ISSUE` | Get a GitHub issue | | `TOOL_GITHUB_PULL_REQUEST_SEARCH` | Search GitHub pull requests | | `TOOL_GITHUB_CREATE_ISSUE` | Create a GitHub issue | | `TOOL_GITHUB_ADD_ISSUE_COMMENT` | Add a comment to a GitHub issue | | `TOOL_GITHUB_LIST_ISSUES` | List GitHub issues | | `TOOL_GITHUB_CREATE_PR_COMMENT` | Add a comment to a GitHub pull request | | `TOOL_GITHUB_LIST_PR_COMMENTS` | List comments on a GitHub pull request | | `TOOL_GITHUB_LIST_PR_REVIEWS` | List reviews on a GitHub pull request | | `TOOL_GITHUB_LIST_ORGANIZATION_MEMBERS` | List members of a GitHub organization | | `TOOL_GITHUB_LIST_REPOS` | List repositories in a GitHub organization | | `TOOL_PROMQL_QUERY` | Execute an instant PromQL query | | `TOOL_PROMQL_QUERY_RANGE` | Execute a range PromQL query | | `TOOL_PROMQL_LABELS` | List all label names from Prometheus | | `TOOL_PROMQL_LABEL_VALUES` | Get label values from Prometheus | | `TOOL_PROMQL_SERIES` | Get time series from Prometheus | | `TOOL_PROMQL_METADATA` | Get metric metadata from Prometheus | | `TOOL_GOOGLE_POSTMASTER_LIST_DOMAINS` | List domains in Google Postmaster Tools | | `TOOL_GOOGLE_POSTMASTER_GET_TRAFFIC_STATS` | Get traffic stats from Google Postmaster Tools | | `TOOL_PYLON_LIST_ISSUES` | List issues from Pylon | | `TOOL_SLACK_SEND_MESSAGE` | Send a message in Slack | | `TOOL_FIRETIGER_QUERY` | Query Firetiger's data lake | | `TOOL_FIRETIGER_CODEBASE_SEARCH` | Search code across configured repositories | | `TOOL_LINEAR_LIST_ISSUES` | List issues from Linear | | `TOOL_LINEAR_LIST_USERS` | List users from Linear | | `TOOL_CLICKHOUSE_QUERY` | Execute a query against ClickHouse | | `TOOL_MYSQL_QUERY` | Execute a read-only query against MySQL | | `TOOL_ICEBERG_QUERY` | Query an Apache Iceberg table | | `TOOL_DATADOG_QUERY_METRICS` | Query metrics from Datadog | | `TOOL_DATADOG_QUERY_LOGS` | Query logs from Datadog | | `TOOL_OPENAPI_REQUEST` | Make HTTP requests to an OpenAPI-described API | | `TOOL_OPENAPI_SCHEMA` | Introspect the OpenAPI spec for available endpoints and schemas | | `TOOL_GCP_QUERY_METRICS` | Query GCP Cloud Monitoring metrics via PromQL | | `TOOL_TRINO_QUERY` | Execute a query against a Trino cluster | | `TOOL_EMAIL_WEBHOOK_SEND` | Send an approved customer email via a guarded webhook | | `TOOL_GRPC_SERVICES` | List all services and methods available via gRPC server reflection | | `TOOL_GRPC_DESCRIBE` | Show the full schema of a gRPC service including field types and documentation | | `TOOL_GRPC_REQUEST` | Invoke a gRPC or ConnectRPC method with a JSON payload | ## Tool Configuration Controls whether a specific tool is enabled on a connection. | Field | Type | Description | |:------|:-----|:------------| | `tool` | [Tool](#tool) | The tool being configured | | `is_enabled` | boolean | Whether this tool is enabled | ## Connection Details Connection-specific configuration. This is a union type -- exactly one variant is set, matching the connection's `connection_type`. The fields vary by connection type (e.g. host/port/database for PostgreSQL, base URL and headers for HTTP). Refer to the connection creation UI or `ftops api connections` for the fields available for each type. ### HTTP Connection Details | Field | Type | Description | |:------|:-----|:------------| | `base_url` | string | Base URL for HTTP requests | | `allowed_routes` | string[] | Allowed route patterns | | `headers` | map\ | Non-auth headers included in every request (e.g. `Content-Type`, `X-Tenant-ID`) | | `max_response_size_bytes` | uint64 | Maximum response size in bytes | | `timeout_seconds` | uint32 | Request timeout in seconds | | `oauth_client_credentials` | [OAuthClientCredentials](#oauth-client-credentials) | OAuth 2.0 Client Credentials grant (oneof `auth`) | | `bearer_token` | [HttpBearerAuth](#bearer-token) | Static Bearer token (oneof `auth`) | | `basic_auth` | [HttpBasicAuth](#basic-auth) | HTTP Basic authentication (oneof `auth`) | | `static_headers` | [HttpAuthStaticHeaders](#static-headers) | Auth via raw headers (oneof `auth`) | | `webhook_signing_secret` | string | Optional outbound webhook signing secret | Authentication is configured via the `auth` oneof — set exactly one of the four auth fields above. #### OAuth Client Credentials The resolver automatically obtains and refreshes an OAuth 2.0 access token using the client credentials grant. The token is injected as an `Authorization: Bearer` header on each request. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `token_url` | string | REQUIRED | Token endpoint URL (must use HTTPS, e.g. `https://api.vanta.com/oauth/token`) | | `client_id` | string | | OAuth client ID (returned in responses) | | `client_secret` | string | INPUT_ONLY | OAuth client secret (not returned in responses) | | `scopes` | string | | Optional space-separated scopes to request | | `extra_params` | map\ | | Optional additional form parameters for the token request (e.g. `audience`) | | `access_token` | string | OUTPUT_ONLY | Current access token (populated by the resolver) | | `token_expires_at` | timestamp | OUTPUT_ONLY | When the current access token expires | #### Bearer Token | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `token` | string | INPUT_ONLY | Bearer token value | #### Basic Auth | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `username` | string | | Username | | `password` | string | INPUT_ONLY | Password | #### Static Headers | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `headers` | map\ | INPUT_ONLY | Auth headers to include in every request | Non-auth `headers` can be used alongside any auth method (e.g. `X-Tenant-ID`). Auth headers are resolved from the `auth` field and merged into the request; do not duplicate auth headers in `headers`. #### AWS SigV4 Auth AWS Signature Version 4 authentication for services that require SigV4-signed requests (e.g., Amazon Managed Prometheus). The server resolves the configured credentials into temporary session credentials before each request. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `region` | string | REQUIRED | AWS region for SigV4 signing (e.g. `us-west-2`) | | `assume_role` | [AwsAssumeRoleAuth](#aws-assume-role-auth) | | IAM role assumption (oneof `credentials`) | | `static_credentials` | [AwsStaticCredentialsAuth](#aws-static-credentials-auth) | | Static IAM credentials (oneof `credentials`) | | `session_credentials` | AwsSessionCredentials | OUTPUT_ONLY | Resolved temporary credentials (populated by the server) | Exactly one of `assume_role` or `static_credentials` must be set. #### AWS Assume Role Auth | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `role_arn` | string | REQUIRED | ARN of the IAM role to assume (e.g. `arn:aws:iam::123456789012:role/prometheus-read`) | | `external_id` | string | | Optional external ID for cross-account access | #### AWS Static Credentials Auth | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `access_key_id` | string | INPUT_ONLY, REQUIRED | AWS access key ID | | `secret_access_key` | string | INPUT_ONLY, REQUIRED | AWS secret access key | | `session_token` | string | INPUT_ONLY | Optional session token for temporary credentials | #### HTTP Webhook Signing When `webhook_signing_secret` is present, outbound HTTP requests can include a GitHub-style `X-Webhook-Signature` header computed as `sha256=` over the exact raw request body bytes. The runtime only signs `POST`, `PUT`, `DELETE`, and `PATCH` requests, and only when a request body is actually sent. ### Email Webhook Connection Details Guarded customer-email delivery to a fixed webhook endpoint. | Field | Type | Description | |:------|:-----|:------------| | `base_url` | string | Full webhook URL for customer email delivery | | `allowed_routes` | string[] | Allowed route patterns. In practice this is fixed to `POST /` | | `headers` | map\ | Non-auth headers included in every request | | `max_response_size_bytes` | uint64 | Maximum response size in bytes | | `timeout_seconds` | uint32 | Request timeout in seconds | | `oauth_client_credentials` | [OAuthClientCredentials](#oauth-client-credentials) | OAuth 2.0 Client Credentials grant (oneof `auth`) | | `bearer_token` | [HttpBearerAuth](#bearer-token) | Static Bearer token (oneof `auth`) | | `basic_auth` | [HttpBasicAuth](#basic-auth) | HTTP Basic authentication (oneof `auth`) | | `static_headers` | [HttpAuthStaticHeaders](#static-headers) | Auth via raw headers (oneof `auth`) | | `webhook_signing_secret` | string | Optional outbound webhook signing secret | | `slack_connection_name` | string | Optional Slack connection to notify when a customer email is waiting for review | | `slack_channel` | string | Optional Slack channel to notify when a customer email is waiting for review | Authentication is configured via the `auth` oneof — set exactly one of the four auth fields above. The auth field shapes are the same as [HTTP Connection Details](#http-connection-details). If both `slack_connection_name` and `slack_channel` are set, Firetiger sends a best-effort Slack notification when a customer email is waiting for review. If they are omitted, the guarded email flow still works normally. ### gRPC Connection Details Connect to a gRPC or ConnectRPC service. Agents use the `grpc_services`, `grpc_describe`, and `grpc_request` tools for reflection-based discovery and method invocation. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `address` | string | REQUIRED | Server address in `host:port` format (e.g. `api.example.com:443`) | | `protocol` | enum | | Wire protocol for method invocation. One of `GRPC_PROTOCOL_GRPC` (default), `GRPC_PROTOCOL_CONNECT`, `GRPC_PROTOCOL_GRPCWEB`. Service discovery always uses gRPC regardless of this setting. | | `basic_auth` | BasicAuth | | HTTP Basic authentication (oneof `auth`) | | `bearer_auth` | BearerAuth | | Bearer token authentication (oneof `auth`) | Authentication is optional — omit the `auth` field for unauthenticated services. When auth is configured, credentials are injected as HTTP headers on TLS connections. Auth injection requires port 443; credentials will not be injected for non-standard ports. #### gRPC Basic Auth | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `username` | string | | Username | | `password` | string | INPUT_ONLY | Password | #### gRPC Bearer Auth | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `token` | string | INPUT_ONLY | Bearer token value | ### PromQL Connection Details Connect to a Prometheus-compatible metrics API. Agents use the `promql_query`, `promql_query_range`, `promql_labels`, `promql_label_values`, `promql_series`, and `promql_metadata` tools. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `base_url` | string | REQUIRED | Base URL of the Prometheus API (e.g. `https://prometheus.example.com`) | | `timeout` | duration | | Request timeout for API calls. Defaults to 30 seconds | | `basic` | [HttpBasicAuth](#basic-auth) | | HTTP Basic authentication (oneof `auth`) | | `bearer` | [HttpBearerAuth](#bearer-token) | | Bearer token authentication (oneof `auth`) | | `sigv4` | [AwsSigV4Auth](#aws-sigv4-auth) | | AWS SigV4 authentication for Amazon Managed Prometheus (oneof `auth`) | Authentication is optional — omit the `auth` field entirely for Prometheus instances that don't require authentication (e.g. internal/VPN-only deployments). When auth is needed, set exactly one of the three auth fields above. **Example (no auth)** ```json { "connectionType": "CONNECTION_TYPE_PROMQL", "displayName": "Internal Prometheus", "description": "VPN-only Prometheus instance", "connectionDetails": { "promql": { "baseUrl": "https://prometheus.internal.example.com" } } } ``` **Example (bearer token)** ```json { "connectionType": "CONNECTION_TYPE_PROMQL", "displayName": "Grafana Cloud Prometheus", "description": "Grafana Cloud managed Prometheus", "connectionDetails": { "promql": { "baseUrl": "https://prometheus-prod-01-eu-west-0.grafana.net/api/prom", "bearer": { "token": "glc_..." } } } } ``` **Example (AWS SigV4 — Amazon Managed Prometheus)** ```json { "connectionType": "CONNECTION_TYPE_PROMQL", "displayName": "Production AMP", "description": "Amazon Managed Prometheus workspace", "connectionDetails": { "promql": { "baseUrl": "https://aps-workspaces.us-west-2.amazonaws.com/workspaces/ws-abc123", "sigv4": { "region": "us-west-2", "assumeRole": { "roleArn": "arn:aws:iam::123456789012:role/prometheus-read" } } } } } ``` ### OpenAPI Connection Details Connect to any REST API that publishes an OpenAPI (Swagger) specification. The agent can introspect the spec to discover endpoints and make authenticated requests. | Field | Type | Description | |:------|:-----|:------------| | `spec_url` | string | URL to fetch the OpenAPI specification (JSON or YAML) | | `server_url` | string | Base URL of the API server. If empty, derived from the spec's `servers[0].url` | | `oauth_client_credentials` | [OAuthClientCredentials](#oauth-client-credentials) | OAuth 2.0 Client Credentials grant (oneof `auth`) | | `bearer_token` | [HttpBearerAuth](#bearer-token) | Static Bearer token (oneof `auth`) | | `basic_auth` | [HttpBasicAuth](#basic-auth) | HTTP Basic authentication (oneof `auth`) | Authentication is configured via the `auth` oneof — set exactly one of the three auth fields above. The auth types reuse the same messages as [HTTP Connection Details](#http-connection-details). When `server_url` is omitted, the resolver fetches the spec and extracts the base URL from `servers[0].url`, resolving relative paths (e.g. `/v1`) against the spec URL. **Example** ```json { "connectionType": "CONNECTION_TYPE_OPENAPI", "displayName": "Vanta API", "description": "Vanta compliance platform API", "connectionDetails": { "openapi": { "specUrl": "https://firetiger-public.s3.us-west-2.amazonaws.com/connections/vanta/openapi.json", "serverUrl": "https://api.vanta.com/v1", "oauthClientCredentials": { "tokenUrl": "https://api.vanta.com/oauth/token", "clientId": "my-client-id", "clientSecret": "my-client-secret", "scopes": "vanta-api.all:read" } } } } ``` ### Clerk Connection Details Connect to the Clerk Backend API for user management and authentication. Uses a Bearer token (Clerk secret key) for auth. | Field | Type | Description | |:------|:-----|:------------| | `bearer_token` | [HttpBearerAuth](#bearer-token) | Clerk Secret Key (`sk_live_...` or `sk_test_...`) as Bearer token (oneof `auth`) | Authentication is configured via the `auth` oneof. The Clerk connection automatically configures the OpenAPI spec URL (`https://clerk.com/docs/reference/spec/bapi/2025-11-10`) and server URL (`https://api.clerk.com`). Agents use the `openapi_schema` and `openapi_request` tools to interact with the API. **Example** ```json { "connectionType": "CONNECTION_TYPE_CLERK", "displayName": "Clerk", "description": "Clerk user management and authentication API", "connectionDetails": { "clerk": { "bearerToken": { "token": "sk_live_..." } } } } ``` ### Vanta Connection Details Connect to the Vanta API for compliance and security automation. Uses OAuth 2.0 Client Credentials for authentication. | Field | Type | Description | |:------|:-----|:------------| | `oauth_client_credentials` | [OAuthClientCredentials](#oauth-client-credentials) | OAuth 2.0 Client Credentials for Vanta API (oneof `auth`) | Authentication is configured via the `auth` oneof. The Vanta connection automatically configures the OpenAPI spec URL (`https://firetiger-public.s3.us-west-2.amazonaws.com/connections/vanta/openapi.json`) and server URL (`https://api.vanta.com/v1`). Agents use the `openapi_schema` and `openapi_request` tools to interact with the API. **Example** ```json { "connectionType": "CONNECTION_TYPE_VANTA", "displayName": "Vanta", "description": "Vanta compliance and security automation API", "connectionDetails": { "vanta": { "oauthClientCredentials": { "tokenUrl": "https://api.vanta.com/oauth/token", "clientId": "my-client-id", "clientSecret": "my-client-secret", "scopes": "vanta-api.all:read" } } } } ``` ### WorkOS Connection Details Connect to the WorkOS API for user management, SSO, directory sync, and audit logs. Uses standard [WorkOS API authentication](https://workos.com/docs/reference/api-authentication) (API key starting with `sk_`). Agents use the `openapi_schema` and `openapi_request` tools to interact with the API. | Field | Type | Description | |:------|:-----|:------------| | `bearer_token` | [HttpBearerAuth](#bearer-token) | WorkOS API key as Bearer token (oneof `auth`) | | `read_only` | boolean | When true, only GET requests are allowed | **Example** ```json { "connectionType": "CONNECTION_TYPE_WORKOS", "displayName": "WorkOS", "connectionDetails": { "workOs": { "bearerToken": { "token": "sk_..." }, "readOnly": true } } } ``` ### Trino Connection Details Connect to a Trino distributed SQL query engine (including Starburst). Agents use the `query_trino` tool to execute SQL queries. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `host` | string | REQUIRED | Trino coordinator host (e.g. `trino.example.com`) | | `port` | int32 | REQUIRED | Coordinator port (default 8080 for HTTP, 443 for HTTPS) | | `catalog` | string | REQUIRED | Default catalog (e.g. `hive`, `iceberg`, `tpch`) | | `schema` | string | | Default schema within the catalog (optional) | | `username` | string | REQUIRED | Username for authentication | | `password` | string | INPUT_ONLY | Password (optional — Trino supports no-auth setups) | | `secure` | boolean | | Enable HTTPS (default true). When `false`, the fireshell proxy routes the configured port as plaintext HTTP and dials the upstream over `http://`; when `true`, it terminates TLS and dials over `https://`. Set `false` for clusters that only serve HTTP on the coordinator port. | **Example** ```json { "connectionType": "CONNECTION_TYPE_TRINO", "displayName": "Starburst Trino", "description": "Production Trino cluster", "connectionDetails": { "trino": { "host": "trino.example.com", "port": 443, "catalog": "hive", "schema": "default", "username": "trino_user", "password": "secret", "secure": true } } } ``` ### Elasticsearch Connection Details Connect to an Elasticsearch cluster. Agents drive Elasticsearch queries through the shell environment — `$ES_URL` is exported and an `Authorization` header (Basic or ApiKey, based on the selected auth branch) is injected by the fireshell proxy. Use [Elasticsearch SQL](https://www.elastic.co/guide/en/elasticsearch/reference/current/xpack-sql.txt) via `POST $ES_URL/_sql?format=json`. There is no dedicated `TOOL_*` enum for Elasticsearch. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `url` | string | REQUIRED | Cluster base URL including scheme. Both `https://` and `http://` are accepted (e.g. `https://es.example.com:9200` or `http://es.internal:9200`). The fireshell proxy MITMs as TLS for `https://` URLs and as plaintext HTTP for `http://` URLs; pick the scheme that matches the actual cluster — pasting `https://` against a plaintext server fails the upstream TLS handshake and returns 502. | | `auth` | oneof | REQUIRED | Authentication method. Set exactly one of `basic`, `api_key`, or `none` | | `auth.basic.username` | string | REQUIRED (within `basic`) | Username for HTTP Basic auth | | `auth.basic.password` | string | INPUT_ONLY | Password for HTTP Basic auth | | `auth.api_key.token` | string | INPUT_ONLY | Base64-encoded Elastic API key, sent as `Authorization: ApiKey ` (the `api_key` oneof branch wraps an [HttpBearerAuth](#bearer-token)-shaped message; only the header scheme prefix differs from a standard bearer token) | | `auth.none` | message | | Explicitly unauthenticated. For local/dev clusters with security disabled. | | `tls_skip_verify` | boolean | | Skip TLS certificate verification. Use only for self-signed clusters on trusted networks. | When updating a connection, omit `auth.basic.password` or `auth.api_key.token` to keep the existing credential. **Example** ```json { "connectionType": "CONNECTION_TYPE_ELASTICSEARCH", "displayName": "Production Elasticsearch", "description": "Primary search cluster", "connectionDetails": { "elasticsearch": { "url": "https://es.example.com:9200", "basic": { "username": "elastic", "password": "secret" } } } } ``` ### GraphQL Connection Details Connect to any GraphQL endpoint. The connection is **shell-only** — there is no dedicated `TOOL_*` enum. Agents drive it through the fireshell environment: `$GRAPHQL_URL` is exported and the configured auth header(s) are injected by the proxy on the URL's host. Discover the schema via GraphQL introspection at runtime (e.g. `{__schema{queryType{fields{name description}}}}`) — schemas are not cached on the connection record because real-world schemas can be hundreds of kilobytes of SDL. The `auth` oneof reuses the same auth messages as the [HTTP](#http-connection-details) and [OpenAPI](#openapi-connection-details) connection types so creation forms, Terraform, and credential rotation behave identically across them. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `url` | string | REQUIRED | Endpoint URL. Both `https://` and `http://` are accepted (e.g. `https://6.8.sourcegraph.com/api/graphql` or `http://gql.internal/graphql`). The fireshell proxy MITMs as TLS for `https://` URLs and as plaintext HTTP for `http://` URLs; pick the scheme that matches the actual server — pasting `https://` against a plaintext endpoint fails the upstream TLS handshake and returns 502. | | `auth` | oneof | REQUIRED | Authentication method. Set exactly one of `bearer`, `basic`, `static_headers`, or `none`. | | `auth.bearer.token` | string | INPUT_ONLY | Bearer token, sent as `Authorization: Bearer `. Use for Linear, GitHub GraphQL, and other APIs that follow the standard Bearer scheme. | | `auth.basic.username` | string | REQUIRED (within `basic`) | Username for HTTP Basic auth. | | `auth.basic.password` | string | INPUT_ONLY | Password for HTTP Basic auth. | | `auth.static_headers.headers` | map\ | INPUT_ONLY | Arbitrary header map, injected on the configured host. Covers schemes that don't fit Bearer — Sourcegraph (`Authorization: token `), Shopify Admin (`X-Shopify-Access-Token: `), Hasura (`X-Hasura-Admin-Secret: `), etc. | | `auth.none` | message | | No authentication — for public GraphQL APIs. | When updating a connection, omit `auth.bearer.token`, `auth.basic.password`, or `auth.static_headers.headers` to keep the existing credential. **Example (Sourcegraph — static headers)** ```json { "connectionType": "CONNECTION_TYPE_GRAPHQL", "displayName": "Sourcegraph", "description": "Sourcegraph code search GraphQL API", "connectionDetails": { "graphql": { "url": "https://6.8.sourcegraph.com/api/graphql", "staticHeaders": { "headers": { "Authorization": "token your-sourcegraph-token" } } } } } ``` **Example (Linear — bearer token)** ```json { "connectionType": "CONNECTION_TYPE_GRAPHQL", "displayName": "Linear", "description": "Linear GraphQL API", "connectionDetails": { "graphql": { "url": "https://api.linear.app/graphql", "bearer": { "token": "lin_api_..." } } } } ``` ### Datadog Connection Details Connect to Datadog for querying metrics, logs, traces, monitors, and dashboards. Both an API key and an Application Key are required. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `site` | string | REQUIRED | Datadog site (e.g. `datadoghq.com`, `datadoghq.eu`, `us3.datadoghq.com`) | | `api_key` | string | INPUT_ONLY | Datadog API key. Required for all Datadog API requests. Find it in **Organization Settings → API Keys**. | | `application_key` | string | INPUT_ONLY | Datadog Application Key. Required for query operations (metrics, logs, traces) and management endpoints (monitors, dashboards). Find it in **Organization Settings → Application Keys**. | When updating a connection, omit `api_key` and `application_key` to keep the existing values. **Example** ```json { "connectionType": "CONNECTION_TYPE_DATADOG", "displayName": "Datadog Production", "description": "Datadog observability platform - query metrics, search logs, inspect monitors, and analyze APM traces", "connectionDetails": { "datadog": { "site": "datadoghq.com", "apiKey": "your-api-key", "applicationKey": "your-application-key" } } } ``` ### PagerDuty Connection Details Two independent modes that can be used together: REST API access for agents and inbound V3 webhook event ingest. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `api_token` | string | INPUT_ONLY | PagerDuty API token used for REST API requests. Sent as `Authorization: Token token=`. Find it under **Integrations → API Access Keys**. Only required if agents need to call the PagerDuty REST API. | | `signing_secret` | string | INPUT_ONLY | PagerDuty V3 webhook signing secret. Shown once when the webhook subscription is created in PagerDuty. Required for webhook ingest; inbound deliveries are verified with HMAC-SHA256 against the `X-PagerDuty-Signature` header. | | `webhook_token` | string | OUTPUT_ONLY | Rotation-safe token assigned by Firetiger when [`RegisterPagerdutyWebhook`](../connections.txt#registerpagerdutywebhook) is called. Embedded in `webhook_url`. | | `webhook_url` | string | OUTPUT_ONLY | Per-connection URL to paste into the PagerDuty V3 webhook subscription. Set by `RegisterPagerdutyWebhook`. | When updating a connection, omit `api_token` and `signing_secret` to keep their existing values. **Round-tripping the webhook fields.** `webhook_token` and `webhook_url` are OUTPUT_ONLY (server-populated), but `UpdateConnection` with `update_mask=["connection_details"]` treats any field absent from the patch as an explicit clear. To update `signing_secret` after `RegisterPagerdutyWebhook` without losing the generated URL, include the current `webhook_token` and `webhook_url` (read from `GetConnection`) in the patch alongside `signing_secret`. **Example** ```json { "connectionType": "CONNECTION_TYPE_PAGERDUTY", "displayName": "Production PagerDuty", "description": "PagerDuty incident management — REST + V3 webhook ingest", "connectionDetails": { "pagerduty": { "apiToken": "your-api-token", "signingSecret": "your-signing-secret", "webhookToken": "", "webhookUrl": "" } } } ``` ### Cursor Connection Details Connect to Cursor to trigger cloud agents that automatically fix issues in your codebase. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `api_key` | string | INPUT_ONLY | Cursor API key. Find it at **cursor.com/dashboard/cloud-agents** under **My User API Keys**. | | `default_repository` | string | Optional | Default GitHub repository (e.g. `https://github.com/owner/repo`) used to seed the agent when the issue carries no GitHub link. A GitHub link on the issue takes precedence. Leave empty to require a link on every issue. | When updating a connection, omit `api_key` to keep the existing value. **Example** ```json { "connectionType": "CONNECTION_TYPE_CURSOR", "displayName": "Cursor", "description": "Cursor AI code editor — trigger cloud agents to fix issues", "connectionDetails": { "cursor": { "apiKey": "your-cursor-api-key", "defaultRepository": "https://github.com/owner/repo" } } } ``` ### Tembo Connection Details Connect to Tembo to launch coding-agent tasks or trigger pre-configured automations on issues. Tembo's API lives at `api.tembo.io`; the organization is derived server-side from the API key. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `api_key` | string | INPUT_ONLY | Tembo API key. Find it at **app.tembo.io** under **Settings → API Keys**. | | `task` | [TemboTaskMode](#tembo-task-mode) | oneof `mode` | Configures ad-hoc coding-agent task launches (`POST /task/create`). Mutually exclusive with `automation`. | | `automation` | [TemboAutomationMode](#tembo-automation-mode) | oneof `mode` | Configures automation triggers (`POST /automation/{key}/trigger`). Mutually exclusive with `task`. | | `default_repository` | string | Optional | Default GitHub repository (e.g. `https://github.com/owner/repo`) used to seed a Tembo task when the issue carries no GitHub link. A GitHub link on the issue takes precedence. Applies to task mode only; automation mode tolerates an empty repository. | Exactly one of `task` or `automation` must be set on create and whenever the full `connection_details` object is replaced on update; this is enforced at the API boundary. When updating a connection, omit `api_key` to keep the existing value. #### Tembo Task Mode | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `default_agent` | string | Optional | Default Tembo agent identifier, e.g. `claudeCode:claude-sonnet-4-6`. Leave empty to use Tembo's org-level default. | #### Tembo Automation Mode | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `automation_key` | string | REQUIRED | Tembo automation key or UUID. Find it in the automation's properties panel on **app.tembo.io**. | **Example (task mode)** ```json { "connectionType": "CONNECTION_TYPE_TEMBO", "displayName": "Tembo", "description": "Launch Tembo coding agents on issues", "connectionDetails": { "tembo": { "apiKey": "your-tembo-api-key", "defaultRepository": "https://github.com/owner/repo", "task": { "defaultAgent": "claudeCode:claude-sonnet-4-6" } } } } ``` **Example (automation mode)** ```json { "connectionType": "CONNECTION_TYPE_TEMBO", "displayName": "Tembo on-call automation", "description": "Trigger the on-call Tembo automation for incoming issues", "connectionDetails": { "tembo": { "apiKey": "your-tembo-api-key", "automation": { "automationKey": "fix-on-call-issues" } } } } ``` ### Replicas Connection Details Connect to Replicas to launch background coding agents on issues. Each agent runs in a sandboxed VM bound to a Replicas environment and opens a pull request. Replicas' API lives at `api.tryreplicas.com`; the organization is derived server-side from the API key. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `api_key` | string | INPUT_ONLY, REQUIRED | Replicas API key. Find it at **tryreplicas.com/dashboard/apikeys**. | | `coding_agent` | [ReplicasCodingAgentBackend](#replicas-coding-agent-backend) | Optional | Which agent backend runs inside the sandbox. Defaults to `claude` when unspecified. | | `environment_id` | string | REQUIRED | UUID of the Replicas environment to launch into. Its repository binding determines which repo the agent operates on. Copy it from **tryreplicas.com/dashboard** — installing the Replicas GitHub App on a repo auto-creates a `Default ` environment. | `api_key` and `environment_id` are both required on create; this is enforced at the API boundary. When updating a connection, omit `api_key` to keep the existing value. #### Replicas Coding Agent Backend | Value | Description | |:------|:------------| | `REPLICAS_CODING_AGENT_BACKEND_UNSPECIFIED` | Use the Replicas API default (currently Claude) | | `REPLICAS_CODING_AGENT_BACKEND_CLAUDE` | Run Claude inside the sandbox | | `REPLICAS_CODING_AGENT_BACKEND_CODEX` | Run Codex inside the sandbox | **Example** ```json { "connectionType": "CONNECTION_TYPE_REPLICAS", "displayName": "Replicas", "description": "Launch Replicas background coding agents on issues", "connectionDetails": { "replicas": { "apiKey": "your-replicas-api-key", "codingAgent": "REPLICAS_CODING_AGENT_BACKEND_CLAUDE", "environmentId": "00000000-0000-0000-0000-000000000000" } } } ``` ### Coder Connection Details Connect to a self-hosted [Coder](https://coder.com) deployment to launch a **Coder Task** on issues — a workspace running an embedded coding agent (e.g. Claude Code). The template version determines the embedded agent and the repository the agent operates on, so it lives on the connection. The Firetiger prompt is forwarded as the Task's `input`, and the Task is created via the GA Tasks API (`POST {base_url}/api/v2/tasks/{username}`) using the `Coder-Session-Token` header. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `base_url` | string | REQUIRED | Base URL of the Coder deployment, e.g. `https://coder.example.com`. | | `session_token` | string | INPUT_ONLY, REQUIRED | Long-lived Coder API token. Create one with `coder tokens create`. | | `template_version_id` | string | REQUIRED | UUID of the template version Tasks are built from. Defines the embedded agent and the repository. Find it in Coder under **Templates → your template → the active version**. | | `template_version_preset_id` | string | Optional | UUID of a template version preset (parameter bundle). Omit to use the template's defaults. | | `username` | string | Optional | Coder username that should own created Tasks. Defaults to the token owner (`me`). | `base_url`, `session_token`, and `template_version_id` are all required on create; this is enforced at the API boundary. When updating a connection, omit `session_token` to keep the existing value. **Example** ```json { "connectionType": "CONNECTION_TYPE_CODER", "displayName": "Coder", "description": "Launch Coder Tasks on issues", "connectionDetails": { "coder": { "baseUrl": "https://coder.example.com", "sessionToken": "your-coder-api-token", "templateVersionId": "00000000-0000-0000-0000-000000000000" } } } ``` ### LaunchDarkly Connection Details Connect a [LaunchDarkly](https://launchdarkly.com) account read-only so Change Monitor can correlate flag flips with deploy-window anomalies. The helper binaries `ff_get_changes` and `ff_list_flags` query LD's audit-log and flag endpoints; the agent never writes to LaunchDarkly. Tokens are introspected at create/update time — write-scoped and service tokens are rejected. A single LaunchDarkly connection can cover multiple LD projects — the helpers fan out one read per `project_keys` entry and tag each result row with its source project. v1 still constrains LaunchDarkly environments to a 1:1 mapping with Firetiger deploy environments. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `api_token` | string | INPUT_ONLY, REQUIRED | Personal LaunchDarkly API token with the built-in **Reader** role. Generate one at Account Settings → Authorization → Create Token. Write-scoped tokens are rejected at save time. Omit on update to keep the existing value. | | `sdk_key` | string | INPUT_ONLY | Optional. SDK key reserved for a future flag-evaluation feature; unused in v1. Omit on update to keep the existing value. | | `project_keys` | repeated string | REQUIRED | LaunchDarkly project keys (e.g. `["default", "billing"]`). The helpers issue one API call per key and tag every output row with its project. At least one key is required. | | `project_key` | string | DEPRECATED | Singular form retained for connections persisted before multi-project support. New writes should use `project_keys`. The service reads `project_keys` first and falls back here when empty. | | `flag_env_to_deploy_env` | map | | Optional 1:1 mapping from LaunchDarkly environment keys to Firetiger deploy-environment names. Defaults to identity mapping (matching names on both sides). | | `read_only_verified_at` | google.protobuf.Timestamp | OUTPUT_ONLY | When the service last confirmed the API token has read-only scope. | **Example** ```json { "connectionType": "CONNECTION_TYPE_LAUNCHDARKLY", "displayName": "Plaid LD", "description": "Correlate feature-flag changes with deploy windows", "connectionDetails": { "launchdarkly": { "apiToken": "api-...", "projectKeys": ["default", "billing"], "flagEnvToDeployEnv": { "production": "prod-eu" } } } } ``` ### Devin Connection Details Connect to [Devin](https://devin.ai), Cognition's cloud coding agent, to launch sessions that fix issues and open pull requests. Sessions are created via Devin's v3 organization-scoped API, so the connection authenticates with a **service-user** API key — legacy personal (`apk_`) keys are not supported. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `api_key` | string | INPUT_ONLY, REQUIRED | Devin service-user API key (`cog_...`). Create one in Devin under **Settings → Service users** with the **ManageOrgSessions** and **ViewOrgSessions** permissions. | | `org_id` | string | Optional | Devin organization ID (`org-...`) the sessions are created in. When left empty, create-time validation discovers it via `GET /v3/self` and persists it — that lookup needs the **ReadAccountMeta** permission, so keys without it must supply the org ID explicitly (shown on the same Settings → Service users page as the key). Always non-empty on stored connections. | | `default_repository` | string | Optional | Default GitHub repository (e.g. `https://github.com/owner/repo`) used to seed the Devin session when the issue carries no GitHub link. A GitHub link on the issue takes precedence. Leave empty to require a link on every issue. | | `max_acu_limit` | int32 | Optional | Per-session ACU spend cap forwarded to Devin's create-session `max_acu_limit`. 0 means no cap. A guardrail worth setting when [Autofix](../autofix.txt) dispatches sessions automatically. | The API key (and organization ID, when supplied) are validated against Devin at create/update time, so bad credentials are rejected at save time. When updating a connection, omit `api_key` to keep the existing value. **Example** ```json { "connectionType": "CONNECTION_TYPE_DEVIN", "displayName": "Devin", "description": "Launch Devin cloud coding-agent sessions on issues", "connectionDetails": { "devin": { "apiKey": "cog_...", "orgId": "org-...", "defaultRepository": "https://github.com/owner/repo", "maxAcuLimit": 10 } } } ``` ### GitHub Connection Details GitHub connections use GitHub App installation tokens for authentication. The installation token is resolved server-side and is not returned in API responses. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `installation_id` | int64 | REQUIRED | GitHub App installation ID. The installation must be owned by the deployment creating this connection. | | `owner` | string | | GitHub account (org or user login) that owns this installation. Set automatically at connection creation. | | `auto_create_deployments` | enum | | `AUTO_CREATE_DEPLOYMENTS_ENABLED` (default) or `AUTO_CREATE_DEPLOYMENTS_DISABLED`. Controls whether GitHub deployment webhook events automatically create Deployment resources. | | `auto_monitor_pull_requests` | enum | | `AUTO_MONITOR_PULL_REQUESTS_DISABLED` (default) or `AUTO_MONITOR_PULL_REQUESTS_ENABLED`. When enabled, opened PR webhook events automatically create a deploy-monitoring agent. Requires `auto_monitor_pr_author_scope` to be set — enabling this without a scope is rejected. | | `auto_monitor_pr_author_scope` | enum | | `AUTO_MONITOR_PR_AUTHOR_SCOPE_SELECTED` or `AUTO_MONITOR_PR_AUTHOR_SCOPE_EVERYONE`. Required when `auto_monitor_pull_requests` is `ENABLED`. `SELECTED` monitors only the authors in `auto_monitor_pr_authors`; `EVERYONE` monitors all PR authors. `AUTO_MONITOR_PR_AUTHOR_SCOPE_UNSPECIFIED` (default) monitors nobody — "everyone" must be chosen explicitly. | | `auto_monitor_pr_filter` | string | | Optional natural-language filter applied to opened PRs when `auto_monitor_pull_requests` is enabled. Non-matching PRs get a skip comment instead of a monitor. | | `auto_monitor_pr_authors` | repeated string | | GitHub username allowlist used when `auto_monitor_pr_author_scope` is `SELECTED`. Only PRs from listed authors are monitored; an empty list under `SELECTED` monitors nobody. Ignored when scope is `EVERYONE`. | | `post_updates_to_github` | enum | | `POST_UPDATES_TO_GITHUB_ENABLED` (default) or `POST_UPDATES_TO_GITHUB_DISABLED`. When disabled, change-monitoring updates (plan invites, plan publishes, status updates, skip explanations) stay in the Firetiger UI and are not posted as comments on the originating pull request. | | `monitor_new_environments` | enum | | `MONITOR_NEW_ENVIRONMENTS_ENABLED` (default) or `MONITOR_NEW_ENVIRONMENTS_DISABLED`. Controls whether a deployment environment discovered for the first time starts out monitored. When disabled, a newly seen environment is recorded with monitoring off and must be enabled explicitly before deploys to it activate monitoring plans. Applies only at discovery — environments that already exist keep their own `monitoring_enabled` value. | ## Shell Environment Shell prompt information for the connection, populated only on Get requests. | Field | Type | Description | |:------|:-----|:------------| | `prompt` | string | Shell prompt text for interactive sessions | ## Status Operational status of the connection's credential resolution, following the [google.rpc.Status](https://cloud.google.com/apis/design/errors#error_model) format. | Field | Type | Description | |:------|:-----|:------------| | `code` | integer | Status code (0 means OK) | | `message` | string | Human-readable error message (empty when healthy) | ### Customer | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`customers/{customer_id}`) | | `external_id` | string | REQUIRED | Identifier from your telemetry system, used to filter this customer's data in logs. Examples: `"acme-corp"`, `"b81cb7725908"` | | `id` | string | OUTPUT_ONLY | The `{customer_id}` portion of the resource name (SHA-256 hash of `external_id`) | | `display_name` | string | | Human-readable name for the customer | | `description` | string | | Description of the customer and their use case | | `runbook` | string | | Instructions for investigating issues with this customer | | `workload_summary` | string | | AI-generated summary of the customer's workload patterns | | `important` | boolean | | Whether this customer is marked as important. Issues for important customers are surfaced higher in the active issues list | | `create_time` | timestamp | OUTPUT_ONLY | When the customer was created | | `update_time` | timestamp | OUTPUT_ONLY | When the customer was last modified | | `delete_time` | timestamp | OUTPUT_ONLY | When the customer was soft-deleted (null if active) | | `purge_time` | timestamp | OUTPUT_ONLY | Earliest time the customer can be permanently purged | **Example** ```json { "name": "customers/cso47xax", "externalId": "acme-corp", "displayName": "Acme Corp", "description": "Enterprise customer with high request volume", "workloadSummary": "High throughput tenant with 835K requests/6h (~39 req/s). Stable latency profile.", "createTime": "2025-10-10T18:07:55.847133Z", "updateTime": "2025-10-14T18:14:29.993442Z" } ``` ### Deployment | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`deployments/{id}`) | | `source` | [DeploymentSource](#deployment-source) | | Origin system that created this deployment event | | `status` | [DeploymentStatus](#deployment-status) | | Current status of the deployment (denormalized from latest status event) | | `labels` | map\ | | Flexible key-value dimensions for categorizing deployments | | `create_time` | timestamp | OUTPUT_ONLY | When the deployment record was created | | `update_time` | timestamp | OUTPUT_ONLY | When the deployment record was last updated | | `delete_time` | timestamp | OUTPUT_ONLY | When the deployment was soft-deleted (null if active) | | `start_time` | timestamp | | When the deployment actually started executing | | `complete_time` | timestamp | | When the deployment completed (success, failure, or error) | | `external_id` | string | | External identifier from the source system (e.g. GitHub deployment ID) | | `external_url` | string | | URL to view this deployment in the source system | | `description` | string | | Human-readable description or title of the deployment | | `monitoring_processed_time` | timestamp | OUTPUT_ONLY | When the post-deploy monitoring hook last processed this deployment; the forward-delta activation watermark (null until processed) | **Example** ```json { "name": "deployments/8981264b-cf59-434c-95a0-1ec58b502226", "source": "DEPLOYMENT_SOURCE_GITHUB", "status": "DEPLOYMENT_STATUS_SUCCESS", "labels": { "deployer": "octocat", "environment": "production", "ref": "main", "repository": "acme/backend", "sha": "acb26d1df10035fdde98ceb7f29d0e9276367fcb" }, "createTime": "2026-02-13T01:04:29.873715Z", "updateTime": "2026-02-13T01:08:40.953822Z", "startTime": "2026-02-13T01:04:28Z", "completeTime": "2026-02-13T01:08:39Z", "externalId": "gh-3831677370", "externalUrl": "https://github.com/acme/backend/deployments" } ``` Common labels auto-extracted from GitHub webhooks: | Label | Example | Description | |:------|:--------|:------------| | `repository` | `acme-corp/backend` | Repository full name | | `environment` | `production` | Deployment environment | | `ref` | `main` | Git reference (branch or tag) | | `sha` | `abc123...` | Commit SHA being deployed | | `deployer` | `octocat` | User who triggered the deployment | ## Deployment Source | Value | Description | |:------|:------------| | `DEPLOYMENT_SOURCE_UNSPECIFIED` | Default value, not used | | `DEPLOYMENT_SOURCE_GITHUB` | Deployment originated from GitHub | | `DEPLOYMENT_SOURCE_MANUAL` | Deployment was recorded manually | ## Deployment Status | Value | Description | |:------|:------------| | `DEPLOYMENT_STATUS_UNSPECIFIED` | Default value, not used | | `DEPLOYMENT_STATUS_PENDING` | Deployment is pending | | `DEPLOYMENT_STATUS_QUEUED` | Deployment is queued | | `DEPLOYMENT_STATUS_IN_PROGRESS` | Deployment is currently executing | | `DEPLOYMENT_STATUS_SUCCESS` | Deployment completed successfully | | `DEPLOYMENT_STATUS_FAILED` | Deployment failed | | `DEPLOYMENT_STATUS_ERROR` | Deployment encountered an error | | `DEPLOYMENT_STATUS_INACTIVE` | Deployment is inactive | ## Deployment Status Event **Resource name pattern**: `deployments/{deployment_id}/status-events/{status_event_id}` | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`deployments/{id}/status-events/{id}`) | | `deployment` | string | REQUIRED | Parent deployment resource name | | `status` | [DeploymentStatus](#deployment-status) | | The status at this point in time | | `description` | string | | Context for this status change | | `event_time` | timestamp | | When this status was recorded in the source system | | `external_url` | string | | URL to view this status event in the source system | | `create_time` | timestamp | OUTPUT_ONLY | When this status event record was created | | `update_time` | timestamp | OUTPUT_ONLY | When this status event record was last updated | | `delete_time` | timestamp | OUTPUT_ONLY | When this status event was soft-deleted (null if active) | --- ## Deployment Monitor Evaluation | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`deployment-monitor-evaluations/{id}`) | | `create_time` | timestamp | OUTPUT_ONLY | When the evaluation was created | | `update_time` | timestamp | OUTPUT_ONLY | When the evaluation was last updated | | `delete_time` | timestamp | OUTPUT_ONLY | When the evaluation was soft-deleted (null if active) | | `session_name` | string | | Reference to the agent session running this evaluation (`agents/{agent}/sessions/{session}`) | | `status` | [DeploymentMonitorEvaluationStatus](#deployment-monitor-evaluation-status) | | Current status of the evaluation | | `github_run_id` | integer | | GitHub Actions workflow run ID that triggered the deployment | | `environment` | string | | The environment being deployed to (e.g. `production`) | | `repository` | string | | Repository in `owner/name` format (e.g. `acme-corp/backend`) | | `head_sha` | string | | The commit SHA being deployed | | `base_sha` | string | | The base commit SHA (previous deployment) | | `pr_number` | integer | | PR number if this deployment is from a PR merge (0 if not) | | `deployment_time` | timestamp | | When the deployment started | | `summary` | string | | Summary of the monitoring period written by the agent on completion | | `overall_status` | string | | Overall outcome: `successful`, `warning`, or `incident` | ## Deployment Monitor Evaluation Status | Value | Description | |:------|:------------| | `DEPLOYMENT_MONITOR_EVALUATION_STATUS_UNSPECIFIED` | Default value, not used | | `DEPLOYMENT_MONITOR_EVALUATION_STATUS_RUNNING` | Evaluation is currently running | | `DEPLOYMENT_MONITOR_EVALUATION_STATUS_COMPLETED` | Evaluation has completed | ### Investigation | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`investigations/{id}`) | | `display_name` | string | | Human-readable display name for the investigation | | `description` | string | | Problem statement describing what to investigate | | `status` | [InvestigationStatus](#investigation-status) | OUTPUT_ONLY | Current execution status (set by the server) | | `created_by` | string | OUTPUT_ONLY | Authenticated user identifier | | `create_time` | timestamp | OUTPUT_ONLY | When the investigation was created | | `update_time` | timestamp | OUTPUT_ONLY | When the investigation was last modified | | `delete_time` | timestamp | OUTPUT_ONLY | When the investigation was soft-deleted (null if active) | **Example** ```json { "name": "investigations/00077c084e17b0ec026a4b713a0f0dbc", "displayName": "User Login Error Investigation", "description": "A user is reporting they cannot log in to the UI. Check for authentication errors in recent logs.", "status": "INVESTIGATION_STATUS_EXECUTING", "createdBy": "user_2xK9mBqHn1pL4vR7wT3eYjZ8aFd", "createTime": "2025-12-15T16:33:31.693279Z", "updateTime": "2025-12-15T16:33:31.693279Z" } ``` ## Investigation Status | Value | Description | |:------|:------------| | `INVESTIGATION_STATUS_UNSPECIFIED` | Default value, not used | | `INVESTIGATION_STATUS_EXECUTING` | Investigation is actively executing | | `INVESTIGATION_STATUS_WAITING` | Investigation is waiting | ### Issue | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`issues/{id}`) | | `title` | string | | Short title for the issue | | `description` | string | | Detailed description of the detected issue | | `details` | string | | Extended details and context | | `workflow_state` | [IssueWorkflowState](#issue-workflow-state) | | Current workflow state | | `closure` | [IssueClosure](#issue-closure) | | Closure details when the issue is resolved or dismissed | | `expert_session` | string | | Agent session investigating this issue | | `observations` | [IssueObservations](#issue-observations) | | Observation timestamps and counts | | `tags` | string[] | | Tag resource names (`tags/{tag}`) | | `deployment_environments` | string[] | | Deployment environment resource names (`deployment-environments/{environment}`) affected by the issue | | `services` | string[] | | Service resource names (`services/{service}`) this issue belongs to. Set by the triage agent so the issue is discoverable from (and dedup-able per) its Service. | | `objectives` | string[] | | Objective resource names (`objectives/{objective}`) this issue belongs to. Set by the triage agent on an objective-breach escalation so the issue is discoverable from (and dedup-able per) its Objective. | | `links` | string[] | | Related links | | `pull_requests` | string[] | | GitHub PR URLs linked to this issue as fix attempts | | `source` | string | | Canonical issue lineage. Can be an agent (`agents/my-agent`), an agent session (`agents/my-agent/sessions/abc`), an investigation (`investigations/xyz`), a deployment environment (`deployment-environments/firetiger-cloud`), or an external URL (`https://incident.io/incident/123`) | | `assignee` | string | | Resource name (`users/{user}`) of the Firetiger user responsible for this issue, resolved automatically at creation from `source` (PR author for change-monitor issues, agent creator for agent issues). Empty when no responsible user can be resolved (investigations, external-URL issues, system agents, API-key agents). Filterable, e.g. `assignee = "users/{user}"` | | `create_time` | timestamp | OUTPUT_ONLY | When the issue was created | | `update_time` | timestamp | OUTPUT_ONLY | When the issue was last modified | | `delete_time` | timestamp | OUTPUT_ONLY | When the issue was soft-deleted (null if active) | **Example** ```json { "name": "issues/db80f50ddbf7", "title": "S3 Timeout Errors Escalating", "description": "datafile-optimizer-plan experiencing 2,839 errors/hour (+22.3% from previous hour), representing 71.2% of planner errors. Errors include 'context canceled' and 'operation error S3: HeadObject/GetObject, context deadline exceeded'.", "source": "deployment-environments/firetiger-cloud", "assignee": "users/user-2abc", "deploymentEnvironments": [ "deployment-environments/firetiger-cloud", "deployment-environments/ft-ramp" ], "createTime": "2026-02-13T01:04:27.924994Z", "updateTime": "2026-02-13T01:04:27.924994Z" } ``` --- ## Issue Workflow State | Value | Description | |:------|:------------| | `ISSUE_WORKFLOW_STATE_UNSPECIFIED` | Default value, not used | | `ISSUE_WORKFLOW_STATE_INVESTIGATING` | Issue is being investigated | | `ISSUE_WORKFLOW_STATE_ACTIONABLE` | Issue requires action | | `ISSUE_WORKFLOW_STATE_VERIFYING_FIX` | A fix has been deployed and is being verified | | `ISSUE_WORKFLOW_STATE_CLOSED` | Issue is closed | --- ## Issue Closure | Field | Type | Description | |:------|:-----|:------------| | `reason` | [IssueCloseReason](#issue-close-reason) | Why the issue was closed | | `dismissal` | [IssueDismissal](#issue-dismissal) | Dismissal details | --- ## Issue Close Reason | Value | Description | |:------|:------------| | `ISSUE_CLOSE_REASON_UNSPECIFIED` | Default value, not used | | `ISSUE_CLOSE_REASON_RESOLVED` | The underlying problem was fixed | | `ISSUE_CLOSE_REASON_ACCEPTED_RISK` | The issue is acknowledged but accepted | | `ISSUE_CLOSE_REASON_FALSE_POSITIVE` | The issue was incorrectly identified | | `ISSUE_CLOSE_REASON_DUPLICATE` | Duplicate of another issue | | `ISSUE_CLOSE_REASON_NOT_USEFUL` | The issue is not useful | --- ## Issue Dismissal | Field | Type | Description | |:------|:-----|:------------| | `comment` | string | Reason for dismissal | | `dismissed_by` | string | Who dismissed the issue | | `dismissed_time` | timestamp | When the issue was dismissed | --- ## Issue Observations | Field | Type | Description | |:------|:-----|:------------| | `first_observation_time` | timestamp | When the issue was first observed | | `last_observation_time` | timestamp | When the issue was last observed | | `observation_count` | int32 | Number of times the issue has been observed | | `first_source_session` | string | Session that first observed the issue | | `last_source_session` | string | Session that most recently observed the issue | --- ## Validate Issue Evidence Response | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `decision` | [IssueEvidenceValidationDecision](#issue-evidence-validation-decision) | | Validation decision for the proposed issues | | `passes` | boolean | | Convenience boolean. True for `ISSUE_EVIDENCE_VALIDATION_DECISION_PASS` and `ISSUE_EVIDENCE_VALIDATION_DECISION_SKIPPED`; false only for `ISSUE_EVIDENCE_VALIDATION_DECISION_FAIL` | | `feedback` | string | | Human-readable validation feedback | | `findings` | [IssueEvidenceValidationFinding](#issue-evidence-validation-finding)[] | | Per-issue validation findings | | `validator_version` | string | | Validator version that produced the response | **Example** ```json { "decision": "ISSUE_EVIDENCE_VALIDATION_DECISION_FAIL", "passes": false, "feedback": "Missing same-window baseline for the temporal anomaly claim.", "findings": [ { "issueRef": "Checkout API errors increased", "feedback": "The evidence compares against yesterday, but does not include a same-day historical baseline." } ], "validatorVersion": "issue-evidence-v1" } ``` --- ## Issue Evidence Validation Decision | Value | Description | |:------|:------------| | `ISSUE_EVIDENCE_VALIDATION_DECISION_UNSPECIFIED` | Default value, not used | | `ISSUE_EVIDENCE_VALIDATION_DECISION_PASS` | Every proposed issue is supported by the supplied evidence | | `ISSUE_EVIDENCE_VALIDATION_DECISION_FAIL` | At least one proposed issue lacks sufficient supporting evidence | | `ISSUE_EVIDENCE_VALIDATION_DECISION_SKIPPED` | Validation was not configured or could not complete; callers should treat this as fail-open unless they require stricter policy | --- ## Issue Evidence Validation Finding | Field | Type | Description | |:------|:-----|:------------| | `issue_ref` | string | Issue name or title that the finding applies to | | `feedback` | string | Issue-specific validation feedback | --- ## Issue Notification Policy | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`issue-notification-policies/{id}`) | | `description` | string | | Human-readable description of the policy | | `connections` | string[] | | List of connection resource names (`connections/{id}`) used for sending notifications | | `prompt` | string | | Instructions for the notification agent defining routing rules, channel selection, and formatting preferences | | `state` | [IssueNotificationPolicyState](#issue-notification-policy-state) | | Current state of the policy | | `review_reason` | string | | Explanation of why the policy needs review (populated when state is `REVIEW_REQUIRED`) | | `planner_session` | string | | Agent session managing the notification planner | | `create_time` | timestamp | OUTPUT_ONLY | When the policy was created | | `update_time` | timestamp | OUTPUT_ONLY | When the policy was last modified | | `delete_time` | timestamp | OUTPUT_ONLY | When the policy was soft-deleted (null if active) | | `purge_time` | timestamp | OUTPUT_ONLY | When the policy is scheduled for permanent deletion | **Example** ```json { "name": "issue-notification-policies/default", "description": "Send notifications to Slack (#alerts) and create Linear issues for all detected issues.", "connections": [ "connections/slack-workspace", "connections/linear-project" ], "prompt": "When an issue is created or updated, send notifications using both channels...", "state": "ISSUE_NOTIFICATION_POLICY_STATE_ACTIVE", "plannerSession": "agents/issue-notification-planner-agent/sessions/ses-def456", "createTime": "2026-02-28T01:09:28.105790Z", "updateTime": "2026-03-04T01:34:50.035946Z" } ``` ## Issue Notification Policy State | Value | Description | |:------|:------------| | `ISSUE_NOTIFICATION_POLICY_STATE_UNSPECIFIED` | Default value, not used | | `ISSUE_NOTIFICATION_POLICY_STATE_ACTIVE` | Policy is active and will send notifications when issues are detected | | `ISSUE_NOTIFICATION_POLICY_STATE_REVIEW_REQUIRED` | Policy needs human review before it will send notifications | ### DeploymentEnvironment An environment that deployments target (e.g. `production`, `staging`). Environments are auto-created the first time a deployment references them, and carry whether change monitoring is enabled plus a short human-authored description. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`deployment-environments/{environment}`) | | `monitoring_enabled` | bool | | Whether deployment monitoring is enabled for this environment. Defaults to `true` when auto-created | | `description` | string | | Short, human-authored description of what this environment is (e.g. "Live customer traffic" or "Pre-prod mirror, safe to ignore for alerting"). Empty until set | | `create_time` | timestamp | OUTPUT_ONLY | When the environment record was created | | `update_time` | timestamp | OUTPUT_ONLY | When the environment record was last updated | | `delete_time` | timestamp | OUTPUT_ONLY | When the environment was soft-deleted (null if active) | **Example** ```json { "name": "deployment-environments/production", "monitoringEnabled": true, "description": "Live customer traffic.", "createTime": "2026-02-13T01:04:29.873715Z", "updateTime": "2026-06-26T22:40:00.000000Z" } ``` ### Monitoring Plan | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`monitoring-plans/{id}`) | | `origin` | [GithubPrOrigin](#github-pr-origin) | | Metadata about the GitHub PR that triggered this plan | | `activation` | [ActivationTrigger](#activation-trigger) | | Activation trigger configuration including the merge commit SHA | | `plan_content` | string | | Prose document describing intended effect, unintended effects to watch for, and risk assessment | | `plan_summary` | string | | One-sentence plain-text summary of the plan (~150 characters max) | | `notification_channel` | string | | Notification channel for alerts (e.g., Slack channel name) | | `author_session` | string | | Reference to the author agent session (`agents/{agent}/sessions/{session}`) | | `create_time` | timestamp | OUTPUT_ONLY | When the plan was created | | `update_time` | timestamp | OUTPUT_ONLY | When the plan was last modified | | `delete_time` | timestamp | OUTPUT_ONLY | When the plan was soft-deleted (null if active) | | `deployments` | [MonitoredDeployment](#monitored-deployment)[] | | Per-environment deployment monitoring state | | `last_check_time` | timestamp | | Timestamp of the most recent scheduler check across all environments | | `related_resources` | string[] | | Internal list of related resources discovered during planning | | `agent_name` | string | | Reference to the agent that owns this monitoring plan (`agents/{agent}`) | | `agent_archived` | boolean | | Whether the associated agent has been archived after monitoring completed | | `conversation` | [Conversation](#conversation) | | Persistent communication anchor for Change Monitor updates about this PR. Populated the first time a notification is delivered; subsequent updates post into the same anchor instead of producing fresh top-level messages. Stitched forward across plan recreations for the same PR URL. | | `deployment_expectation` | [DeploymentExpectation](#deployment-expectation) | | Tracks whether the change this plan watches actually shipped. Distinct from `deployments` (which only exists once a deploy succeeds): models the shipment expectation so a deploy that fails is a first-class, reportable state. Set at merge; updated by the deploy success/failure hooks. | | `intended_effects` | string[] | | Intended effects of the change as discrete bullets — what it should accomplish and the signal that confirms it. The structured, queryable form of the effects described in `plan_content`; written at publish time and reflecting the most recent publish. | | `risks` | string[] | | Named risks to watch for as discrete bullets, each pairing a failure mode with its signal and alert condition. Structured companion to `intended_effects`; written at publish time and reflecting the most recent publish. | **Example** ```json { "name": "monitoring-plans/f045b69d-8b64-43fb-9601-0d3118772da9", "origin": { "repository": "acme/backend", "prNumber": 3751, "prUrl": "https://github.com/acme/backend/pull/3751", "prTitle": "feat(ui): add support link to app", "prAuthorGithubLogin": "octocat", "prAuthorGithubUserId": "1234567" }, "activation": { "mergeSha": "17078c805a9fb991a09283f41b5c29ce70269028" }, "planContent": "# Monitoring Plan: Add Support Link\n\n## Risk Assessment: MINIMAL\nFrontend-only change...", "planSummary": "Add support mailto link to user dropdown menu - minimal risk frontend change", "intendedEffects": [ "Support link renders in the user dropdown for all users (UI render count > 0 post-deploy)." ], "risks": [ "Broken mailto target: watch client error logs for support-link clicks; alert on any 4xx/5xx." ], "authorSession": "agents/plan-author/sessions/ses-abc123", "createTime": "2026-02-17T22:00:01.717128Z", "updateTime": "2026-02-27T21:11:22.041370Z", "agentName": "agents/dm-f045b69d", "deployments": [ { "environment": "production", "deployment": "deployments/2cdaaa4f-3baa-4aa8-8cd9-74b3f663c5ef", "deployTime": "2026-02-17T22:18:04Z", "intendedEffectConfirmed": true, "outcome": "MONITORING_OUTCOME_NO_ISSUE", "completeTime": "2026-02-18T01:46:06.188498Z" } ], "deploymentExpectation": { "status": "STATUS_DEPLOYED", "mergeTime": "2026-02-17T22:05:11Z", "resolvedTime": "2026-02-17T22:18:04Z" }, "agentArchived": true } ``` ## GitHub PR Origin | Field | Type | Description | |:------|:-----|:------------| | `repository` | string | Repository in org/repo format (e.g., `acme-corp/backend`) | | `pr_number` | integer | Pull request number | | `pr_url` | string | Full URL to the PR (used as deduplication key) | | `head_sha` | string | PR HEAD SHA at plan creation time | | `installation_id` | integer | GitHub App installation ID | | `pr_title` | string | PR title at plan creation time | | `pr_author_github_login` | string | GitHub login of the PR author | | `pr_author_github_user_id` | string | Stable GitHub user ID of the PR author | | `pr_in_progress_reaction_id` | integer | GitHub reaction ID for the in-progress reaction Firetiger posts on the PR description while building the change-monitoring plan. Zero when no reaction is currently outstanding. Cleared after the plan comment is posted. | | `pr_in_progress_comment_id` | integer | GitHub comment ID of the "Firetiger is working on a monitoring plan for this PR" placeholder comment, edited in place into the "has created a monitoring plan" comment when the plan is published. Zero when no placeholder is outstanding. | ## Activation Trigger | Field | Type | Description | |:------|:-----|:------------| | `merge_sha` | string | Merge commit SHA to watch for. Set when the PR merges; empty while PR is open. | | `environments` | string[] | Only activate for these environments (e.g., `["production"]`). If empty, activates for all. | ## Monitored Deployment | Field | Type | Description | |:------|:-----|:------------| | `environment` | string | The environment being monitored (e.g., `staging`, `production`) | | `deployment` | string | Reference to the deployment resource (`deployments/{deployment}`) | | `deploy_time` | timestamp | Deploy time from the Deployment resource | | `intended_effect_confirmed` | boolean | Whether the intended effect has been confirmed for this environment. Cleared if an issue is later detected, so a deployment is never both confirmed and issue-detected | | `outcome` | [MonitoringOutcome](#monitoring-outcome) | Outcome of monitoring for this environment | | `issue` | string | Reference to the issue if outcome is `ISSUE_DETECTED` (`issues/{id}`) | | `complete_time` | timestamp | When monitoring for this environment completed | | `last_checked_at` | timestamp | When the scheduler last fired a checkpoint check that observed this deployment | | `status_summary` | string | Super-short, human-readable one-liner companion to the status, set when the intended effect is confirmed or an issue is reported (e.g. `API p99 down 20%`, `5xx rate up on /checkout`). Empty until a confirmed/issue verdict is recorded | | `monitoring_complete_eta` | timestamp | Output-only. Latest time monitoring may continue for this deployment before the scheduler force-completes it. Derived at read time as `deploy_time + 73h` (the scheduler's per-deployment monitoring window); not persisted. Empty when `deploy_time` is unset | ## Monitoring Outcome | Value | Description | |:------|:------------| | `MONITORING_OUTCOME_UNSPECIFIED` | Default value, not used | | `MONITORING_OUTCOME_NO_ISSUE` | No issues detected during monitoring | | `MONITORING_OUTCOME_ISSUE_DETECTED` | An issue was detected during monitoring | ## Deployment Expectation Tracks whether the change a MonitoringPlan watches actually shipped. A merged PR is expected to deploy; if the deploy fails (a `deployment_status` failure/error correlated to the merge SHA) the change may be in a partially-degraded state that Change Monitor surfaces — even though no [MonitoredDeployment](#monitored-deployment) is created for a failed deploy. | Field | Type | Description | |:------|:-----|:------------| | `status` | [DeploymentExpectationStatus](#deployment-expectation-status) | Current shipment status | | `merge_time` | timestamp | When the PR merged (clock anchor for "merged X ago") | | `resolved_time` | timestamp | When the expectation reached a terminal status (`DEPLOYED` or `DEPLOY_FAILED`) | | `detail` | string | Human-readable detail about a failure (GitHub state + deploy logs URL) | | `reported_issue` | string | Reference to the Issue filed for a failure (`issues/{id}`) | | `reported_time` | timestamp | When the failure report fired (set exactly once) | ## Deployment Expectation Status | Value | Description | |:------|:------------| | `STATUS_UNSPECIFIED` | Merged and waiting for a deploy (or PR not yet merged) | | `STATUS_DEPLOYED` | At least one deployment activated for this plan | | `STATUS_DEPLOY_FAILED` | A deployment failure/error was correlated to this merge SHA | | `STATUS_DEPLOY_TIMED_OUT` | Reserved: the deploy window elapsed with no deploy observed (not currently set) | | `STATUS_WAIVED` | Reserved: the deploy was determined not to be expected (not currently set) | ## Conversation Persistent communication anchor for Change Monitor updates about a PR. Wraps the channel-specific anchor in a `oneof` so additional destinations (email, Linear, etc.) can be added without renaming the existing JSON fields stored in protodb. | Field | Type | Description | |:------|:-----|:------------| | `slack` | [SlackThread](#slack-thread) | Slack DM thread the PR author is participating in (oneof `channel`) | ## Slack Thread Anchor identifying a Slack thread for Change Monitor DMs. | Field | Type | Description | |:------|:-----|:------------| | `channel_id` | string | Slack channel ID (the DM channel for the recipient) | | `thread_ts` | string | Slack message timestamp of the thread's top-level message | | `slack_connection` | string | Slack connection resource that created the thread (`connections/{connection}`) | | `status_reaction_emoji` | string | Emoji name (no colons, e.g. `white_check_mark`) of the status reaction currently applied to the top-level DM message; empty when none is applied | --- ## Monitoring Run > MonitoringRunService is a legacy service. New monitoring state is stored directly on MonitoringPlan.deployments. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`monitoring-plans/{plan}/runs/{run}`) | | `environment` | string | REQUIRED | The environment being monitored (e.g., `staging`, `production`) | | `deployment` | string | | Reference to the deployment that triggered this run (`deployments/{deployment}`) | | `status` | [MonitoringRunStatus](#monitoring-run-status) | | Current status of the monitoring run | | `outcome` | [MonitoringRunOutcome](#monitoring-run-outcome) | | Outcome of the monitoring run (set when status is COMPLETED or TIMED_OUT) | | `issue` | string | | Reference to the issue if outcome is `ISSUE_DETECTED` (`issues/{id}`) | | `intended_effect_confirmed` | boolean | | Whether the intended effect has been confirmed | | `activate_time` | timestamp | | When the monitoring run was activated (deployment detected) | | `complete_time` | timestamp | | When the monitoring run completed | | `last_check_time` | timestamp | | When the last monitoring check ran | | `create_time` | timestamp | OUTPUT_ONLY | When the run was created | | `update_time` | timestamp | OUTPUT_ONLY | When the run was last modified | | `delete_time` | timestamp | OUTPUT_ONLY | When the run was soft-deleted (null if active) | ## Monitoring Run Status | Value | Description | |:------|:------------| | `MONITORING_RUN_STATUS_UNSPECIFIED` | Default value, not used | | `MONITORING_RUN_STATUS_RUNNING` | Monitoring is actively running | | `MONITORING_RUN_STATUS_COMPLETED` | Monitoring has completed | | `MONITORING_RUN_STATUS_TIMED_OUT` | Monitoring timed out before completing | ## Monitoring Run Outcome | Value | Description | |:------|:------------| | `MONITORING_RUN_OUTCOME_UNSPECIFIED` | Default value, not used | | `MONITORING_RUN_OUTCOME_NO_ISSUE` | No issues detected during the run | | `MONITORING_RUN_OUTCOME_ISSUE_DETECTED` | An issue was detected during the run | ### Note | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`notes/{id}`) | | `display_name` | string | | Human-readable name for the note | | `notes` | string | | Free-form note content | | `description` | string | | Additional description or summary | | `tool_name` | string | | Name of the tool that produced this note | | `tool_args` | object | | Arguments passed to the tool (JSON object) | | `create_time` | timestamp | OUTPUT_ONLY | When the note was created | | `update_time` | timestamp | OUTPUT_ONLY | When the note was last modified | | `delete_time` | timestamp | OUTPUT_ONLY | When the note was soft-deleted (null if active) | **Example** ```json { "name": "notes/c858d27932ef", "displayName": "Check CLS Threshold Violations", "description": "Query to detect CLS values exceeding the 0.1 threshold in the last hour from browser logs.", "toolName": "query", "toolArgs": { "sql": "SELECT time, attributes.page.path as page_path, CAST(attributes.web_vital.value AS DOUBLE) as cls_score FROM \"opentelemetry/logs/browser\" WHERE time >= CURRENT_TIMESTAMP - INTERVAL '1 hour' AND attributes.web_vital.name = 'CLS' AND attributes.web_vital.value IS NOT NULL" }, "createTime": "2025-12-02T19:26:03.311886Z", "updateTime": "2026-01-21T23:24:41.583126Z" } ``` ### Runbook | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`runbooks/{id}`) | | `display_name` | string | | Human-readable name for the runbook | | `description` | string | | Short description of what the runbook is for | | `text` | string | | The full runbook content with instructions for the agent | | `connections` | [RunbookConnection](#runbook-connection)[] | | Connections and tools available to the agent while executing this runbook | | `session_name` | string | | Agent session that created or manages this runbook (`agents/{agent}/sessions/{session}`) | | `create_time` | timestamp | OUTPUT_ONLY | When the runbook was created | | `update_time` | timestamp | OUTPUT_ONLY | When the runbook was last modified | | `delete_time` | timestamp | OUTPUT_ONLY | When the runbook was soft-deleted (null if active) | **Example** ```json { "name": "runbooks/high-error-rate-triage", "displayName": "High Error Rate Triage", "description": "Steps for triaging elevated error rates on production services", "text": "1. Query the database for recent deployments.\n2. Check HTTP error rates via Prometheus.\n3. If a recent deploy correlates, escalate to the owning team.", "connections": [ { "name": "connections/prod-postgres", "enabledTools": ["TOOL_POSTGRES_QUERY"] }, { "name": "connections/prod-prometheus", "enabledTools": ["TOOL_PROMQL_QUERY", "TOOL_PROMQL_QUERY_RANGE"] } ], "createTime": "2024-08-10T09:00:00Z", "updateTime": "2024-08-10T09:00:00Z" } ``` ## Runbook Connection | Field | Type | Description | |:------|:-----|:------------| | `name` | string | Resource name of the connection (`connections/{id}`) | | `enabled_tools` | [Tool](connection.txt#tool)[] | Which tools from this connection the agent may use (e.g. `TOOL_POSTGRES_QUERY`, `TOOL_HTTP_REQUEST`) | ### Tag | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`tags/{id}`) | | `display_name` | string | REQUIRED | Human-readable name shown in the UI | | `description` | string | | Optional description of the tag | | `color` | string | | Hex color string (e.g., `#5E6AD2`). Ignored for system tags. | | `system` | boolean | OUTPUT_ONLY | True for `firetiger:*` tags (set by server) | | `create_time` | timestamp | OUTPUT_ONLY | When the tag was created | | `update_time` | timestamp | OUTPUT_ONLY | When the tag was last modified | | `delete_time` | timestamp | OUTPUT_ONLY | When the tag was soft-deleted (null if active) | **Example** ```json { "name": "tags/production", "displayName": "Production", "description": "Agents monitoring production systems", "color": "#5E6AD2", "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } ``` ## System tags System tags have names prefixed with `firetiger:` and are created by the system. They: - Have `system` set to `true` - Cannot be created, updated, or deleted via the API - Are always displayed in grey in the UI (the `color` field is ignored) **Example system tag** ```json { "name": "tags/firetiger:monitoring", "displayName": "Monitoring", "system": true, "createTime": "2024-01-01T00:00:00Z", "updateTime": "2024-01-01T00:00:00Z" } ``` ### Trigger | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`triggers/{id}`) | | `display_name` | string | | Human-readable name for the trigger | | `description` | string | | Description of what this trigger does | | `agent` | string | REQUIRED | The agent to create sessions for. Format: `agents/{agent}` | | `configuration` | [TriggerConfiguration](#trigger-configuration) | | Determines trigger type and behavior | | `enabled` | boolean | | Whether the trigger is enabled. Disabled cron triggers are not executed. Manual triggers can always be invoked regardless of this setting. | | `activation_state` | [TriggerActivationState](#trigger-activation-state) | OUTPUT_ONLY | Runtime activation state. Set for post_deploy and row triggers. | | `associated_resources` | string[] | | Resource names this trigger associates with the sessions it spawns (merged into the session's `associated_resources`), so the agent and UI can resolve a session back to its subject — e.g. a resource-scoped trigger can carry `services/{id}`. | | `create_time` | timestamp | OUTPUT_ONLY | When the trigger was created | | `update_time` | timestamp | OUTPUT_ONLY | When the trigger was last modified | | `delete_time` | timestamp | OUTPUT_ONLY | When the trigger was soft-deleted (null if active) | **Example** ```json { "name": "triggers/daily-review", "displayName": "Daily Review", "description": "Runs a scheduled review every morning at 9 AM Eastern", "agent": "agents/reviewer", "configuration": { "cron": { "schedule": "0 9 * * *", "timezone": "America/New_York" } }, "enabled": true, "createTime": "2026-02-06T01:42:31.481530Z", "updateTime": "2026-02-06T01:42:31.481530Z" } ``` ## Trigger Configuration Exactly one of the following fields must be set to determine the trigger type. | Field | Type | Description | |:------|:-----|:------------| | `cron` | [CronTriggerConfig](#cron-trigger-config) | Trigger fires on the cron schedule | | `manual` | [ManualTriggerConfig](#manual-trigger-config) | Trigger can only be manually invoked via InvokeTrigger | | `post_deploy` | [PostDeployTriggerConfig](#post-deploy-trigger-config) | Trigger fires once after a specific commit deploys | | `row` | [RowTriggerConfig](#row-trigger-config) | Trigger fires when a new row matching a predicate is written to a table | | `slack_message_posted` | [SlackMessagePostedTriggerConfig](#slack-message-posted-trigger-config) | Trigger fires when a message is posted to a listed Slack channel | | `slack_agent_mentioned` | [SlackAgentMentionedTriggerConfig](#slack-agent-mentioned-trigger-config) | Trigger fires when a referenced [Slack Handle](./slack-handle.md) is @mentioned | ## Cron Trigger Config | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `schedule` | string | Yes | Standard 5-field cron expression (e.g. `"0 9 * * *"` for daily at 9 AM, `"*/15 * * * *"` for every 15 minutes) | | `timezone` | string | No | IANA timezone for interpreting the schedule (e.g. `"America/New_York"`). Defaults to `"UTC"`. | ## Manual Trigger Config Empty object. Manual triggers have no automatic execution and can only be invoked via InvokeTrigger. ## Post Deploy Trigger Config | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `repository` | string | Yes | GitHub repository in `"owner/repo"` format | | `environment` | string | Yes | Environment label to watch (e.g. `"production"`, `"staging"`) | | `sha` | string | Yes | Commit SHA that must deploy before this trigger fires. Also fires for descendant commits. Immutable after creation. | | `delay` | duration | Yes | How long to wait after the deployment before firing (e.g. `"300s"` for 5 minutes) | ## Row Trigger Config | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `table_name` | string | Yes | Logical table name to watch (e.g. `"opentelemetry/logs/api-server"`). Does not include the organization namespace. | | `predicate` | string | Yes | SQL boolean expression evaluated against each incoming row. Column references are validated against the table schema at creation time. Subqueries and aggregations are not permitted. | | `cooldown` | duration | No | Minimum time between agent session invocations. Subsequent matching rows are suppressed until the cooldown expires. Must be at least 5 minutes if set. Defaults to 15 minutes if omitted. | ## Slack Message Posted Trigger Config | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `slack_connection` | string | Yes | The Slack connection to scope this trigger to (`connections/{id}`) | | `channels` | array of string | Yes | Channel names (with leading `#`) that fire this trigger. Must be channels the Firetiger bot is a member of (it only receives events from channels it has been invited to). | | `include_thread_replies` | bool | No | When true, fires on replies in existing threads as well as top-level messages. When false, only top-level messages. Defaults to `true` when omitted. | Every matching message creates a new agent session — there is no server-side cooldown. Callers that need throttling should handle it upstream or via the agent's tool configuration. ## Slack Agent Mentioned Trigger Config A SlackAgentMentioned trigger is the routing binding between a [Slack Handle](./slack-handle.md) and an agent. The trigger fires when the handle is @mentioned in Slack; `trigger.agent` is the session target and `slack_handle` identifies which handle's mentions fire this trigger. Fan-out to multiple agents is "multiple triggers referencing the same handle." | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `slack_handle` | string | Yes | The Slack handle whose @-mentions fire this trigger (`connections/{connection}/slack-handles/{slack_handle}`). The Slack connection is derived from the handle. | | `channels` | array of string | No | Optional channel scope. When non-empty, restricts firing to these channels (which must be channels the bot is a member of). Empty means any channel the bot is in. | ## Trigger Activation State Output-only state set by the system. Post-deploy triggers set `post_deploy`; row triggers set `row`. Slack triggers are stateless — every matching event creates a new session. | Field | Type | Description | |:------|:-----|:------------| | `post_deploy` | [PostDeployActivationState](#post-deploy-activation-state) | Activation state for post-deploy triggers | | `row` | [RowTriggerActivationState](#row-trigger-activation-state) | Activation state for row triggers | ## Post Deploy Activation State | Field | Type | Description | |:------|:-----|:------------| | `deployment` | string | The deployment that triggered activation (`deployments/{id}`) | | `deployed_sha` | string | The SHA that was actually deployed (may be a descendant of the configured SHA) | | `deploy_time` | timestamp | Time of the deployment | | `fire_at_time` | timestamp | Precomputed time to fire (`deploy_time` + `delay`) | | `fired_time` | timestamp | When a session was created, or null if activated but not yet fired | ## Row Trigger Activation State | Field | Type | Description | |:------|:-----|:------------| | `last_fire_time` | timestamp | The last time this trigger successfully fired an agent session | ### Activity Activities represent individual events in an agent session's conversation history. An Activity is a union type -- exactly one of the following payload fields will be set. | Field | Type | Description | |:------|:-----|:------------| | `user` | [UserActivity](#user-activity) | A message or action from a non-LLM actor (human user, system, or tool results) | | `assistant` | [AssistantActivity](#assistant-activity) | A message or action generated by the LLM | | `error` | [Error](#error) | An error that occurred during execution | | `compaction` | [Compaction](#compaction) | A summarization of earlier conversation history | | `slack_thread` | [SlackThreadActivity](#slack-thread-activity) | A reply in a Slack thread the agent is participating in | | `slack_mention` | [SlackMentionActivity](#slack-mention-activity) | An @-mention of the agent in Slack | | `slack_reaction` | [SlackReactionActivity](#slack-reaction-activity) | An emoji reaction added to an agent-sent message in Slack | | `external_agent` | [ExternalAgentActivity](#external-agent-activity) | A message from another agent | | `task_transition` | [TaskTransition](#task-transition) | A boundary between tasks: a subtask call, a tail call, or a resume after the session waited for the next message | | `artifacts_changed` | [ArtifactsChanged](#artifacts-changed) | The session's artifact set changed (user-initiated PUT/DELETE through the session artifacts endpoint) | | `timestamp` | timestamp | When this activity occurred | ## User Activity A message or action from a non-LLM actor. | Field | Type | Description | |:------|:-----|:------------| | `text` | [Text](#text) | Text content of the user message | | `tool_results` | [ToolResult](#tool-result)[] | Results from tool calls the user (or system) executed | | `author` | string | Author identifier | ## Assistant Activity A message or action generated by the LLM. | Field | Type | Description | |:------|:-----|:------------| | `text` | [Text](#text) | Generated text content | | `tool_calls` | [ToolCall](#tool-call)[] | Tool calls the assistant wants to execute | | `usage` | [Usage](#usage) | Model identity and token accounting for the LLM call that produced this turn. Unset for turns that did not originate from a model call. | ## Slack Thread Activity A reply posted to a Slack thread the agent is participating in. | Field | Type | Description | |:------|:-----|:------------| | `permalink` | string | Full Slack URL to the thread | | `user_id` | string | Slack user ID of the person who replied | | `text` | string | Reply content | | `channel_name` | string | Name of the Slack channel | ## Slack Mention Activity An @Firetiger mention in a Slack message. | Field | Type | Description | |:------|:-----|:------------| | `permalink` | string | Slack URL to the mention | | `user_id` | string | Slack user ID of the person who mentioned the agent | | `text` | string | Message text (with @mention stripped) | | `channel_id` | string | Slack channel ID (server-resolved from `channel_name` when seeded via [InvokeTrigger](../triggers.txt#slackinvokecontext); never caller-supplied on the public surface) | | `channel_name` | string | Name of the Slack channel | | `thread_ts` | string | Thread timestamp | | `message_ts` | string | Message timestamp | | `idempotency_key` | string | Stamped by [TriggersService.InvokeTrigger](../triggers.txt#invoketrigger) from `InvokeTriggerRequest.idempotency_key`. The Slack event dispatcher sets this to a per-(event, trigger) key so retries of the same Slack event return the prior session instead of creating a duplicate. Empty when `InvokeTrigger` is called without an idempotency key. | ## Slack Reaction Activity An emoji reaction added to an agent-sent message in Slack. Used to capture user feedback on agent responses. | Field | Type | Description | |:------|:-----|:------------| | `permalink` | string | Slack URL to the reacted message | | `user_id` | string | Slack user ID of the person who added the reaction | | `reaction` | string | Emoji name without colons (e.g., "thumbsup", "+1", "white_check_mark") | | `channel_id` | string | Slack channel ID | | `channel_name` | string | Name of the Slack channel | | `message_ts` | string | Timestamp of the message that was reacted to | | `thread_ts` | string | Thread timestamp if the reacted message is in a thread | ## External Agent Activity A message from another agent session. | Field | Type | Description | |:------|:-----|:------------| | `source_session` | string | Resource name of the source agent session | | `source_agent_name` | string | Human-readable name of the source agent | | `title` | string | Message title | | `summary` | string | Brief summary | | `content` | string | Full content (markdown supported) | ## Task Transition A boundary between tasks within an agent session. The runtime writes one when control moves from one task to another. Clients reading session history can use `kind` to interpret why the task changed. | Field | Type | Description | |:------|:-----|:------------| | `from_task` | string | Name of the task control moved from | | `to_task` | string | Name of the task control moved to | | `kind` | TaskTransitionKind | Why the transition happened (see below) | | `input` | object | Structured input for the new task; resolves `${field}` references in its prompt | | `context` | TaskTransitionContext | How the new task receives prior conversation context (defaults to hidden — no prior context) | `kind` is one of: | Value | Description | |:------|:------------| | `TASK_TRANSITION_KIND_CALL` | A subtask was spawned; the parent suspends until it returns | | `TASK_TRANSITION_KIND_TAIL_CALL` | A tail call into another task of the same agent (the state graph) | | `TASK_TRANSITION_KIND_AGENT_TAIL_CALL` | A tail call into another agent | | `TASK_TRANSITION_KIND_END_TURN_RESUME` | The session ended its turn, waited, and resumed into the declared task when the next message arrived — a user reply in interactive mode, or a peer/scheduler push in autonomous mode | | `TASK_TRANSITION_KIND_ASK_RESUME` | **Deprecated.** Collapsed into `TASK_TRANSITION_KIND_END_TURN_RESUME`; retained only so sessions written before the migration remain readable | | `TASK_TRANSITION_KIND_PAUSE_RESUME` | **Deprecated.** Collapsed into `TASK_TRANSITION_KIND_END_TURN_RESUME`; retained only so sessions written before the migration remain readable | ## Artifacts Changed A signal-only event indicating that the session's artifact set under the `artifacts/` namespace has changed — a user-initiated PUT or DELETE through the session artifacts endpoint completed successfully. Carries no payload: consumers re-derive truth by listing the session's artifacts. Tool-result `writeArtifact` calls do not emit this event; their effect is already visible via the surrounding [ToolResult](#tool-result)'s artifacts. ## Compaction A summarization that replaces earlier conversation history to keep sessions manageable. | Field | Type | Description | |:------|:-----|:------------| | `summary` | string | Summary of the preceding messages that were compacted | | `truncated_token_count` | integer | Number of tokens that were truncated | ## Text | Field | Type | Description | |:------|:-----|:------------| | `content` | string | The text content | | `role` | MessageRole | The sender role: `USER`, `ASSISTANT`, or `SYSTEM`. Set `USER` for a client-supplied seed message. | ## Usage Model identity and token accounting for a single LLM call, mirroring the OpenTelemetry `gen_ai` semantic conventions. Carried on [AssistantActivity](#assistant-activity) so observability backends can attribute and price each turn. | Field | Type | Description | |:------|:-----|:------------| | `model` | string | Request model id, e.g. `claude-opus-4-5-20251101` | | `provider` | string | Serving platform: `anthropic`, `aws.bedrock`, `gcp.vertex_ai`, `openai`, or `baseten` | | `input_tokens` | int64 | Prompt tokens consumed | | `output_tokens` | int64 | Completion tokens generated | | `cache_read_tokens` | int64 | Prompt-cache hit tokens (0 if no cache hit) | | `cache_write_tokens` | int64 | Prompt-cache creation tokens (0 if no cache write) | | `finish_reason` | string | Provider stop reason, e.g. `end_turn`, `tool_use`, `max_tokens` | ## Tool Call A request from the assistant to invoke a tool. | Field | Type | Description | |:------|:-----|:------------| | `id` | string | Unique identifier for this tool call (referenced by the corresponding ToolResult) | | `name` | string | Name of the tool to invoke (e.g. `TOOL_POSTGRES_QUERY`) | | `arguments` | string | JSON-encoded arguments for the tool | ## Tool Result The result of executing a tool call. | Field | Type | Description | |:------|:-----|:------------| | `tool_call_id` | string | ID of the ToolCall this result corresponds to | | `content` | string | Output of the tool execution | | `is_error` | boolean | Whether the tool execution failed | ## Error An error that occurred during session execution. | Field | Type | Description | |:------|:-----|:------------| | `message` | string | Human-readable error message | | `details` | map | Optional key-value diagnostics (e.g. provider, error type, trace id) | | `status` | [google.rpc.Status](https://cloud.google.com/apis/design/errors#error_model) | Canonical error status. `status.code` is a [google.rpc.Code](https://cloud.google.com/apis/design/errors#handling_errors); `RESOURCE_EXHAUSTED`, `UNAVAILABLE`, and `DEADLINE_EXCEEDED` denote transient failures the agent retries automatically with backoff, while other codes denote non-transient failures. `status.message` mirrors `message`. For a transient failure `message` is a plain-language headline and the raw provider/runtime text is preserved in `details["detail"]`; for other errors `message` is itself the raw text. `details` also carries triage context (trace id, provider, …). Absent on activities produced before this field was introduced. | ### Slack Handle A `SlackHandle` reserves a Slack workspace user-group `@handle` for Firetiger and tracks the provisioned `user_group_id`. It is pure identity — it does not carry which agents receive mentions. Routing lives on [SlackAgentMentioned triggers](trigger.txt#slack-agent-mentioned-trigger-config) that reference the handle via `configuration.slack_agent_mentioned.slack_handle`. A handle with no referencing trigger is a legitimate intermediate state: the `@`-name is reserved in Slack, but `@`-mentions route nothing until a trigger is created. **Parent**: a Slack [Connection](connection.txt). Handles are unique within a workspace, not globally — the nesting expresses that. The server validates that the parent `Connection` is `CONNECTION_TYPE_SLACK`. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`connections/{connection}/slack-handles/{slack_handle}`) | | `handle` | string | REQUIRED, immutable | `@`-mention token without the leading `@`. Server strips a leading `@` on input and lowercases. Unique within the workspace. | | `user_group_id` | string | OUTPUT_ONLY | Slack user-group ID. Populated after provisioning (`usergroups.create`) succeeds. Stable across the resource's lifetime. | | `create_time` | timestamp | OUTPUT_ONLY | When the handle was created | | `update_time` | timestamp | OUTPUT_ONLY | When the handle was last modified | | `delete_time` | timestamp | OUTPUT_ONLY | When the handle was soft-deleted (null if active) | **Example** ```json { "name": "connections/slack-prod/slack-handles/on-call", "handle": "on-call", "userGroupId": "S0123ABCDEF", "createTime": "2026-04-23T08:28:56Z", "updateTime": "2026-04-23T08:28:56Z" } ``` ### User Identity And Change Monitor Notifications These resources model the authenticated Firetiger user, their typed linked external identities, and the Change Monitor notification preference that controls Slack DMs to PR authors. ## User `User` is Firetiger's local projection of the authenticated Clerk user. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`users/{user}`) | | `clerk_user_id` | string | OUTPUT_ONLY | Authenticated Clerk user identifier | | `primary_email` | string | | User's primary email address | | `full_name` | string | | User's display name, when available | | `image_url` | string | | User avatar URL, when available | | `create_time` | timestamp | OUTPUT_ONLY | When the user projection was created | | `update_time` | timestamp | OUTPUT_ONLY | When the user projection was last modified | | `delete_time` | timestamp | OUTPUT_ONLY | When the user projection was soft-deleted | | `last_synced_at` | timestamp | OUTPUT_ONLY | When Firetiger last refreshed this projection | | `roles` | repeated string | OUTPUT_ONLY | Role resource names (`roles/{role}`) assigned to this user. Managed through the Roles service (`AssignRoles`); the user's effective permissions are the union of these roles' permissions | | `rbac_bootstrap_time` | timestamp | OUTPUT_ONLY | When role-based access control first provisioned this user (assigned their initial role) | | `clerk_membership_role` | string | OUTPUT_ONLY | Internal marker recording the organization role this user was last reconciled to. Not intended for API-client consumption | ## ExternalIdentity `ExternalIdentity` is Firetiger's queryable projection of a verified external account linked through Clerk. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`external-identities/{external_identity}`) | | `user` | string | | [User](#user) resource that owns this identity | | `clerk_user_id` | string | OUTPUT_ONLY | Authenticated Clerk user identifier | | `github` | [GitHubIdentity](#githubidentity) | | GitHub account payload. Mutually exclusive with `slack`. | | `slack` | [SlackIdentity](#slackidentity) | | Slack account payload. Mutually exclusive with `github`. | | `create_time` | timestamp | OUTPUT_ONLY | When the identity was created | | `update_time` | timestamp | OUTPUT_ONLY | When the identity was last modified | | `delete_time` | timestamp | OUTPUT_ONLY | When the identity was soft-deleted after unlinking | | `last_synced_at` | timestamp | OUTPUT_ONLY | When Firetiger last refreshed this projection | ## GitHubIdentity `GitHubIdentity` is the typed payload for a linked GitHub account. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `github_user_id` | string | | Globally stable GitHub user ID | | `username` | string | | GitHub username, when available | | `email` | string | | Email address reported by GitHub | ## SlackIdentity `SlackIdentity` is the typed payload for a linked Slack account. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `slack_user_id` | string | | Slack user ID from the linked identity | | `username` | string | | Slack username, when available | | `email` | string | | Email address reported by Slack | | `workspace_id` | string | | Slack workspace ID for the linked identity | | `workspace_name` | string | | Slack workspace name for the linked identity | ## ChangeMonitorNotificationPreference `ChangeMonitorNotificationPreference` captures the caller's Change Monitor notification choices: whether they have opted in to Slack DM notifications and where those are delivered. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`change-monitor-notification-preferences/{preference}`) | | `enabled` | boolean | | Whether Change Monitor Slack DM notifications are enabled | | `slack_dm` | [ChangeMonitorSlackDMDestination](#changemonitorslackdmdestination) | | Verified Slack DM destination | | `create_time` | timestamp | OUTPUT_ONLY | When the preference was created | | `update_time` | timestamp | OUTPUT_ONLY | When the preference was last modified | | `delete_time` | timestamp | OUTPUT_ONLY | When the preference was soft-deleted | ## ChangeMonitorSlackDMDestination `ChangeMonitorSlackDMDestination` captures the verified Slack delivery target for Change Monitor notifications. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `external_identity` | string | | Linked Slack [ExternalIdentity](#externalidentity) that proves account ownership | | `slack_connection` | string | | Slack [Connection](connection.txt) used for delivery | ## ChangeMonitorNotificationSetupStatus `ChangeMonitorNotificationSetupStatus` summarizes whether the authenticated user has the linked identities and verified Slack DM destination needed for Change Monitor notifications. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `github_identity_linked` | boolean | | Whether the user has linked a GitHub identity | | `slack_identity_linked` | boolean | | Whether the user has linked a Slack identity | | `slack_dm_verified` | boolean | | Whether the user has verified a Slack DM destination | ### Role A role is a named, organization-scoped bundle of permissions. Users hold zero or more roles; their effective permission set is the deduplicated union across all assigned roles. Every organization is seeded with a single system role, **Admin**. Admin is locked (it cannot be renamed, edited, or deleted) and always resolves to every permission in the catalog, including permissions added in future releases, regardless of what is stored in its `permissions` field. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`roles/{role_id}`) | | `display_name` | string | | Human-readable name shown in the UI (e.g. `Integrations Owner`) | | `description` | string | | Optional description of the role | | `permissions` | [Permission](#permission)[] | | Permissions held by this role. Reads are implicit, so the catalog contains only `:write` and `:run` keys. Empty for the system Admin role, whose effective set is computed server-side. | | `system` | boolean | OUTPUT_ONLY | True on the single seeded system role (Admin). Uneditable and undeletable. | | `is_default` | boolean | OUTPUT_ONLY | True on exactly one role per org: the default assigned to new members. Change it with [SetDefaultRole](../roles.txt#setdefaultrole). | | `create_time` | timestamp | OUTPUT_ONLY | When the role was created | | `update_time` | timestamp | OUTPUT_ONLY | When the role was last modified | | `delete_time` | timestamp | OUTPUT_ONLY | When the role was soft-deleted (null if active) | **Example** ```json { "name": "roles/integrations-owner", "displayName": "Integrations Owner", "description": "Manages connections and integrations", "permissions": ["INTEGRATIONS_WRITE"], "system": false, "isDefault": false, "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } ``` ## Permission The permission catalog is fixed and owned by the platform; organizations compose these values into roles but cannot invent new keys. Each value follows a `resource:action` shape. There are no `:read` keys, reads are implicit for any authenticated organization member. A role's stored `permissions` array carries the enum names below. The no- privilege-escalation rule applies on every mutation: an actor may only grant permissions that are a subset of their own effective permissions. | Value | Meaning | |:------|:--------| | `PERMISSION_UNSPECIFIED` | Default zero value, never a valid grant | | `OBSERVABILITY_WRITE` | Edit the observability model (services, SLOs, signals, providers) and configure its Expert agents | | `OBSERVABILITY_RUN` | Trigger SLO evaluations and recreate observability Expert sessions | | `ISSUES_WRITE` | Manage issues, notification policies, and autofix configuration | | `KNOWLEDGE_WRITE` | Edit the knowledge base (runbooks, flows, notes, customer catalog) | | `INVESTIGATIONS_WRITE` | Edit investigation metadata and purge investigations | | `INVESTIGATIONS_RUN` | Start new investigation sessions | | `AGENTS_WRITE` | Create, edit, and delete custom workflow agents | | `AGENTS_RUN` | Start and stop custom agent runs | | `INTEGRATIONS_WRITE` | Manage connections, MCP servers, transports, ingest, and skills | | `CHANGE_MONITOR_WRITE` | Manage change-monitor configuration and its agent | | `MEMBERS_WRITE` | Assign roles to organization members | | `BILLING_WRITE` | Change plan and payment methods | | `ROLES_WRITE` | Create, edit, and delete roles and their permission grants | ### SkillsBundle | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`skills-bundles/{id}`) | | `display_name` | string | REQUIRED | Human-readable label | | `description` | string | | Optional description of what the skills cover | | `connection` | string | REQUIRED | Resource name of the GitHub connection that grants repository access (`connections/{id}`) | | `repository` | string | REQUIRED | Repository holding the skills, in `owner/repo` form | | `directory` | string | | Directory of skill subdirectories within the repository (each holds a `SKILL.md`). Defaults to `skills` | | `branch` | string | | Git ref to read from. Empty uses the repository's default branch | | `enabled` | boolean | | When false, the bundle is configured but its skills are not served. Defaults to `false` on create — set `true` to expose the bundle's skills | | `create_time` | timestamp | OUTPUT_ONLY | When the bundle was created | | `update_time` | timestamp | OUTPUT_ONLY | When the bundle was last modified | | `delete_time` | timestamp | OUTPUT_ONLY | When the bundle was soft-deleted (null if active) | **Example** ```json { "name": "skills-bundles/team-runbooks", "displayName": "Team Runbooks", "connection": "connections/acme-github", "repository": "acme/runbooks", "directory": "skills", "enabled": true, "createTime": "2026-06-20T14:30:00Z", "updateTime": "2026-06-20T14:30:00Z" } ``` ## Network Profiles Network profiles are named, per-organization allow-lists of egress domains used by the Firetiger bash tool to gate outbound HTTPS and DNS. Each agent references one profile; agents with no explicit profile fall back to `network-profiles/default`, seeded on first access with a curated list of common research domains (search engines, package ecosystems, source control, reference docs). Domains are managed as the `AllowedDomain` sub-resource with standard AIP CRUD (`PUT`/`PATCH`/`DELETE` on the URL path segment) so a human-in-the-loop approval UI can allow or deny individual hostnames per conversation turn. Each `AllowedDomain` carries an `allow` boolean: `true` means the domain is in the active allow-list; `false` records a persistent user denial so the approval flow doesn't re-prompt. **Service**: `firetiger.networkprofiles.v1.NetworkProfilesService` **Resource name pattern**: `network-profiles/{network_profile}` and `network-profiles/{network_profile}/allowed-domains/{domain}` **Access**: Read-write (default profile cannot be deleted; all other profiles freely editable) ## Example flow Create a profile and add a domain. The bash tool consumes the allow-list via `GetShellEnvironment` on the connections service, which folds the profile's domains into the `allowed_domains` it already returns for configured connections — no separate fetch needed. **1. Create a profile** ```bash curl -X POST "{{ site.api_url }}/firetiger.networkprofiles.v1.NetworkProfilesService/CreateNetworkProfile" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "networkProfileId": "research", "networkProfile": { "displayName": "Research", "description": "Agents doing open-web research" } }' ``` ```json { "networkProfile": { "name": "network-profiles/research", "displayName": "Research", "description": "Agents doing open-web research" } } ``` **2. Add a domain to the allow-list** `CreateAllowedDomain` is strict: a duplicate live row returns `AlreadyExists`. A previously-deleted row is revived transparently. ```bash curl -X PUT "{{ site.api_url }}/v1/network-profiles/research/allowed-domains/api.example.com" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"allow": true}' ``` ```json { "allowedDomain": { "name": "network-profiles/research/allowed-domains/api.example.com", "allow": true } } ``` ## Match semantics Domains use the implicit-subdomain matcher from `internal/fireshell`: listing `example.com` allows the host **and** every subdomain (`api.example.com`, `cdn.example.com`, `a.b.c.example.com`). The legacy `*.example.com` form is still accepted as an alias for the same thing. One entry per hostname you want to allow; no need for pairs. ## Methods | Method | Description | |:-------|:------------| | [CreateNetworkProfile](#createnetworkprofile) | Create a new profile | | [GetNetworkProfile](#getnetworkprofile) | Retrieve a profile by name | | [UpdateNetworkProfile](#updatenetworkprofile) | Update a profile | | [DeleteNetworkProfile](#deletenetworkprofile) | Soft-delete a profile (except `default`) | | [ListNetworkProfiles](#listnetworkprofiles) | List profiles with filtering + pagination | | [CreateAllowedDomain](#createalloweddomain) | Add a domain to a profile's allow-list | | [UpdateAllowedDomain](#updatealloweddomain) | Toggle the `allow` flag on an existing domain | | [DeleteAllowedDomain](#deletealloweddomain) | Remove a domain from a profile's allow-list | | [GetAllowedDomain](#getalloweddomain) | Check whether a domain is in a profile's allow-list | | [ListAllowedDomains](#listalloweddomains) | List the domains in a profile | The agent runtime does not fetch the allow-list separately: the connections service's `GetShellEnvironment` RPC folds the requested profile's domains into its `allowed_domains` response, so one RPC returns everything a bash call needs. --- ## CreateNetworkProfile Create a new network profile. The ID must match `^[a-zA-Z0-9][a-zA-Z0-9-]{3,62}$` (alphanumeric or `-`, 4-63 characters, no underscores, no leading dash). ``` POST /firetiger.networkprofiles.v1.NetworkProfilesService/CreateNetworkProfile ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `networkProfileId` | string | Yes | ID for the new profile | | `networkProfile` | NetworkProfile | Yes | Profile fields (`displayName`, `description`) | --- ## GetNetworkProfile Retrieve a profile by resource name. Accessing `network-profiles/default` lazily seeds the profile and its starter allow-list if they don't exist. ``` POST /firetiger.networkprofiles.v1.NetworkProfilesService/GetNetworkProfile ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name (`network-profiles/{id}`) | --- ## UpdateNetworkProfile Partial update via `update_mask`. Output-only fields (`name`, `createTime`, `updateTime`, `deleteTime`) are filtered out automatically. ``` POST /firetiger.networkprofiles.v1.NetworkProfilesService/UpdateNetworkProfile ``` --- ## DeleteNetworkProfile Soft-delete a profile. `network-profiles/default` cannot be deleted — it is the fallback for every unconfigured agent and returns `FAILED_PRECONDITION`. ``` POST /firetiger.networkprofiles.v1.NetworkProfilesService/DeleteNetworkProfile ``` --- ## ListNetworkProfiles AIP-158 paginated list. Supports `filter`, `orderBy`, `pageSize`, `pageToken`, `showDeleted`. ``` POST /firetiger.networkprofiles.v1.NetworkProfilesService/ListNetworkProfiles ``` --- ## CreateAllowedDomain Add a domain pattern to a profile's allow-list. Strict: a duplicate live row returns `AlreadyExists`. A previously-deleted row is revived in place, with the `allow` flag updated to whatever the caller sent. ``` PUT /v1/network-profiles/{parent}/allowed-domains/{domain} ``` Or over Connect: ``` POST /firetiger.networkprofiles.v1.NetworkProfilesService/CreateAllowedDomain ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `parent` | string | Yes | Parent profile resource name | | `domain` | string | Yes | Hostname pattern (see [match semantics](#match-semantics)) | | `allow` | bool | No | `true` (default) approves the domain; `false` records a persistent denial | --- ## UpdateAllowedDomain Toggle the `allow` flag on an existing domain. Use `update_mask` to restrict writes to specific fields (`allow` is the only writable path today). Refuses tombstoned rows with `NotFound`. ``` PATCH /v1/network-profiles/{parent}/allowed-domains/{domain} ``` Or over Connect: ``` POST /firetiger.networkprofiles.v1.NetworkProfilesService/UpdateAllowedDomain ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `parent` | string | Yes | Parent profile resource name | | `domain` | string | Yes | Hostname pattern | | `allow` | bool | Yes | New value for the `allow` flag | | `updateMask` | FieldMask | Yes | Fields to update (typically `"allow"`) | --- ## DeleteAllowedDomain Remove a domain pattern from a profile's allow-list. Soft-delete; the row can be revived by a subsequent `CreateAllowedDomain` with the same `{parent}/{domain}`. ``` DELETE /v1/network-profiles/{parent}/allowed-domains/{domain} ``` --- ## GetAllowedDomain Check whether a specific domain is present in a profile's allow-list. Returns the full `AllowedDomain` row including the `allow` flag — callers that care about the distinction between "approved" and "persistently denied" should read the field directly. ``` POST /firetiger.networkprofiles.v1.NetworkProfilesService/GetAllowedDomain ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `parent` | string | Yes | Parent profile resource name | | `domain` | string | Yes | Hostname pattern to look up | --- ## ListAllowedDomains Structured list of domains in a profile (AIP-paginated JSON). ``` POST /firetiger.networkprofiles.v1.NetworkProfilesService/ListAllowedDomains ``` The agent runtime does not call this directly — it receives the merged allow-list from `GetShellEnvironment` on the connections service, which folds the profile's domains into the connection-derived list before returning. --- ## Slack Handles A `SlackHandle` reserves a Slack user-group `@handle` for Firetiger inside a specific Slack workspace (identified by its [Connection](connections.txt)). Once a handle exists and the Slack user group has been provisioned, you can create [`SlackAgentMentioned` triggers](triggers.txt) that route `@`-mentions of that handle to specific agents. **Service**: `firetiger.slackhandles.v1.SlackHandlesService` **Resource name pattern**: `connections/{connection}/slack-handles/{slack_handle}` **Access**: Read-write **Resource type**: [SlackHandle](types/slack-handle.txt) ## Why a separate resource - **Identity vs routing**: the handle is an identity (who can be paged in Slack); a trigger is the routing rule (which agent responds, on what channels). Separating them lets a trigger be edited/enabled/disabled without churning the Slack user group, and lets the same handle fan out to multiple agents via multiple triggers. - **Workspace scope**: `@on-call` in two different workspaces are legitimately different identities — the parent-child naming (`connections/{connection}/slack-handles/{slack_handle}`) makes that explicit. ## Prerequisites The parent `Connection` must be a Slack connection (`CONNECTION_TYPE_SLACK`) whose stored OAuth scopes include `usergroups:read`. Creating a brand-new Slack user group also requires `usergroups:write`; importing an existing Slack user group handle only needs read access. Missing scopes return a structured `FailedPrecondition` with `reason = SLACK_USERGROUP_SCOPE_MISSING`. ## Example flow **1. Create a handle** Reserves the `@`-name in the workspace and provisions or binds to the backing Slack user group. If the handle is unused, Firetiger creates a new Slack user group when the connection has permission. If a workspace user group already holds the handle, Firetiger binds to it without changing group membership. The service enforces one `SlackHandle` row per `(connection, handle)` pair. ```bash curl -X POST "{{ site.api_url }}/firetiger.slackhandles.v1.SlackHandlesService/CreateSlackHandle" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "parent": "connections/slack-prod", "slack_handle_id": "on-call", "slack_handle": { "handle": "on-call" } }' ``` ```json { "slackHandle": { "name": "connections/slack-prod/slack-handles/on-call", "handle": "on-call", "userGroupId": "S0123ABCDEF", "createTime": "2026-04-23T08:28:56Z", "updateTime": "2026-04-23T08:28:56Z" } } ``` **2. Wire the handle to an agent via a trigger** ```bash curl -X POST "{{ site.api_url }}/firetiger.triggers.v1.TriggersService/CreateTrigger" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "trigger_id": "on-call-mentions", "trigger": { "display_name": "On-call @mentions", "agent": "agents/on-call", "enabled": true, "configuration": { "slack_agent_mentioned": { "slack_handle": "connections/slack-prod/slack-handles/on-call", "channels": ["#on-call"] } } } }' ``` **3. List handles on a workspace** ```bash curl -X POST "{{ site.api_url }}/firetiger.slackhandles.v1.SlackHandlesService/ListSlackHandles" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"parent": "connections/slack-prod"}' ``` **4. Delete a handle** Refused while any trigger still references the handle — delete those first. The Firetiger row is soft-deleted; the underlying Slack user group is left in place, including its membership. Other people in the workspace may rely on that group, so its lifecycle stays in Slack — a workspace admin can disable or delete it from Slack if they want it removed. ```bash curl -X POST "{{ site.api_url }}/firetiger.slackhandles.v1.SlackHandlesService/DeleteSlackHandle" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "connections/slack-prod/slack-handles/on-call"}' ``` ## Handle drift The `handle` string is a snapshot captured at create time. Routing from Slack mentions is keyed off `user_group_id`, which is stable across renames — so if a Slack admin renames the backing user group out-of-band, mentions continue to route correctly but `GetSlackHandle` keeps reporting the original handle. The API rejects attempts to change `handle` in place; the reconcile path is to delete the `SlackHandle` and create a new one (`CreateOrGetByHandle` re-binds idempotently to the existing user group, so you don't lose your Slack user-group ID). ## Co-mentions with `@firetiger` If both `@firetiger` and a custom Slack handle appear in a single message (e.g. `@firetiger @on-call help`), only the custom handle's trigger fires. The default `@firetiger` investigation path is suppressed to avoid double-responding to the same message. To get both, send two separate messages. ## Structured errors CreateSlackHandle maps Slack Web-API failures to `google.rpc.ErrorInfo` with stable reason codes so clients can branch without parsing messages: | Reason | Meaning | Remediation | |:-------|:--------|:------------| | `SLACK_USERGROUP_SCOPE_MISSING` | Connection lacks the Slack scope needed for lookup or creation | Reinstall the Slack app with the updated scopes | | `SLACK_USERGROUP_RESTRICTED` | Workspace plan or admin policy blocks Firetiger from creating the requested user group, and no existing group with that handle was found | Ask a Slack admin to create the group in Slack first, upgrade plan, or lift the restriction | | `SLACK_USERGROUP_DISABLED` | A workspace user group with the handle exists but is currently disabled | Ask a Slack workspace admin to re-enable the group, then retry | | `SLACK_CONNECTION_UNAUTHORIZED` | Stored bot token is invalid/revoked | Reinstall the Slack connection | ## Indicators Indicators are named, reusable timeseries measurements backed by ConfitSQL queries. They surface across Agents, Issues, and Change Monitors, and live in a shared catalog that Firetiger maintains over time as your telemetry evolves. Each Indicator is one of two kinds: - **`INDICATOR_KIND_RATIO`** — query produces `time`, `good_events`, `total_events` columns. Used for success-rate measurements (e.g. fraction of `/checkout` requests under 450ms). - **`INDICATOR_KIND_GAUGE`** — query produces `time`, `value` columns. Used for raw scalar measurements (e.g. queue depth, active organizations). The query template references three reserved time-window placeholders that the service substitutes at compile time: - `@start_time` — inclusive query-window start - `@end_time` — exclusive query-window end - `@resolution` — bucket size **Service**: `firetiger.goals.v1.IndicatorsService`, `firetiger.goals.v1.IndicatorRelationsService`, `firetiger.observability.v1.IndicatorsService`, and `firetiger.observability.v1.IndicatorRelationsService` **Resource name patterns**: - `indicators/{indicator}` — v1 Indicator definition - `indicators-v2/{indicator}` — v2 Indicator definition - `indicators/{indicator}/activities/{activity}` — append-only maintenance log - `indicatorRelations/{relation}` — attaches an Indicator to an Agent / Issue / Investigation v2 keeps the collection path at `POST /v2/indicators` and `GET /v2/indicators`, but member and action paths use the `indicators-v2` namespace: - `GET /v2/{name=indicators-v2/*}` - `PATCH /v2/{indicator.name=indicators-v2/*}` - `DELETE /v2/{name=indicators-v2/*}` - `POST /v2/{name=indicators-v2/*}:compileQuery` - `POST /v2/{name=indicators-v2/*}:reportQueryError` v2 IndicatorRelations store `indicator` values as `indicators-v2/{indicator}`; v1 relations continue to store `indicators/{indicator}`. **Access**: v1 read-write; v2 read-only > **The v2 Indicator surface is frozen.** v2 Indicators exist to back > [Objectives](objectives.txt), which are no longer evaluated, so v2 writes > return `FAILED_PRECONDITION` with reason `FEATURE_READ_ONLY`: > `CreateIndicator`, `UpdateIndicator`, `DeleteIndicator`, > `ReportIndicatorQueryError`, and the v2 `CreateIndicatorRelation` / > `DeleteIndicatorRelation`. > > Reads are unaffected — `GET /v2/indicators`, `GET /v2/{name=indicators-v2/*}`, > and `ListIndicatorRelations` all still serve everything, so you can review and > export it. `POST /v2/{name=indicators-v2/*}:compileQuery` also still works: it > compiles and validates a query without persisting anything. > > **v1 is unaffected and stays read-write.** v1 Indicators are evidence attached > to Issues, Investigations, Agents, and Change Monitors, which all continue to > run and to create them. ## Example flow Define an Indicator attached to an Agent, attach it to an Issue, then compile its query for charting. **1. Create an Indicator** ```bash curl -X POST "{{ site.api_url }}/firetiger.goals.v1.IndicatorsService/CreateIndicator" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "indicator_id": "checkout-latency-p95", "indicator": { "display_name": "Checkout p95 latency under 450ms", "description": "Fraction of /checkout requests completing under 450ms.", "kind": "INDICATOR_KIND_RATIO", "unit": "%", "query": { "connections": ["connections/spans"], "confit_sql": "SELECT time_bucket(@resolution, span_start) AS time, count(*) FILTER (WHERE duration_ms < 450) AS good_events, count(*) AS total_events FROM spans WHERE span_start >= @start_time AND span_start < @end_time AND span_name = '\''/checkout'\'' GROUP BY 1", "description": "Reads from spans where span_name = '\''/checkout'\''. Latency from duration_ms." } }, "initial_resource": "agents/checkout-flow" }' ``` `CreateIndicator` is atomic: on success the response carries the Indicator, the initial IndicatorRelation against `initial_resource`, and an initial IndicatorActivity (`"Indicator created."`). The server validates that `confit_sql` references all three reserved placeholders, then asks `ConfitService.ValidateQuery` to perform a schema-aware dry-run — the engine plans the query against in-memory empty parquet files derived from each touched table's iceberg-declared schema (no data scan) — and confirms the planned output schema matches the kind contract. **2. Attach to an Issue** ```bash curl -X POST "{{ site.api_url }}/firetiger.goals.v1.IndicatorRelationsService/CreateIndicatorRelation" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "indicator_relation": { "indicator": "indicators/checkout-latency-p95", "resource": "issues/FT-241" } }' ``` The backing table enforces uniqueness on `(indicator, resource)` for non-deleted relations. Re-attaching after a soft-delete is permitted. **3. Compile a chart query** ```bash curl -X POST "{{ site.api_url }}/firetiger.goals.v1.IndicatorsService/CompileIndicatorQuery" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "name": "indicators/checkout-latency-p95", "start_time": "2026-04-01T00:00:00Z", "end_time": "2026-04-29T00:00:00Z", "resolution": "300s" }' ``` The response carries a ready-to-execute `firetiger.query.v2.QueryRequest` and the current Indicator metadata. Send the request directly to `firetiger.query.v2.ConfitService.Query` — result data does not proxy through the goals API. ## Methods ### IndicatorsService | Method | Description | |:-------|:------------| | [CreateIndicator](#createindicator) | Define a new Indicator and its first relation atomically | | [GetIndicator](#getindicator) | Retrieve an Indicator by name | | [BatchGetIndicators](#batchgetindicators) | Retrieve multiple Indicators in one round trip | | [ListIndicators](#listindicators) | Enumerate Indicators in the catalog | | [UpdateIndicator](#updateindicator) | Edit the Indicator definition (writes a maintenance-log activity) | | [DeleteIndicator](#deleteindicator) | Soft-delete (archive) an Indicator and its IndicatorRelations | | [UndeleteIndicator](#undeleteindicator) | Restore a soft-deleted Indicator and its cascade-deleted relations | | [PurgeIndicators](#purgeindicators) | Hard-delete soft-deleted Indicators matching a filter | | [ListIndicatorActivities](#listindicatoractivities) | Read the maintenance log | | [CompileIndicatorQuery](#compileindicatorquery) | Compile a stored Indicator into a ready-to-execute Confit query | | [ReportIndicatorQueryError](#reportindicatorqueryerror) | Flag a runtime query failure observed by a client so the indicator-fixer cron can repair it | ### IndicatorRelationsService | Method | Description | |:-------|:------------| | [CreateIndicatorRelation](#createindicatorrelation) | Attach an Indicator to a resource | | [ListIndicatorRelations](#listindicatorrelations) | Enumerate relations matching a filter | | [DeleteIndicatorRelation](#deleteindicatorrelation) | Detach (soft-delete) a relation | --- ## CreateIndicator Define a new Indicator together with its first IndicatorRelation. Both writes plus an initial `"Indicator created."` activity land in one transaction. ``` POST /firetiger.goals.v1.IndicatorsService/CreateIndicator ``` REST alternative: ``` POST /v1/indicators ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `indicator_id` | string | No | Caller-chosen kebab-case slug; if empty, the server generates one | | `indicator` | Indicator | Yes | The Indicator definition (see below) | | `initial_resource` | string | Yes | Resource the Indicator is initially attached to. Must match `agents/{agent}`, `issues/{issue}`, `investigations/{investigation}`, or `monitoring-plans/{plan}` | The `indicator` object accepts: | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `display_name` | string | Yes | Human-readable label | | `description` | string | No | Markdown rendered as "What this measures" in the UI | | `kind` | enum | Yes | `INDICATOR_KIND_RATIO` or `INDICATOR_KIND_GAUGE` | | `query.connections` | string[] | Yes | Connection resource names the query reads from | | `query.confit_sql` | string | Yes | ConfitSQL template; must reference `@start_time`, `@end_time`, and `@resolution` | | `query.description` | string | No | Markdown rendered as "How this is grounded" | | `unit` | string | v1: No · v2: Yes | Display unit (`%`, `ms`, `{jobs}`, etc.). Required when creating a v2 Indicator (`indicators-v2/…`, including via `RecommendServiceObservability`); optional on v1 and on any update | --- ## GetIndicator Retrieve a single Indicator including its query status (validation timestamp + last error). ``` POST /firetiger.goals.v1.IndicatorsService/GetIndicator ``` REST alternative: ``` GET /v1/{name=indicators/*} ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Indicator resource name (e.g. `indicators/checkout-latency-p95`) | --- ## BatchGetIndicators Retrieve several Indicators in a single request. Used after listing IndicatorRelations to hydrate Indicator details for each related resource. ``` POST /firetiger.goals.v1.IndicatorsService/BatchGetIndicators ``` REST alternative: ``` POST /v1/indicators:batchGet ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `names` | string[] | Yes | Indicator resource names | Returns Indicators in the same order as `names`. A missing Indicator fails the entire batch with `NOT_FOUND`. --- ## ListIndicators Enumerate Indicators in the shared catalog. Supports AIP-160 filtering, AIP-158 pagination, and `show_deleted` to surface archived Indicators. ``` POST /firetiger.goals.v1.IndicatorsService/ListIndicators ``` REST alternative: ``` GET /v1/indicators ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `filter` | string | No | AIP-160 filter (e.g. `kind = "INDICATOR_KIND_RATIO"`) | | `order_by` | string | No | Sort order | | `page_size` | int | No | Maximum number of Indicators per page | | `page_token` | string | No | Pagination token from a previous response | | `show_deleted` | bool | No | Include soft-deleted Indicators | --- ## UpdateIndicator Edit the live Indicator definition. There are no Indicator revisions in v1 — the definition mutates in place and an IndicatorActivity entry lands in the same transaction. Pass `activity_description` for human-authored maintenance notes; otherwise the service auto-generates a generic entry. ``` POST /firetiger.goals.v1.IndicatorsService/UpdateIndicator ``` REST alternative: ``` PATCH /v1/{indicator.name=indicators/*} ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `indicator` | Indicator | Yes | New field values; only fields named in `update_mask` are written | | `update_mask` | FieldMask | Yes | List of paths to update (e.g. `display_name,description`) | | `activity_description` | string | No | Maintenance-log entry to write atomically with the update | Server-managed paths (`name`, `etag`, `create_time`, `update_time`, `delete_time`, `query.status*`) are silently filtered from the mask. --- ## DeleteIndicator Soft-delete the Indicator (sets `delete_time`) and cascade-soft-delete its IndicatorRelations using a shared `delete_time`. The shared timestamp lets `UndeleteIndicator` selectively restore only the relations the cascade touched — relations the user previously detached on their own carry a different `delete_time` and stay archived. A `"Indicator archived."` activity is written. ``` POST /firetiger.goals.v1.IndicatorsService/DeleteIndicator ``` REST alternative: ``` DELETE /v1/{name=indicators/*} ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Indicator resource name | --- ## UndeleteIndicator Restore a previously soft-deleted Indicator. Clears `delete_time` on the Indicator and cascade-restores any IndicatorRelations whose `delete_time` matches the Indicator's (i.e. the ones `DeleteIndicator` archived together with it). Relations the user detached separately stay archived. An `"Indicator restored."` activity is written. ``` POST /firetiger.goals.v1.IndicatorsService/UndeleteIndicator ``` REST alternative: ``` POST /v1/{name=indicators/*}:undelete ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Indicator resource name | The response carries the restored Indicator. --- ## PurgeIndicators Hard-delete soft-deleted Indicators matching an AIP-160 filter, along with their cascade-deleted IndicatorRelations and IndicatorActivities. **Irreversible** — typically driven by a retention cron rather than interactive use. Pass `force=false` for a dry-run that returns the count and a sample of names that would be purged without actually deleting anything. ``` POST /firetiger.goals.v1.IndicatorsService/PurgeIndicators ``` REST alternative: ``` POST /v1/indicators:purge ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `filter` | string | No | AIP-160 filter against soft-deleted Indicators | | `force` | bool | No | When `false` (default), returns the dry-run count + sample without deleting | The response carries `purge_count` (number of Indicators that were/would be purged) and `purge_sample` (sample of Indicator names from that set). --- ## ListIndicatorActivities Read the maintenance log for an Indicator — creation, edits, archive, and (in future) re-grounding and dispute events. Activities are written by the service itself; there is no public `CreateIndicatorActivity`. ``` POST /firetiger.goals.v1.IndicatorsService/ListIndicatorActivities ``` REST alternative: ``` GET /v1/{parent=indicators/*}/activities ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `parent` | string | Yes | Indicator resource name | | `filter` | string | No | AIP-160 filter | | `order_by` | string | No | Sort order. Default: `create_time desc` | | `page_size` | int | No | Maximum number of activities per page | | `page_token` | string | No | Pagination token | | `show_deleted` | bool | No | Include purged activities | --- ## CompileIndicatorQuery Compile the stored Indicator into a ready-to-execute Confit `QueryRequest`. The service substitutes `@start_time` / `@end_time` / `@resolution` with typed SQL literals from the request and resolves `query.connections` into `ConnectionConfig` values. The client sends `response.query_request` directly to `firetiger.query.v2.ConfitService.Query` so result data does not proxy through the goals API server. ``` POST /firetiger.goals.v1.IndicatorsService/CompileIndicatorQuery ``` REST alternative: ``` POST /v1/{name=indicators/*}:compileQuery ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Indicator resource name | | `start_time` | timestamp | Yes | Inclusive query-window start (substituted for `@start_time`) | | `end_time` | timestamp | Yes | Exclusive query-window end (substituted for `@end_time`) | | `resolution` | duration | Yes | Bucket size (substituted for `@resolution`) | --- ## ReportIndicatorQueryError Flag that an Indicator's query failed at runtime. The server persists the (truncated) error to `query.status.error` and bumps `query.status.validate_time`, so the indicator-fixer cron picks the Indicator up on its next pass and either auto-heals it (if a smoke-test against real data succeeds) or queues it for the indicator-curator agent to repair, replace, or delete. Idempotent: re-reporting the same error string still bumps `validate_time` but skips appending a new `IndicatorActivity` so a chart that errors on every page-load doesn't spam the maintenance log. Used by UI surfaces that execute compiled Indicator queries (e.g. the indicator detail chart, issue indicator cards) to surface live failures to the maintenance loop. ``` POST /firetiger.goals.v1.IndicatorsService/ReportIndicatorQueryError ``` REST alternative: ``` POST /v1/{name=indicators/*}:reportQueryError ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Indicator resource name | | `error` | string | Yes | Runtime error observed by the client (e.g. DuckDB binder text, Confit error body). Truncated server-side; only a head slice is persisted to `query.status.error`. Empty values are rejected — clearing the status is the indicator-fixer cron's responsibility, not a public client surface. | --- ## CreateIndicatorRelation Attach an Indicator to a resource (Agent, Issue, Investigation, or Monitoring Plan). Idempotent against soft-deleted relations: re-creating one that was previously detached is permitted. ``` POST /firetiger.goals.v1.IndicatorRelationsService/CreateIndicatorRelation ``` REST alternative: ``` POST /v1/indicatorRelations ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `indicator_relation.indicator` | string | Yes | Indicator resource name | | `indicator_relation.resource` | string | Yes | Target resource name (`agents/...`, `issues/...`, `investigations/...`, or `monitoring-plans/...`) | --- ## ListIndicatorRelations Enumerate relations matching a filter. Typical reads filter by a single `resource` (Issue / Agent / Investigation / Monitoring Plan page) or a single `indicator` (Indicator dependents tab). ``` POST /firetiger.goals.v1.IndicatorRelationsService/ListIndicatorRelations ``` REST alternative: ``` GET /v1/indicatorRelations ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `filter` | string | No | AIP-160 filter (e.g. `resource = "issues/FT-241"` or `indicator = "indicators/checkout-latency-p95"`) | | `order_by` | string | No | Sort order | | `page_size` | int | No | Maximum number of relations per page | | `page_token` | string | No | Pagination token | | `show_deleted` | bool | No | Include detached relations | --- ## DeleteIndicatorRelation Detach an Indicator from a resource (soft-delete). The Indicator itself is unaffected. ``` POST /firetiger.goals.v1.IndicatorRelationsService/DeleteIndicatorRelation ``` REST alternative: ``` DELETE /v1/{name=indicatorRelations/*} ``` | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | IndicatorRelation resource name (e.g. `indicatorRelations/rel-abc`) | ## Flows Flows are structured descriptions of user journeys or business processes that span multiple services. Each Flow lists the ordered steps, their dependencies, the services touched, and the telemetry signals that identify each step. Agents use Flows to ground investigations in the caller's real call graph instead of reasoning about opaque service boundaries. **Service**: `firetiger.objectives.v1.FlowsService` **Resource name pattern**: `flows/{flow_id}` **Access**: Read-write ## CRUD methods The standard AIP-compliant CRUD surface is available: `CreateFlow`, `GetFlow`, `UpdateFlow`, `DeleteFlow`, `ListFlows`. See `proto/firetiger/objectives/v1/flows.proto` for request/response shapes — they follow the same patterns as other resources documented here (e.g. [Tags](tags.txt)). ## Revision history (AIP-162) Every mutation to a Flow (`Create`, `Update`, `Delete`, `Undelete`) atomically writes a revision row recording **who** changed the resource, **what** kind of change, and **when**. Two RPCs expose the history: ### ListFlowRevisions List the revision history of one Flow. Default order is reverse-chronological (newest first). ``` GET /v1/{parent=flows/*}/revisions ``` or as a Connect RPC: ``` POST /firetiger.objectives.v1.FlowsService/ListFlowRevisions ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `parent` | string | Yes | Resource name of the Flow whose revisions to list. Format: `flows/{flow}` | | `page_size` | integer | No | Maximum revisions per page | | `page_token` | string | No | Token from a previous `ListFlowRevisionsResponse` | | `filter` | string | No | [AIP-160 filter](https://google.aip.dev/160) over `name`, `revision_number`, `operation`, `actor_subject`, `actor_kind`, `create_time` | | `order_by` | string | No | AIP-132 ordering. Defaults to `create_time desc` | **Example — full history** ```bash curl -X POST "{{ site.api_url }}/firetiger.objectives.v1.FlowsService/ListFlowRevisions" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"parent": "flows/checkout", "page_size": 50}' ``` **Example — temporal read ("what did this Flow look like at time T?")** ```bash curl -X POST "{{ site.api_url }}/firetiger.objectives.v1.FlowsService/ListFlowRevisions" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "parent": "flows/checkout", "filter": "create_time <= \"2026-05-14T18:06:00Z\"", "order_by": "create_time desc", "page_size": 1 }' ``` The first item in the response carries the `snapshot` of the Flow as of T. **Response** ```json { "flowRevisions": [ { "name": "flows/checkout/revisions/3", "snapshot": { "name": "flows/checkout", "displayName": "Checkout v3", "...": "..." }, "createTime": "2026-05-14T18:10:11Z", "operation": "REVISION_OPERATION_UPDATE", "actor": { "subject": "user_2abc", "kind": "ACTOR_KIND_USER" } } ], "nextPageToken": "" } ``` ### GetFlowRevision Return a single revision by its AIP-162 nested-collection name. ``` GET /v1/{name=flows/*/revisions/*} ``` or: ``` POST /firetiger.objectives.v1.FlowsService/GetFlowRevision ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Format: `flows/{flow}/revisions/{n}` where `n` is a positive integer | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.objectives.v1.FlowsService/GetFlowRevision" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "flows/checkout/revisions/2"}' ``` **Response** ```json { "flowRevision": { "name": "flows/checkout/revisions/2", "snapshot": { "name": "flows/checkout", "displayName": "Checkout v2", "...": "..." }, "createTime": "2026-05-14T18:05:32Z", "operation": "REVISION_OPERATION_UPDATE", "actor": { "subject": "user_2abc", "kind": "ACTOR_KIND_USER" } } } ``` ## Notes - **History starts at opt-in time.** Flows that existed before this revision API shipped have no historical revisions until their next mutation. Calls against the pre-opt-in window return an empty page (not NotFound). - **`Purge`** (force delete) cascades to revisions atomically — deleting a Flow erases its history. - The `snapshot` field is the full Flow body at that revision. For the canonical AIP-162 spec, see [google.aip.dev/162](https://google.aip.dev/162). ## Objectives An Objective is a Service Level Objective: a target evaluated against an [Indicator](indicators.txt). It pairs a plain-English promise to your users (the `description`) with a machine-checkable target (the `definition`). **Service**: `firetiger.observability.v1.ObjectivesService` **Resource name pattern**: `objectives/{objective}` **Access**: Read-only > **Objectives are frozen.** They are no longer evaluated and no longer accept > changes. Every existing Objective stays readable so you can review and export > it, but the write and run methods below return `FAILED_PRECONDITION`, and no > Investigations are opened from an Objective breach. See > [Exporting your Objectives](#exporting-your-objectives). ## Methods Available: | RPC | HTTP | |:----|:-----| | `GetObjective` | `GET /v2/{name=objectives/*}` | | `ListObjectives` | `GET /v2/objectives` | | `ListRecommendedObjectiveChanges` | `GET /v2/{parent=objectives/*}/recommendedObjectiveChanges` | | `GetRecommendedObjectiveChange` | `GET /v2/{name=objectives/*/recommendedObjectiveChanges/*}` | Frozen — these return `FAILED_PRECONDITION` with reason `FEATURE_READ_ONLY`: | RPC | HTTP | |:----|:-----| | `CreateObjective` | `POST /v2/objectives` | | `EvaluateObjective` | `GET /v2/{name=objectives/*}:evaluate` | | `UpdateObjective` | `PATCH /v2/{objective.name=objectives/*}` | | `DeleteObjective` | `DELETE /v2/{name=objectives/*}` | | `ActivateObjective` | `POST /v2/{name=objectives/*}:activate` | | `DeactivateObjective` | `POST /v2/{name=objectives/*}:deactivate` | | `ReactivateObjective` | `POST /v2/{name=objectives/*}:reactivate` | | `AcceptObjective` | `POST /v2/{name=objectives/*}:accept` | | `ArchiveObjective` | `POST /v2/{name=objectives/*}:archive` | | `RestoreObjective` | `POST /v2/{name=objectives/*}:restore` | | `UndeleteObjective` | `POST /v2/{name=objectives/*}:undelete` | | `RecreateObjectiveExpertSession` | `POST /v2/{name=objectives/*}:recreateExpertSession` | | `CreateRecommendedObjectiveChange` | `POST /v2/{parent=objectives/*}/recommendedObjectiveChanges` | | `AcceptRecommendedObjectiveChange` | `POST /v2/{name=objectives/*/recommendedObjectiveChanges/*}:accept` | | `RejectRecommendedObjectiveChange` | `POST /v2/{name=objectives/*/recommendedObjectiveChanges/*}:reject` | Request/response shapes are in `proto/firetiger/observability/v1/objectives.proto`. ## Exporting your Objectives `ListObjectives` returns the full resource, so one paginated sweep is a complete export — definition, state, backing Indicator, calibration snapshots, and the latest evaluation: Authenticate with an [API key](../account-management/api_keys.txt) — HTTP Basic, the same as every other programmatic call: `showDeleted=true` matters: List omits soft-deleted records by default, and the freeze means an Objective you deleted can no longer be undeleted — so without it those rows are gone for good. ```bash curl -s -u "$FT_API_KEY_USERNAME:$FT_API_KEY_PASSWORD" \ "https://api./v2/objectives?pageSize=1000&showDeleted=true" > objectives.json ``` Follow `nextPageToken` if you have more than one page. The recommended changes attached to an Objective come from a second sweep per Objective: ```bash curl -s -u "$FT_API_KEY_USERNAME:$FT_API_KEY_PASSWORD" \ "https://api./v2/objectives//recommendedObjectiveChanges?showDeleted=true" ``` The `ftops` CLI wraps the same endpoints: ```bash ftops api objectives list --all --show-deleted ``` The related [Services](services.txt) and Indicators catalogs export the same way through their own List methods. ## Objective resource | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | Output only | Format: `objectives/{objective}` | | `display_name` | string | Required | Short label shown in lists and headers | | `description` | string | Required | One-sentence, plain-English promise to the user (avoid SRE jargon) | | `indicator` | string | Required | The Indicator this Objective is computed against. Format: `indicators-v2/{indicator}` | | `definition` | oneof `gauge` \| `ratio` | Conditionally required | Kind-specific SLO body; at most one variant, and it must match the Indicator's kind. The target (GAUGE `threshold`, RATIO `target_failure_rate`) lives inside the variant. Optional while the Objective is `RECOMMENDED` or `CALIBRATING` (agent recommendations are concept-only and the working target is tuned during calibration); required to enter `ACTIVE` — activation is where the target is committed | | `filter` | DimensionFilter[] | Optional | Narrows included rows to a subset of the Indicator's declared dimension values. Filters do not define whether a dimension matters | | `owner_resource` | string | Optional | Catalog resource this Objective belongs to (MVP: `services/{service}`) | | `state` | ObjectiveState | Output only | Lifecycle state. `CALIBRATING` on create (unless `calibration_config.skip`); activated via `ActivateObjective`; paused/resumed via `DeactivateObjective` and `ReactivateObjective`. Agent-recommended Objectives persist in `RECOMMENDED` until accepted. See [ObjectiveState](#objectivestate) | | `calibration_config` | CalibrationConfig | Optional | Set `skip = true` to start `ACTIVE` with no calibration phase. A skipping create must carry a `definition` (rejected `INVALID_ARGUMENT` otherwise), and accepting a skip-flagged recommendation that has no definition lands `CALIBRATING` instead of `ACTIVE` | | `calibration_observations` | CalibrationObservations | Output only | Rolling daily baseline snapshots (≤7, FIFO) gathered while `CALIBRATING`; frozen on activation | | `trigger` | ObjectiveTrigger | Optional | What opens an Investigation against this Objective. A default `RateMultiplierTrigger` is applied on create when unset | | `latest_evaluation` | LatestObjectiveEvaluation | Output only | Latest persisted health snapshot written by the periodic evaluator. Includes bounded per-Cell health rows, aggregate health counts, evaluation time, and any `google.rpc.Status` execution error | | `trigger_state` | ObjectiveTriggerState | Output only | Bounded server-owned bookkeeping for the current unhealthy episode observed by `trigger`. Present only while the Objective is unresolved; cleared when all observed Cells are healthy. See [ObjectiveTriggerState](#objectivetriggerstate) | | `expert_session` | string | Output only | The per-Objective objective-expert agent session that owns this Objective's Indicator lifecycle (review, re-validation, repair). Format: `agents/{agent}/sessions/{session}` | | `indicator_unit` | string | Output only | Display unit of the backing Indicator (e.g. `s`, `ms`, `%`), resolved from the referenced Indicator on read by `GetObjective`/`ListObjectives` so threshold/target numbers render with their unit. Computed on read, never persisted; empty when the Indicator can't be read | | `recommendation_confidence` | ObjectiveRecommendationConfidence | Optional | Agent's confidence in this recommendation. See [ObjectiveRecommendationConfidence](#objectiverecommendationconfidence) | | `recommendation_evidence` | string[] | Optional | Supporting evidence the agent cited when recommending this Objective | | `recommendation_reasoning` | string | Optional | Free-text rationale for why this Objective was recommended | | `etag` | string | Output only | Optimistic-concurrency token; required on `Update`, stale etags fail with `ABORTED` | | `create_time` / `update_time` / `delete_time` | timestamp | Output only | Standard AIP lifecycle timestamps | `UpdateObjective` is field-masked: send the `Objective` with an `update_mask` of the paths to write. Output-only fields (`state`, `calibration_observations`, `latest_evaluation`, `trigger_state`, `expert_session`, `indicator_unit`, `etag`, timestamps) are server-managed; do not set them on `Create`/`Update`. Calibration-phase revisions are agent-internal tooling, not a separate API: the per-Objective expert agent revises the working definition/details through a calibration-gated masked `UpdateObjective` (its tooling refuses any Objective that is not `CALIBRATING`, restricts the mask to `{display_name, description, gauge, ratio, filter}`, and echoes the read `etag` so a concurrent activation fails the write with `ABORTED`). Once `ACTIVE`, agent changes go through `RecommendedObjectiveChange`. ### ObjectiveState | Value | Description | |:------|:------------| | `OBJECTIVE_STATE_UNSPECIFIED` | Default zero value; not a valid persisted state | | `OBJECTIVE_STATE_CALIBRATING` | Set on create (unless `calibration_config.skip`); gathers baseline samples but does not alert. The working `definition` and details are revisable in place by the per-Objective expert agent while in this state. Transitions to `ACTIVE` only via `ActivateObjective` or an accepted `KIND_ACTIVATE` recommended change | | `OBJECTIVE_STATE_ACTIVE` | Evaluating and alerting | | `OBJECTIVE_STATE_INACTIVE` | Customer-paused via `DeactivateObjective`; not evaluating. Resumed with `ReactivateObjective` | | `OBJECTIVE_STATE_RECOMMENDED` | Persisted agent recommendation. Visible in the Service UI but does not calibrate, evaluate, alert, or spin up an objective-expert session until accepted | | `OBJECTIVE_STATE_ARCHIVED` | Dismissed recommendation. Hidden from active views and skipped by `AcceptService` bulk-activation | ## Customer lifecycle Customer-owned Objectives move through the normal operational lifecycle after creation: - **`ActivateObjective`** transitions a `CALIBRATING` Objective to `ACTIVE`, optionally applying a `threshold_override` in the same transaction. It is idempotent if the Objective is already `ACTIVE`. - **`DeactivateObjective`** pauses an `ACTIVE` Objective by moving it to `INACTIVE`. Paused Objectives are not evaluated and do not open Investigations. The call is idempotent for an already-`INACTIVE` Objective and rejects `CALIBRATING`, `RECOMMENDED`, and `ARCHIVED` Objectives with `FAILED_PRECONDITION`. - **`ReactivateObjective`** resumes an `INACTIVE` Objective by moving it back to `ACTIVE`. The call is idempotent for an already-`ACTIVE` Objective and rejects `CALIBRATING`, `RECOMMENDED`, and `ARCHIVED` Objectives with `FAILED_PRECONDITION`. - **`DeleteObjective`** soft-deletes the Objective by setting `delete_time`. **`UndeleteObjective`** clears `delete_time` for non-`ARCHIVED` Objectives, backing undo flows after user-initiated archive/delete actions. ### ObjectiveRecommendationConfidence | Value | Description | |:------|:------------| | `OBJECTIVE_RECOMMENDATION_CONFIDENCE_UNSPECIFIED` | Confidence not set | | `OBJECTIVE_RECOMMENDATION_CONFIDENCE_HIGH` | High confidence | | `OBJECTIVE_RECOMMENDATION_CONFIDENCE_MEDIUM` | Medium confidence | | `OBJECTIVE_RECOMMENDATION_CONFIDENCE_LOW` | Low confidence | ## Recommendation lifecycle Beyond the customer-authored Create → calibrate → activate path, Objectives can be proposed by a Service's expert agent. A recommended Objective is persisted as a real `objectives/{objective}` resource in `OBJECTIVE_STATE_RECOMMENDED`: it shows up in the Service UI alongside active Objectives but does not calibrate, evaluate, alert, or create an objective-expert session. - **`AcceptObjective`** moves a `RECOMMENDED` Objective into the normal lifecycle (entering `CALIBRATING`, or `ACTIVE` if calibration is skipped), spinning up its expert session. - **`ArchiveObjective`** dismisses a recommendation into `OBJECTIVE_STATE_ARCHIVED`. Archived Objectives are hidden from active views and skipped by `AcceptService` bulk-activation. - **`RestoreObjective`** brings an archived Objective back to `RECOMMENDED`. Recommendation metadata (`recommendation_confidence`, `recommendation_evidence`, `recommendation_reasoning`) is carried on the Objective resource itself. ### Recommending changes to existing Objectives A `RecommendedObjectiveChange` is a child of the Objective it advises (`objectives/{objective}/recommendedObjectiveChanges/{recommended_objective_change}`) and proposes an **edit, removal, or activation** of that live Objective, distinct from a brand-new-Objective recommendation (which is a `RECOMMENDED`-state Objective). The agent only ever creates one; it never mutates an `ACTIVE` Objective directly. The human accepts or rejects it. At most one change is pending per Objective: creating a new one supersedes (resolves) any still-pending proposal on the same target. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | Output only | Format: `objectives/{objective}/recommendedObjectiveChanges/{recommended_objective_change}` | | `kind` | Kind | Required | `KIND_UPDATE` (edit), `KIND_DELETE` (removal), or `KIND_ACTIVATE` (lock in the calibrated target and activate) | | `proposed` | Objective | Optional | UPDATE/ACTIVATE. Sparse Objective holding just the changed fields; applied under `objective_update_mask`. Not validated at propose time, the merged result is validated on accept | | `objective_update_mask` | FieldMask | Optional | UPDATE/ACTIVATE. Paths to apply from `proposed`; for UPDATE restricted to `display_name`, `description`, `gauge`, `ratio`, `filter` (the SLO body is masked by its oneof variant `gauge`/`ratio`, not `definition`); for ACTIVATE restricted to at most one of `gauge`/`ratio` (the settled target), or empty when the working definition was already tuned in during calibration | | `proposed_indicator` | Indicator | Optional | UPDATE only. Sparse query/dimension rewrite of the target Objective's backing Indicator, applied under `indicator_update_mask` | | `indicator_update_mask` | FieldMask | Optional | UPDATE only. Paths to apply from `proposed_indicator` | | `reasoning` / `confidence` | — | Optional | Why the change is proposed and the agent's confidence in it. `reasoning` is review-card copy, capped at **1200 characters**: a longer value is rejected with `INVALID_ARGUMENT` at create time | | `target_etag` | string | Output only | The target Objective's etag captured at propose time. Advisory: the UI warns when it differs from the live Objective. Accept is last-write-wins, not gated on it | | `source` | string | Optional | The agent session that authored the proposal (`agents/{agent}/sessions/{session}`). The agent tooling fills this from the running session, like `Issue.source`; a direct caller may set it or leave it blank | | `diff` | FieldDiff[] | Output only | Server-rendered `{field_path, before, after}` for each masked path (objective fields, plus backing-indicator fields prefixed `indicator.`), computed at create time. Clients render these directly rather than reconstructing the diff from `proposed` + masks, so no change is hidden before accept. Empty for a DELETE | | `create_time` / `update_time` / `delete_time` | timestamp | Output only | `delete_time` is stamped when the change is accepted or rejected (soft-delete as history) | - **`AcceptRecommendedObjectiveChange`** applies the change in one transaction: UPDATE → `UpdateObjective` + `UpdateIndicator` (the rewrite is always bound to the target Objective's own backing Indicator, never an arbitrary one a proposal might name); DELETE → `DeleteObjective`, also resolving the Objective's other pending changes so none dangle; ACTIVATE → applies the proposed `gauge`/`ratio` (if the card carries one) and transitions `CALIBRATING` → `ACTIVE` atomically, with the same semantics as `ActivateObjective` (idempotent if a manual activation raced the card). The accepted change is soft-deleted as history. - **`RejectRecommendedObjectiveChange`** soft-deletes the change without touching the Objective. - **`UndeleteObjective`** clears `delete_time` on a soft-deleted Objective, backing the undo after an accepted removal. Restricted to non-`ARCHIVED` objectives: a service-archived Objective is also soft-deleted but is restored with its Service (it carries the `delete_time` marker that service-restore keys on), so calling this on an `ARCHIVED` Objective returns `FAILED_PRECONDITION`. **Errors / edge cases:** - Accept or reject of a change that is already resolved (its `delete_time` is set) returns `FAILED_PRECONDITION`. - Creating a change supersedes any still-pending change on the same Objective (the superseded card is soft-deleted as resolved history), so at most one card is pending per Objective. - `KIND_UPDATE` and `KIND_DELETE` require a **live** target (`ACTIVE`, `INACTIVE`, or `CALIBRATING`); a `RECOMMENDED` or `ARCHIVED` target is rejected with `FAILED_PRECONDITION` — corrections to a pending recommendation are replacement proposals (create the corrected recommendation, archive the stale one), not edits layered on an unaccepted card. - An ACTIVATE may not carry indicator changes, and its `objective_update_mask` admits at most one of `gauge`/`ratio`, whose variant must be present in `proposed`. An ACTIVATE with an empty mask against an Objective that has no definition is rejected with `INVALID_ARGUMENT` at create time — there would be no target to activate against. - An ACTIVATE may only be proposed against a `CALIBRATING` Objective; any other target state is rejected with `FAILED_PRECONDITION` at create time (target edits on an `ACTIVE` Objective use `KIND_UPDATE`). If the Objective was activated manually while the card was pending, accepting the card is idempotent: it resolves without re-applying the proposed target. - Accept of an UPDATE is **last-write-wins**: it applies the masked patch to the current live Objective/Indicator. `target_etag` is advisory (the UI warns on drift) and is not a precondition, so an accept against a since-changed Objective succeeds and only overwrites the masked paths. - `objective_update_mask` is restricted to `{display_name, description, gauge, ratio, filter}` and `indicator_update_mask` to `{query[.confit_sql/.connections/.description], display_name, description, unit, dimensions}`; any other path is rejected with `INVALID_ARGUMENT` at create time. The indicator rewrite is always bound to the target Objective's own backing Indicator regardless of any name on the proposed patch. - `reasoning` longer than 1200 characters is rejected with `INVALID_ARGUMENT` at create time — it is the human-scanned review-card rationale, not a place for full evidence dumps. - Accept of an UPDATE re-runs the standard Objective (definition-vs-Indicator) and Indicator (ConfitSQL/placeholder/dimension) validation against the merged result; a now-invalid merge fails with `INVALID_ARGUMENT` and the Objective is left unchanged. - Accept of a DELETE soft-deletes the Objective and resolves its other pending changes; `UndeleteObjective` restores it. ## How evaluation works Firetiger re-checks every active Objective on a short cycle (about every 5 minutes). It does **not** wait for a long window to "fill up" — each check looks *back* over two rolling windows that both end at now: - **Short window** (default 5 minutes) — "is it bad right now?" - **Long window** (default 1 hour) — "is it bad looking back over the longer period?" A Cell is **Unhealthy** only when **both** windows are over the line at the same time. Requiring both is deliberate: the short window catches a problem quickly, and the long window confirms it is real and not a one-off blip. (A window with too little traffic or no data is reported [`LOW_VOLUME`](#minimum-volume-guard) / `NO_DATA` instead of being judged.) "Over the line" means the Objective's target adjusted by the trigger's `multiplier` (`1.0` = fire right at the target). For dimensional Objectives, every Cell is judged on its own and the Objective is Unhealthy if any Cell is. **How fast it reacts depends on what you measure:** - **Latency-style Objectives** (a GAUGE, e.g. p99 latency) react **quickly**. If responses are slow right now, that slowness shows up in both the 5-minute and the 1-hour view at the same time, so it is flagged on the next check. The long window mainly makes the breach "stick" a little longer. - **Error-rate Objectives** (a RATIO) react **more slowly, on purpose**. The 1-hour view averages all traffic together, so a brief blip gets diluted and ignored. The Objective only turns Unhealthy once errors keep happening hard or long enough to move the whole hour's average past the target — a genuinely sustained problem. Once a Cell is Unhealthy, Firetiger opens at most one Investigation per `cooldown` window (default 1 hour), so a sustained problem does not re-page every cycle. ### Trigger settings (`RateMultiplierTrigger`) Every Objective carries a `trigger.rate_multiplier`; a default is applied on create, and each field can be tuned per Objective. | Field | Default | Meaning | |:------|:--------|:--------| | `multiplier` | `1.0` (GAUGE), `14.4` (RATIO) | How far past the target counts as a breach. `1.0` fires right at the target; a higher value adds margin so only a clear breach triggers | | `long_window` | `1h` | The longer rolling window — confirms the problem is sustained | | `short_window` | `5m` | The recent rolling window — catches the problem quickly and bounds how soon a recovered Cell clears | | `cooldown` | `1h` | Minimum gap between automated Investigations for the same ongoing episode | | `min_volume` | per-kind default | Minimum events in the short window before a Cell is evaluated. See [Minimum-volume guard](#minimum-volume-guard) | ## Dimensional evaluation Objective dimensionality comes from the backing Indicator: - If `Indicator.dimensions` is empty, the Objective is scalar. - If `Indicator.dimensions` contains ServiceDimensions, Firetiger evaluates the Objective independently for each observed Cell. A Cell is the set of observed ServiceDimension values for that row, such as `region=us-west-2` and `tenant=acme-corp`. - `Objective.filter[]` narrows which rows are included before evaluation; it does not change which bound dimensions define the Cells. Unhealthy dimensional Cells do not create one Investigation per Cell. Firetiger creates at most one Objective-level Investigation per cooldown window and includes the unhealthy Cells as evidence. `EvaluateObjective` returns the current health on demand. The response contains `health_status`, `summary`, and `cell_evaluations`; scalar Objectives return one Cell with empty `dimensions`, while dimensional Objectives return one row per observed Cell with dimension values, health status, long/short observed values, target, row count, and ratio total events when applicable. ### Minimum-volume guard A Cell whose evaluation window holds too few events for its percentile or ratio to be statistically trustworthy is reported `OBJECTIVE_HEALTH_STATUS_LOW_VOLUME` instead of being evaluated, so a single outlier in a low-traffic Cell cannot trip a breach. Like `NO_DATA`, a `LOW_VOLUME` Cell never opens an Investigation and never clears the trigger episode; it is counted separately from `no_data` in `summary`. The floor is `trigger.rate_multiplier.min_volume`, measured over the short window. When unset (`0`) the server applies a conservative per-kind default: a small event count for RATIO (from the Indicator's `total_events`), and a small number of populated data points for GAUGE. Set `min_volume` explicitly to raise or lower the floor for a given Objective. The periodic evaluator writes the same bounded health shape to `Objective.latest_evaluation` so readers can render current health without running a fresh Indicator query: | Field | Type | Description | |:------|:-----|:------------| | `health_status` | ObjectiveHealthStatus | Overall Objective health: `OBJECTIVE_HEALTH_STATUS_HEALTHY`, `OBJECTIVE_HEALTH_STATUS_UNHEALTHY`, `OBJECTIVE_HEALTH_STATUS_LOW_VOLUME`, or `OBJECTIVE_HEALTH_STATUS_NO_DATA` | | `summary` | ObjectiveHealthSummary | Aggregate counts: `unhealthy`, `healthy`, `low_volume`, and `no_data` | | `evaluation_time` | timestamp | Time the evaluator produced this snapshot | | `cell_evaluations` | ObjectiveCellEvaluation[] | Bounded, priority-sorted per-Cell rows. Truncated snapshots set `cell_evaluations_truncated = true` | | `total_cells` | int32 | Full number of Cells evaluated before truncation | | `cell_evaluations_truncated` | bool | True when the server kept only the highest-priority Cell rows | | `execution_status` | google.rpc.Status | Set when evaluation failed; nil or OK means the snapshot ran successfully | ### ObjectiveTriggerState `trigger_state` is not an alert history or a copy of Objective evaluations. It is bounded coordination state for the current unhealthy episode so the evaluator can dedupe automated Investigation check-ins without storing every evaluation row in Postgres. `NO_DATA` and `LOW_VOLUME` leave this state intact because an inconclusive evaluation does not prove recovery; only a fully healthy observed evaluation (every Cell `HEALTHY`) clears it. | Field | Type | Description | |:------|:-----|:------------| | `unhealthy_scope_fingerprint` | string | Stable hash of the Objective name plus the current set of unhealthy Cell identities. Metric values, severity, and timestamps are excluded so small fluctuations do not create new episodes | | `first_unhealthy_time` | timestamp | First time this continuous unhealthy episode was observed | | `last_investigation_time` | timestamp | Last automated Investigation check-in started for this episode | | `last_investigation_session` | string | Last automated Investigation session created for this episode. Format: `agents/{agent}/sessions/{session}` | | `investigation_count` | int32 | Number of automated Investigation check-ins started for the current unhealthy scope fingerprint. Used only to derive backoff | | `next_investigation_time` | timestamp | Earliest time the evaluator may start another automated Investigation check-in for this episode | ### ObjectiveHealthStatus | Value | Description | |:------|:------------| | `OBJECTIVE_HEALTH_STATUS_UNSPECIFIED` | Default zero value; not a persisted health state | | `OBJECTIVE_HEALTH_STATUS_HEALTHY` | No evaluated Cell is unhealthy and at least one Cell has data | | `OBJECTIVE_HEALTH_STATUS_UNHEALTHY` | At least one Cell crossed the Objective target in both long and short windows | | `OBJECTIVE_HEALTH_STATUS_NO_DATA` | No usable evaluation data, or evaluation could not produce a health result | | `OBJECTIVE_HEALTH_STATUS_LOW_VOLUME` | A Cell had data but too few samples in the evaluation window for its percentile or ratio to be trustworthy, so it is reported rather than evaluated. Inconclusive like `NO_DATA` — it never opens an Investigation and never clears `trigger_state` — but distinct so clients can surface "low volume" rather than "no data". See [Minimum-volume guard](#minimum-volume-guard) | ## Calibration and activation New Objectives start in `CALIBRATING`: the evaluator gathers up to seven daily baseline snapshots, but no Investigations are triggered yet. While calibrating, the working `definition` and details are revisable in place — agent-recommended Objectives arrive concept-only (no definition), and the per-Objective expert agent tunes the working target against the accruing observations through its calibration-gated tooling. Activation (`CALIBRATING` → `ACTIVE`) is human-driven, by either path: - **`ActivateObjective`** — direct, optionally applying a `threshold_override` atomically. The override is **required** when the Objective has no definition yet; activation is where a target is first committed, so a target-less activate returns `INVALID_ARGUMENT`. - **An accepted `KIND_ACTIVATE` recommended change** — the expert-authored path: the card carries the settled `gauge`/`ratio` plus the reasoning grounded in the observed distribution, and accepting it applies the target and activates in one transaction. Pass `calibration_config.skip = true` on create (with a `definition`) to start `ACTIVE` immediately. ## Providers Providers track the external infrastructure and dependencies a customer's system relies on — clouds (AWS, GCP), databases (Postgres, MySQL, ClickHouse), AI APIs (Anthropic, OpenAI), and the like. A Provider is a sibling to a [Service](services.txt): it carries a human description, a durable agent-curated `context` describing the Provider, and its own observability surface (Objectives and triggered Investigations). **Service**: `firetiger.observability.v1.ProviderCatalogService` **Resource name pattern**: `providers/{provider_id}` **Access**: Read-only > **The Provider catalog is frozen.** Provider discovery no longer runs, Provider > Experts are no longer woken, and the catalog no longer accepts changes. Every > existing Provider stays readable so you can review and export it, but the write > methods below return `FAILED_PRECONDITION`. See > [Exporting your catalog](services.txt#exporting-your-catalog) — Providers > export the same way Services do, from > `/v1/catalog/providers?showDeleted=true`. ## Methods The standard AIP-compliant CRUD surface plus a recommendation-lifecycle surface is available. Request/response shapes are in `proto/firetiger/observability/v1/providers.proto` and follow the same patterns as other resources documented here (e.g. [Services](services.txt)). Available: | RPC | HTTP | |:----|:-----| | `GetProvider` | `GET /v1/catalog/{name=providers/*}` | | `ListProviders` | `GET /v1/catalog/providers` | | `GetProviderRecommendations` | `GET /v1/catalog/{name=providers/*}/recommendations` | Frozen — these return `FAILED_PRECONDITION` with reason `FEATURE_READ_ONLY`: | RPC | HTTP | |:----|:-----| | `CreateProvider` | `POST /v1/catalog/providers` | | `UpdateProvider` | `PATCH /v1/catalog/{provider.name=providers/*}` | | `DeleteProvider` | `DELETE /v1/catalog/{name=providers/*}` | | `RecommendProvider` | `POST /v1/catalog/providers:recommend` | | `AcceptProvider` | `POST /v1/catalog/{name=providers/*}:accept` | | `ArchiveProvider` | `POST /v1/catalog/{name=providers/*}:archive` | | `RestoreProvider` | `POST /v1/catalog/{name=providers/*}:restore` | | `RecreateProviderExpertSession` | `POST /v1/catalog/{name=providers/*}:recreateExpertSession` | ## Recommendation lifecycle Providers are first-class resources that carry a `state` and flow through the same recommendation lifecycle as Services: - **Detection** stores newly-discovered Providers via `RecommendProvider`. The recommended Provider is persisted as a real resource in `PROVIDER_STATE_RECOMMENDED` and its Provider Expert is provisioned (best-effort) to answer questions and review coverage. - A customer **accepts** a recommendation (`AcceptProvider`) to promote it to `PROVIDER_STATE_ACTIVE`, or **archives** it (`ArchiveProvider`) to dismiss it into `PROVIDER_STATE_ARCHIVED`. **Restore** (`RestoreProvider`) brings an archived Provider back. User-created Providers default to `PROVIDER_STATE_ACTIVE`. A Provider's Objectives are ordinary [Objectives](objectives.txt) that set `owner_resource` to the Provider's name (`providers/{provider}`). Beyond the lifecycle RPCs, `GetProviderRecommendations` (see [Recommended Connections](#recommended-connections)) surfaces the Connection(s) recommended to observe a Provider. Provider Monitoring (running checks against a Provider) is a separate follow-up and not yet part of this API. Recommendation metadata is carried on the resource itself: `recommendation_confidence` and `recommendation_reasoning` (see field table below). ## Recommended Connections `GetProviderRecommendations` (no side effects) returns, for the Provider's `provider_type`, the Connection(s) recommended to observe it: - `recommended_connection_types` — `ConnectionType` enum **string keys** (e.g. `"CONNECTION_TYPE_POSTGRES"`) for the Connection(s) Firetiger needs to observe the Provider. Empty for context-only Providers whose type Firetiger can represent but cannot yet observe through a first-class Connection. String keys, not the typed enum, keep `observability.v1` decoupled from `connections.v1`. Provider Monitoring — running connection-gated health checks against a Provider — is a separate follow-up and is not yet exposed on this service. ## Provider resource | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | Output only | Format: `providers/{provider}` | | `display_name` | string | Required | Short, scannable label shown in headers and Provider lists (1–200 chars) | | `description` | string | Optional | One-paragraph, human-facing overview of what the Provider is | | `context` | string | Optional | Durable, structured Markdown describing the Provider, read by agents and rendered on the Provider page. Agents curate it into canonical sections (purpose, code references, dependent services, telemetry signature, invariants, failure modes and known bugs); it holds steady-state knowledge, not point-in-time evidence or timestamps. Agent-proposed at recommend time; customer-editable | | `expert_session` | string | Output only | The per-Provider `provider-expert` agent session that answers questions and periodically reviews this Provider's dependencies, Connections, telemetry, and Objectives. Format: `agents/{agent}/sessions/{session}` | | `provider_type` | ProviderType | Optional | The kind of Provider (AWS, Postgres, Anthropic, …). The typed join key to the brand logo and recommended Connections, resolved through a registry. See [ProviderType](#providertype) | | `state` | ProviderState | Output only | Lifecycle state. User-created Providers default to `PROVIDER_STATE_ACTIVE`; detected Providers persist in `PROVIDER_STATE_RECOMMENDED` until accepted or archived. See [ProviderState](#providerstate) | | `recommendation_confidence` | RecommendationConfidence | Optional | Agent's confidence in this recommendation. See [RecommendationConfidence](#recommendationconfidence) | | `recommendation_reasoning` | string | Optional | Free-text rationale for why this Provider was recommended | | `etag` | string | Output only | Optimistic-concurrency token returned by `Get` and required on `Update`; stale etags fail with `ABORTED` | | `create_time` / `update_time` / `delete_time` | timestamp | Output only | Standard AIP lifecycle timestamps | `UpdateProvider` is field-masked: send the `Provider` with an `update_mask` listing the paths to write (e.g. `context`). `state`, `expert_session`, and the other output-only fields are server-managed and ignored on `Create`/`Update`. ## Enums ### ProviderState | Value | Description | |:------|:------------| | `PROVIDER_STATE_UNSPECIFIED` | Default zero value; not a valid persisted state | | `PROVIDER_STATE_RECOMMENDED` | Detected Provider awaiting accept/archive | | `PROVIDER_STATE_ACTIVE` | Accepted (or user-created) Provider | | `PROVIDER_STATE_ARCHIVED` | Dismissed recommendation; hidden from active views | ### RecommendationConfidence | Value | Description | |:------|:------------| | `RECOMMENDATION_CONFIDENCE_UNSPECIFIED` | Confidence not set | | `RECOMMENDATION_CONFIDENCE_HIGH` | High confidence | | `RECOMMENDATION_CONFIDENCE_MEDIUM` | Medium confidence | | `RECOMMENDATION_CONFIDENCE_LOW` | Low confidence | ### ProviderType The bounded taxonomy of Provider kinds. Independent of `firetiger.connections.v1.ConnectionType` — the `ProviderType → ConnectionType` / logo mapping lives in registries. New kinds are added here as detection grows. | Group | Values | |:------|:-------| | Clouds / PaaS | `PROVIDER_TYPE_AWS`, `PROVIDER_TYPE_GCP`, `PROVIDER_TYPE_VERCEL`, `PROVIDER_TYPE_SUPABASE` | | Databases / data stores | `PROVIDER_TYPE_POSTGRES`, `PROVIDER_TYPE_MYSQL`, `PROVIDER_TYPE_CLICKHOUSE`, `PROVIDER_TYPE_ELASTICSEARCH` | | AI APIs | `PROVIDER_TYPE_ANTHROPIC`, `PROVIDER_TYPE_OPENAI`, `PROVIDER_TYPE_TOGETHER_AI`, `PROVIDER_TYPE_DEEPSEEK`, `PROVIDER_TYPE_BASETEN` | | Durable execution / workflow orchestration | `PROVIDER_TYPE_TEMPORAL` | ## Notes - A Provider mirrors a [Service](services.txt)'s recommendation shape, minus the Service-only breakdown Dimensions. - The Provider lifecycle RPCs manage the Provider resource only; provider Objectives and their triaged Investigations are owned by the provider monitoring work. ## Services The Services catalog is the system map agents use to reason about ownership, blast radius, and which components show up in a given [Flow](flows.txt). Each Service entry carries a human description, a durable agent-curated `context` describing the Service, and the breakdown Dimensions its health is measured along. **Service**: `firetiger.observability.v1.ServiceCatalogService` **Resource name pattern**: `services/{service_id}` **Access**: Read-only > **The Services catalog is frozen.** Service discovery no longer runs, Service > Experts are no longer woken, and the catalog no longer accepts changes. Every > existing Service and ServiceDimension stays readable so you can review and > export it, but the write methods below return `FAILED_PRECONDITION`. See > [Exporting your catalog](#exporting-your-catalog). ## Methods The standard AIP-compliant CRUD surface plus a recommendation-lifecycle surface is available. Request/response shapes are in `proto/firetiger/observability/v1/services.proto` and follow the same patterns as other resources documented here (e.g. [Tags](tags.txt)). ### Service methods Available: | RPC | HTTP | |:----|:-----| | `GetService` | `GET /v1/catalog/{name=services/*}` | | `ListServices` | `GET /v1/catalog/services` | Frozen — these return `FAILED_PRECONDITION` with reason `FEATURE_READ_ONLY`: | RPC | HTTP | |:----|:-----| | `CreateService` | `POST /v1/catalog/services` | | `UpdateService` | `PATCH /v1/catalog/{service.name=services/*}` | | `DeleteService` | `DELETE /v1/catalog/{name=services/*}` | | `RecommendService` | `POST /v1/catalog/services:recommend` | | `AcceptService` | `POST /v1/catalog/{name=services/*}:accept` | | `ArchiveService` | `POST /v1/catalog/{name=services/*}:archive` | | `RestoreService` | `POST /v1/catalog/{name=services/*}:restore` | | `RecommendServiceObservability` | `POST /v1/catalog/{parent=services/*}:recommendObservability` | | `RecreateServiceExpertSession` | `POST /v1/catalog/{name=services/*}:recreateExpertSession` | | `RefreshServiceContributors` | `POST /v1/catalog/{name=services/*}:refreshContributors` | ### ServiceDimension methods A `ServiceDimension` is a service-local health breakdown axis nested under a Service (`services/{service}/dimensions/{dimension}`). It replaces the deprecated top-level `Service.dimensions` list for Service observability: Objectives and Indicators bind to a `ServiceDimension`. Available: | RPC | HTTP | |:----|:-----| | `GetServiceDimension` | `GET /v1/catalog/{name=services/*/dimensions/*}` | | `ListServiceDimensions` | `GET /v1/catalog/{parent=services/*}/dimensions` | Frozen — these return `FAILED_PRECONDITION` with reason `FEATURE_READ_ONLY`: | RPC | HTTP | |:----|:-----| | `CreateServiceDimension` | `POST /v1/catalog/{parent=services/*}/dimensions` | | `UpdateServiceDimension` | `PATCH /v1/catalog/{service_dimension.name=services/*/dimensions/*}` | | `DeleteServiceDimension` | `DELETE /v1/catalog/{name=services/*/dimensions/*}` | | `AcceptServiceDimension` | `POST /v1/catalog/{name=services/*/dimensions/*}:accept` | | `ArchiveServiceDimension` | `POST /v1/catalog/{name=services/*/dimensions/*}:archive` | | `RestoreServiceDimension` | `POST /v1/catalog/{name=services/*/dimensions/*}:restore` | ## Exporting your catalog `ListServices` returns the full resource, so one paginated sweep is a complete export — description, the agent-curated `context`, code location, contributors, and state: Authenticate with an [API key](../account-management/api_keys.txt) — HTTP Basic, the same as every other programmatic call: `showDeleted=true` matters: List omits soft-deleted records by default, and the freeze means a Service you deleted can no longer be undeleted — so without it those rows are gone for good. ```bash curl -s -u "$FT_API_KEY_USERNAME:$FT_API_KEY_PASSWORD" \ "https://api./v1/catalog/services?pageSize=500&showDeleted=true" > services.json ``` Follow `nextPageToken` if you have more than one page. Dimensions are a second sweep per Service: ```bash curl -s -u "$FT_API_KEY_USERNAME:$FT_API_KEY_PASSWORD" \ "https://api./v1/catalog/services//dimensions?showDeleted=true" ``` Providers export the same way from `/v1/catalog/providers`. This catalog has no `ftops api` subcommand — use the REST endpoints above. For the Objectives that hang off these Services, see [Exporting your Objectives](objectives.txt#exporting-your-objectives). ## Recommendation lifecycle This lifecycle no longer runs — nothing is discovered, proposed, or accepted. It is documented because the `state` values it produced are on the resources you are reading and exporting, and a Service left in `SERVICE_STATE_RECOMMENDED` will stay there. Services and ServiceDimensions are both first-class catalog resources that carry a `state`, and both flow through the same recommendation lifecycle: - **System discovery** stores newly-discovered catalog entries via `RecommendService`. The recommended Service is persisted as a real resource in `SERVICE_STATE_RECOMMENDED` and its Service Expert is provisioned immediately. - Each Service's Expert then proposes its breakdown dimensions, Objectives, and backing Indicators via `RecommendServiceObservability`. Recommended ServiceDimensions land in `SERVICE_DIMENSION_STATE_RECOMMENDED`. - A customer **accepts** a recommendation (`AcceptService` / `AcceptServiceDimension`) to promote it to the `*_ACTIVE` state, or **archives** it (`ArchiveService` / `ArchiveServiceDimension`) to dismiss it into the `*_ARCHIVED` state. **Restore** (`RestoreService` / `RestoreServiceDimension`) brings an archived resource back. User-created Services default to `SERVICE_STATE_ACTIVE`. - `AcceptService` bulk-activates the Service's accepted breakdown and Objectives; archived child Objectives are skipped. Recommendation metadata is carried on the resource itself: `recommendation_confidence`, `recommendation_evidence`, and `recommendation_reasoning` (see field tables below). ## Service resource | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | Output only | Format: `services/{service}` | | `display_name` | string | Required | Short, scannable label shown in headers and Service lists (1–200 chars) | | `description` | string | Optional | One-paragraph, human-facing overview of what the Service is | | `context` | string | Optional | Durable, structured Markdown describing the Service, read by agents and rendered on the Service page. Agents curate it into canonical sections (purpose, code references, Service and Provider dependencies, invariants, failure modes and known bugs); it holds steady-state knowledge, not point-in-time evidence or timestamps. Customer-editable | | `expert_session` | string | Output only | The per-Service `service-expert` agent session that proposes and maintains this Service's Objectives, Indicators, and Dimensions. Format: `agents/{agent}/sessions/{session}` | | `dimensions` | string[] | Optional, **deprecated** | The `firetiger.dimensions.v1` Dimensions this Service's health is broken down by — its declared deployment shape (e.g. environment, region, cloud). Each entry is a `dimensions/{dimension}` resource name. Superseded by the nested `ServiceDimension` resource; use `CreateServiceDimension`/`ListServiceDimensions` instead | | `state` | ServiceState | Output only | Lifecycle state. User-created Services default to `SERVICE_STATE_ACTIVE`; system-recommended Services persist in `SERVICE_STATE_RECOMMENDED` until accepted or archived. See [ServiceState](#servicestate) | | `recommendation_confidence` | RecommendationConfidence | Optional | Agent's confidence in this recommendation. See [RecommendationConfidence](#recommendationconfidence) | | `recommendation_evidence` | string[] | Optional | Supporting evidence the agent cited when recommending this Service | | `recommendation_reasoning` | string | Optional | Free-text rationale for why this Service was recommended | | `etag` | string | Output only | Optimistic-concurrency token returned by `Get` and required on `Update`; stale etags fail with `ABORTED` | | `create_time` / `update_time` / `delete_time` | timestamp | Output only | Standard AIP lifecycle timestamps | `UpdateService` is field-masked: send the `Service` with an `update_mask` listing the paths to write (e.g. `context`). `state`, `expert_session`, and the other output-only fields are server-managed and ignored on `Create`/`Update`. ## ServiceDimension resource | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | Output only | Format: `services/{service}/dimensions/{dimension}` | | `display_name` | string | Required | Short, scannable label for the breakdown axis (1–200 chars) | | `description` | string | Optional | One-line, human-facing description of what this axis splits on | | `state` | ServiceDimensionState | Output only | Lifecycle state. Recommended dimensions persist in `SERVICE_DIMENSION_STATE_RECOMMENDED` until accepted or archived. See [ServiceDimensionState](#servicedimensionstate) | | `source_column` | string | Optional | Suggested or observed query output column for this axis. Indicator `DimensionBinding`s still carry the authoritative per-query column mapping; this lets the Service page preview recommended dimensions before an Objective is opened | | `recommendation_confidence` | RecommendationConfidence | Optional | Agent's confidence in this recommendation. See [RecommendationConfidence](#recommendationconfidence) | | `recommendation_evidence` | string[] | Optional | Supporting evidence the agent cited when recommending this dimension | | `recommendation_reasoning` | string | Optional | Free-text rationale for why this dimension was recommended | | `etag` | string | Output only | Optimistic-concurrency token returned by `Get` and required on `Update`; stale etags fail with `ABORTED` | | `create_time` / `update_time` / `delete_time` | timestamp | Output only | Standard AIP lifecycle timestamps | `UpdateServiceDimension` is field-masked: send the `ServiceDimension` with an `update_mask` of the paths to write. Output-only fields (`state`, `etag`, timestamps) are server-managed and ignored on `Create`/`Update`. ## Enums ### ServiceState | Value | Description | |:------|:------------| | `SERVICE_STATE_UNSPECIFIED` | Default zero value; not a valid persisted state | | `SERVICE_STATE_RECOMMENDED` | System-recommended Service awaiting accept/archive | | `SERVICE_STATE_ACTIVE` | Accepted (or user-created) Service | | `SERVICE_STATE_ARCHIVED` | Dismissed recommendation; hidden from active views | ### ServiceDimensionState | Value | Description | |:------|:------------| | `SERVICE_DIMENSION_STATE_UNSPECIFIED` | Default zero value; not a valid persisted state | | `SERVICE_DIMENSION_STATE_RECOMMENDED` | Recommended breakdown axis awaiting accept/archive | | `SERVICE_DIMENSION_STATE_ACTIVE` | Accepted dimension | | `SERVICE_DIMENSION_STATE_ARCHIVED` | Dismissed recommendation | ### RecommendationConfidence | Value | Description | |:------|:------------| | `RECOMMENDATION_CONFIDENCE_UNSPECIFIED` | Confidence not set | | `RECOMMENDATION_CONFIDENCE_HIGH` | High confidence | | `RECOMMENDATION_CONFIDENCE_MEDIUM` | Medium confidence | | `RECOMMENDATION_CONFIDENCE_LOW` | Low confidence | ## Revision history (AIP-162) Revision history is served by the legacy `firetiger.objectives.v1.ServicesService`, which exposes the same `services/{service}` resource plus two read RPCs. Every mutation (`Create`, `Update`, `Delete`, `Undelete`) atomically writes a revision row recording **who** changed the resource, **what** kind of change, and **when**. ### ListServiceRevisions List the revision history of one Service. Default order is reverse-chronological (newest first). ``` GET /v1/{parent=services/*}/revisions ``` or as a Connect RPC: ``` POST /firetiger.objectives.v1.ServicesService/ListServiceRevisions ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `parent` | string | Yes | Resource name of the Service whose revisions to list. Format: `services/{service}` | | `page_size` | integer | No | Maximum revisions per page | | `page_token` | string | No | Token from a previous `ListServiceRevisionsResponse` | | `filter` | string | No | [AIP-160 filter](https://google.aip.dev/160) over `name`, `revision_number`, `operation`, `actor_subject`, `actor_kind`, `create_time` | | `order_by` | string | No | AIP-132 ordering. Defaults to `create_time desc` | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.objectives.v1.ServicesService/ListServiceRevisions" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"parent": "services/checkout-api", "page_size": 50}' ``` **Response** ```json { "serviceRevisions": [ { "name": "services/checkout-api/revisions/3", "snapshot": { "name": "services/checkout-api", "displayName": "Checkout API v3", "...": "..." }, "createTime": "2026-05-14T18:10:11Z", "operation": "REVISION_OPERATION_UPDATE", "actor": { "subject": "user_2abc", "kind": "ACTOR_KIND_USER" } } ], "nextPageToken": "" } ``` ### GetServiceRevision Return a single revision by its AIP-162 nested-collection name. ``` GET /v1/{name=services/*/revisions/*} ``` or: ``` POST /firetiger.objectives.v1.ServicesService/GetServiceRevision ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Format: `services/{service}/revisions/{n}` where `n` is a positive integer | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.objectives.v1.ServicesService/GetServiceRevision" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "services/checkout-api/revisions/2"}' ``` ## Notes - **History starts at opt-in time.** Services that existed before this revision API shipped have no historical revisions until their next mutation. - **`Purge`** (force delete) cascades to revisions atomically. - See [Flows](flows.txt) for the same revision pattern applied to the Flows resource. ## Billing The billing service exposes read access to your account's metered usage — both the current billing period and a 12-month history broken down by individual agent. Use it to render usage charts, build internal cost-attribution reports, or warn users before they cross a paid-tier threshold. **Service**: `firetiger.billing.v1.BillingService` **Access**: Read-only ## Example flow Read the current period's headline counters, then pull a 6-month per-agent history for a cost-attribution view. **1. Get current-period usage** ```bash curl -X POST "{{ site.api_url }}/firetiger.billing.v1.BillingService/GetBillingUsage" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{}' ``` ```json { "agentOperationCount": "8430", "agentOperationLimit": "25000", "periodStart": "2024-06-01T00:00:00Z", "periodEnd": "2024-07-01T00:00:00Z", "dailyFreeUsage": "120", "dailyFreeLimit": "300", "paidUsage": "1530", "paidLimit": "25000", "periodUsageBySource": [ {"sourceType": "BILLING_EVENT_SOURCE_CHANGE_MONITOR", "paidOperationCount": "900"}, {"sourceType": "BILLING_EVENT_SOURCE_CUSTOM_AGENT", "paidOperationCount": "500"}, {"sourceType": "BILLING_EVENT_SOURCE_INVESTIGATION", "paidOperationCount": "130"} ] } ``` **2. List the last 6 months of per-agent usage** ```bash curl -X POST "{{ site.api_url }}/firetiger.billing.v1.BillingService/ListUsageHistory" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"months": 6}' ``` ```json { "months": [ { "periodStart": "2024-01-01T00:00:00Z", "periodEnd": "2024-02-01T00:00:00Z", "totalOperations": "4200", "paidOperations": "600", "estimatedCostCents": "2000", "byAgent": [ { "source": "agents/my-agent", "sourceType": "BILLING_EVENT_SOURCE_CUSTOM_AGENT", "displayName": "Production change monitor", "totalOperations": "3000", "paidOperations": "450" }, { "source": "investigations", "sourceType": "BILLING_EVENT_SOURCE_INVESTIGATION", "displayName": "Investigations", "totalOperations": "1200", "paidOperations": "150" } ] } ] } ``` ## Methods | Method | Description | |:-------|:------------| | [GetBillingUsage](#getbillingusage) | Read current-period usage with a per-surface breakdown | | [ListUsageHistory](#listusagehistory) | List per-month, per-agent historical usage with estimated cost | --- ## GetBillingUsage Reports metered usage for the current billing period — daily free credits (300/day, resets at UTC midnight), paid allocation, and period boundaries. Use this to render a usage meter or to decide whether to throttle agent kicks before overage. ``` POST /firetiger.billing.v1.BillingService/GetBillingUsage ``` **Request body** No fields. The account is identified from the authenticated session. **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.billing.v1.BillingService/GetBillingUsage" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{}' ``` **Response** | Field | Type | Description | |:------|:-----|:------------| | `agentOperationCount` | int64 | Total operations metered in the current period across all sources | | `agentOperationLimit` | int64 | The headline operation limit applied to this account (plan-dependent) | | `periodStart` | timestamp | Inclusive UTC midnight start of the current billing period | | `periodEnd` | timestamp | Exclusive UTC midnight end of the current billing period | | `dailyFreeUsage` | int64 | Operations counted against today's free-credit pool | | `dailyFreeLimit` | int64 | The daily free-credit allowance (300) | | `paidUsage` | int64 | Operations above the per-day free credit, summed across days in this period | | `paidLimit` | int64 | Paid-allocation ceiling for this plan (0 for free accounts — all overage bills) | | `periodUsageBySource` | repeated UsageBySource | Paid operations split by product surface (change monitor / custom agent / investigation) | `UsageBySource` fields: | Field | Type | Description | |:------|:-----|:------------| | `sourceType` | BillingEventSource | Product surface that produced the operations | | `paidOperationCount` | int64 | Paid operations attributable to this source after pro-rata allocation of the daily free credit. Summing across rows equals `paidUsage`. | ```json { "agentOperationCount": "8430", "agentOperationLimit": "25000", "periodStart": "2024-06-01T00:00:00Z", "periodEnd": "2024-07-01T00:00:00Z", "dailyFreeUsage": "120", "dailyFreeLimit": "300", "paidUsage": "1530", "paidLimit": "25000", "periodUsageBySource": [ {"sourceType": "BILLING_EVENT_SOURCE_CHANGE_MONITOR", "paidOperationCount": "900"}, {"sourceType": "BILLING_EVENT_SOURCE_CUSTOM_AGENT", "paidOperationCount": "500"}, {"sourceType": "BILLING_EVENT_SOURCE_INVESTIGATION", "paidOperationCount": "130"} ] } ``` --- ## ListUsageHistory Returns per-month usage with a per-agent breakdown for the last N billing periods. Investigation events are rolled up into a single synthetic entry per period (`source = "investigations"`), since individual investigation IDs are ephemeral. ``` POST /firetiger.billing.v1.BillingService/ListUsageHistory ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `months` | int32 | No | Number of recent billing periods to return. Defaults to 12, clamped to `[1, 24]`. | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.billing.v1.BillingService/ListUsageHistory" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"months": 6}' ``` **Response** | Field | Type | Description | |:------|:-----|:------------| | `months` | repeated MonthlyUsage | One entry per billing period that contains usage in the requested window, oldest-first. May be shorter than the requested `months` if the account is younger than that. | `MonthlyUsage` fields: | Field | Type | Description | |:------|:-----|:------------| | `periodStart` | timestamp | Inclusive UTC midnight start of the billing period | | `periodEnd` | timestamp | Exclusive UTC midnight end of the billing period | | `totalOperations` | int64 | Total operations metered in this period across all sources | | `paidOperations` | int64 | Operations after the 300/day free credit, summed across sources | | `estimatedCostCents` | int64 | Estimated total dollar cost for this period (base subscription + overage), in cents. Computed as `current_base_plan_per_month + ceil(max(paid_operations − included_allocation, 0) / 600) × current overage rate`, where `included_allocation` is what the plan bundles into its base fee (0 for Bootstrap/free, 25,000 for Growth). Uses the *current* plan and rates against historical paid operations — it's an estimate, not an invoice. Annual subscriptions are divided by 12 so each monthly bucket reflects one month's share. | | `byAgent` | repeated AgentUsage | Per-agent breakdown, sorted by `totalOperations` descending | `AgentUsage` fields: | Field | Type | Description | |:------|:-----|:------------| | `source` | string | Resource path of the originating agent, e.g. `agents/my-agent`. The synthetic value `investigations` represents the rolled-up investigations bucket. | | `sourceType` | BillingEventSource | Source-type classification carried through for tooltip / filter use | | `displayName` | string | Human-readable label. Falls back to the source path when the agent's title can't be resolved (e.g. it was deleted). | | `totalOperations` | int64 | Total operations attributed to this agent in the period | | `paidOperations` | int64 | Operations after the pro-rata daily free-credit allocation. Summing across `byAgent` rows equals `MonthlyUsage.paidOperations`. | ```json { "months": [ { "periodStart": "2024-01-01T00:00:00Z", "periodEnd": "2024-02-01T00:00:00Z", "totalOperations": "4200", "paidOperations": "600", "estimatedCostCents": "2000", "byAgent": [ { "source": "agents/my-agent", "sourceType": "BILLING_EVENT_SOURCE_CUSTOM_AGENT", "displayName": "Production change monitor", "totalOperations": "3000", "paidOperations": "450" }, { "source": "investigations", "sourceType": "BILLING_EVENT_SOURCE_INVESTIGATION", "displayName": "Investigations", "totalOperations": "1200", "paidOperations": "150" } ] } ] } ``` ## BillingEventSource Classifies a billable operation by the product surface that produced it. | Value | Description | |:------|:------------| | `BILLING_EVENT_SOURCE_UNSPECIFIED` | Unknown / legacy event with no recorded source | | `BILLING_EVENT_SOURCE_CHANGE_MONITOR` | Operations triggered by a change monitor (monitoring plan authoring or execution) | | `BILLING_EVENT_SOURCE_CUSTOM_AGENT` | Operations from user-created custom agents | | `BILLING_EVENT_SOURCE_INVESTIGATION` | Operations from the investigations surface | ## Network Transports Network transports are the overlay networks Firetiger agents use to reach customer-side systems that aren't reachable from the public internet ([Tailscale](https://tailscale.com/) or a SOCKS5 proxy). A transport holds the provider credentials and policy; sibling resources (such as Connections) reference a transport by `name` when they need egress into your private network. The secret you supply (`tailscale.oauthClientSecret` or `socks5.password`) is stored in a secrets manager. `Create`, `Update`, and `List` responses omit it, but **`GetNetworkTransport` returns the stored secret** — treat `Get` responses as sensitive (don't log or expose them). **Service**: `firetiger.network_transports.v1.NetworkTransportsService` **Resource name pattern**: `network-transports/{network_transport}` **Access**: Read-write ## Example flow Create a Tailscale transport, then reference it by `name` from a connection that needs to reach a private host. **1. Create a transport** ```bash curl -X POST "{{ site.api_url }}/firetiger.network_transports.v1.NetworkTransportsService/CreateNetworkTransport" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "networkTransportId": "prod-vpc", "networkTransport": { "displayName": "Production VPC", "description": "Egress into the prod VPC via Tailscale", "tailscale": { "oauthClientId": "k123abc", "oauthClientSecret": "tskey-client-...", "tailnet": "example.com", "tags": ["tag:firetiger"] } } }' ``` ```json { "networkTransport": { "name": "network-transports/prod-vpc", "displayName": "Production VPC", "description": "Egress into the prod VPC via Tailscale", "tailscale": { "oauthClientId": "k123abc", "tailnet": "example.com", "tags": ["tag:firetiger"] }, "createTime": "2026-05-27T00:00:00Z" } } ``` Note the create response omits `oauthClientSecret`. ([`Get`](#getnetworktransport) re-populates it from the secrets store.) ## Transport details (Tailscale) The `tailscale` field carries the provider-specific configuration. Firetiger joins your tailnet as an ephemeral, preauthorized node using these credentials. | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `oauthClientId` | string | Yes | Tailscale OAuth client ID. | | `oauthClientSecret` | string | Yes | Tailscale OAuth client secret. Stored in a secrets manager. Omitted from `Create`/`Update`/`List` responses, but **returned by `Get`**. | | `tailnet` | string | Yes | The tailnet to join (e.g. `example.com` or `example.ts.net`). | | `hostname` | string | No | Hostname for the proxy node. Defaults to an auto-generated `ft-proxy-*` name. | | `tags` | string[] | No | ACL tags assigned to the proxy node. Must be a subset of the tags authorized for the OAuth client (e.g. `["tag:firetiger"]`). | | `acceptDns` | bool | No | See [DNS resolution](#dns-resolution-accept_dns) below. Defaults to `false`. | ### DNS resolution (`accept_dns`) By default (`acceptDns: false`), Firetiger resolves target hostnames using its own resolver: it first tries the tailnet's MagicDNS, then falls back to the standard public resolver. This handles the common case — including public hostnames that resolve to **private** addresses reachable through a [subnet router](https://tailscale.com/kb/1019/subnets) (for example, an `*.rds.amazonaws.com` endpoint mapping to a VPC-internal IP). Set `acceptDns: true` only when your target hostnames can **only** be resolved by a private nameserver inside your network — for example, split-DNS or restricted nameservers configured in the Tailscale admin console (internal zones served by a private DNS server). This is equivalent to `tailscale up --accept-dns` and makes the node use the tailnet's DNS configuration. > Enabling `acceptDns` is **not** purely additive. If your tailnet is > configured to override local DNS, routing resolution through the tailnet's > nameservers can prevent resolution of public names (such as RDS endpoints) > that resolve correctly with the default resolver. Leave it `false` unless > you specifically need private-nameserver resolution. ## Transport details (SOCKS5) Alternatively, set the `socks5` field to route through a SOCKS5 proxy (for example, a bastion host that fronts your private network). The Firetiger proxy opens a connection to the SOCKS5 server, which dials the target on its behalf — so the target only needs to be reachable from the SOCKS5 proxy, not from Firetiger directly. | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `host` | string | Yes | SOCKS5 proxy hostname or IP, reachable from Firetiger. | | `port` | int32 | Yes | SOCKS5 proxy port (e.g. `1080`). | | `username` | string | No | Username for SOCKS5 username/password auth (RFC 1929). | | `password` | string | No | Password for SOCKS5 auth. Stored in a secrets manager; omitted from `Create`/`Update`/`List` responses but **returned by `Get`** (like `tailscale.oauthClientSecret`). | ```json { "networkTransport": { "displayName": "Bastion SOCKS5", "socks5": { "host": "bastion.internal", "port": 1080, "username": "ft", "password": "..." } } } ``` ## Methods | Method | Description | |:-------|:------------| | [CreateNetworkTransport](#createnetworktransport) | Register a new transport | | [GetNetworkTransport](#getnetworktransport) | Retrieve a transport by name (returns the stored secret) | | [UpdateNetworkTransport](#updatenetworktransport) | Edit a transport (partial update via `update_mask`) | | [DeleteNetworkTransport](#deletenetworktransport) | Soft-delete a transport | | [ListNetworkTransports](#listnetworktransports) | List transports with filtering + pagination | --- ## CreateNetworkTransport Register a new transport. The `transportDetails` oneof (`tailscale` or `socks5`) picks the provider and carries its required credentials. Secret fields (`tailscale.oauthClientSecret`, `socks5.password`) are stored in a secrets manager and cleared from the create response (though [`Get`](#getnetworktransport) returns them). ``` POST /firetiger.network_transports.v1.NetworkTransportsService/CreateNetworkTransport ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `networkTransportId` | string | No | ID for the new transport (matches `^[a-zA-Z0-9][a-zA-Z0-9_-]*$`). Server-assigned if omitted. | | `networkTransport` | NetworkTransport | Yes | Transport fields. `displayName` is required; set exactly one of the [`tailscale`](#transport-details-tailscale) or [`socks5`](#transport-details-socks5) [transport details](#transport-details-tailscale). | --- ## GetNetworkTransport Retrieve one transport's metadata, including the stored secret (`tailscale.oauthClientSecret` or `socks5.password`, re-fetched from the secrets store and merged into the response). ``` POST /firetiger.network_transports.v1.NetworkTransportsService/GetNetworkTransport ``` > Unlike `Create`, `Update`, and `List` — which omit it — `GetNetworkTransport` > returns the stored secret (`tailscale.oauthClientSecret` / > `socks5.password`) in the response. Treat `Get` responses as sensitive: > don't log them or surface them to untrusted callers. **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name (`network-transports/{id}`) | --- ## UpdateNetworkTransport Edit a transport. Partial updates follow [AIP-134](https://google.aip.dev/134) via `updateMask`. The provider within `transportDetails` cannot be swapped in place — create a new transport and point consumers at it instead. ``` POST /firetiger.network_transports.v1.NetworkTransportsService/UpdateNetworkTransport ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `networkTransport` | NetworkTransport | Yes | Transport object with `name` set | | `updateMask` | FieldMask | No | Fields to update (e.g. `"tailscale.acceptDns"`) | --- ## DeleteNetworkTransport Soft-delete the transport per [AIP-164](https://google.aip.dev/164). Connections that reference it keep the reference but will fail at egress time; reconcile downstream wiring before relying on the delete. ``` POST /firetiger.network_transports.v1.NetworkTransportsService/DeleteNetworkTransport ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name (`network-transports/{id}`) | --- ## ListNetworkTransports Enumerate the organization's transports. Supports [AIP-158](https://google.aip.dev/158) pagination, [AIP-160](https://google.aip.dev/160) `filter` / `orderBy`, and `showDeleted`. ``` POST /firetiger.network_transports.v1.NetworkTransportsService/ListNetworkTransports ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `filter` | string | No | AIP-160 filter expression | | `orderBy` | string | No | Sort order | | `pageSize` | int32 | No | Maximum results per page | | `pageToken` | string | No | Token from a previous `nextPageToken` | | `showDeleted` | bool | No | Include soft-deleted transports | --- ## Autofix **Autofix** automatically launches a [coding agent](coding-agents.txt) to fix an issue the moment it transitions to the `ACTIONABLE` workflow state — no manual click on the issue's **Fix** dropdown required. It is opt-in and configured per organization through a single `AutofixSettings` resource: a toggle plus the coding-agent [Connection](connections.txt) to launch. While enabled, every transition into `ACTIONABLE` launches a session on the selected agent (best-effort — a launch failure never blocks the issue update). Issues created directly in the `ACTIONABLE` state are not auto-fixed; only transitions trigger autofix. **Service**: `firetiger.autofix.v1.AutofixSettingsService` **Resource name pattern**: `autofix-settings/{autofix_settings}` (singleton — the only instance is `autofix-settings/default`) **Access**: Read + update The settings are a singleton: there is no Create or Delete, only Get and Update. `GetAutofixSettings` returns disabled defaults on first read, so it is always safe to call from a fresh onboarding UI. ## Methods | Method | Description | |:-------|:------------| | [GetAutofixSettings](#getautofixsettings) | Retrieve the organization's autofix settings | | [UpdateAutofixSettings](#updateautofixsettings) | Enable/disable autofix and select the coding-agent connection | --- ## GetAutofixSettings ``` GET /v1/autofix-settings/current ``` Returns the singleton `AutofixSettings` for the caller's organization. When no settings have been saved yet, returns the disabled default (`autofix-settings/default`, `enabled: false`, no connection). **Response** ```json { "autofixSettings": { "name": "autofix-settings/default", "enabled": true, "codingAgentConnection": "connections/cursor-prod" } } ``` --- ## UpdateAutofixSettings ``` PATCH /v1/{autofix_settings.name=autofix-settings/*} ``` Edits the `enabled` toggle and/or the selected `coding_agent_connection`. Partial updates follow the [standard field-mask convention](index.txt#partial-updates) via `update_mask`. `enabled: true` requires a `coding_agent_connection` — an update that would leave autofix enabled without a connection is rejected with `INVALID_ARGUMENT`, since autofix could never launch. **Body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `autofix_settings.enabled` | bool | No | Whether autofix is enabled for the organization. | | `autofix_settings.coding_agent_connection` | string | No | Resource name of the coding-agent connection to launch, e.g. `connections/cursor-prod`. Must be a [coding-agent connection type](coding-agents.txt). | | `update_mask` | string | No | Comma-separated field paths to update (e.g. `enabled,codingAgentConnection`). | **Example** ```json { "autofixSettings": { "enabled": true, "codingAgentConnection": "connections/cursor-prod" }, "updateMask": "enabled,codingAgentConnection" } ``` ## Slack `SlackService` sends messages to Slack channels through a configured [Connection](connections.txt), resolving channel names to IDs **server-side from the bot's channel membership** so callers never page Slack's workspace directory themselves. It also exposes a connection health check and a channel listing. **Service**: `firetiger.slack.v1.SlackService` **Access**: Read-write (requires a Slack [Connection](connections.txt), `CONNECTION_TYPE_SLACK`) ## Why this service Slack has no name→ID lookup API. Paging the entire workspace directory (`conversations.list`) to find a channel is rate-limited (Tier 2, ~20 req/min) and, on a large workspace, cannot reach a deep channel within any reasonable budget. So `SlackService` resolves names against `users.conversations` — the (small) set of channels the bot is a **member** of — instead. A channel a human has invited the Firetiger app into resolves in ~1 page; a channel the bot is not in returns `CHANNEL_NOT_FOUND` with instructions to invite the app. Membership is the source of truth for what the bot may post to — there is no configured allowlist. The service still hard-blocks `#general` and DMs server-side, and makes sends idempotent so retries don't double-post. ## SendSlackMessage Resolves each target channel (against the bot's member channels), enforces the `#general`/no-DM rules, and posts. Provide an `idempotency_key` to make a retried request replay the original result instead of re-posting. ```bash curl -X POST "{{ site.api_url }}/firetiger.slack.v1.SlackService/SendSlackMessage" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "connection": "connections/slack-prod", "targets": [{ "channel": "#alerts" }], "text": "Deploy v1.2.3 completed", "idempotency_key": "deploy-v1.2.3-notify" }' ``` ```json { "results": [ { "channel": "#alerts", "channelId": "C0123ABCD", "ts": "1718384000.000100", "permalink": "https://acme.slack.com/archives/C0123ABCD/p1718384000000100" } ] } ``` **Targets** carry the channel plus optional threading: `{ "channel": "#alerts", "thread_ts": "...", "reply_broadcast": true }`. Pass `blocks` (Slack Block Kit JSON) instead of, or alongside, `text`. **Per-channel errors** are returned inline rather than failing the whole call. Each failed result carries a typed `error.kind`: | `kind` | Meaning | | --- | --- | | `CHANNEL_NOT_FOUND` | The bot is not a member of any channel with that name (or no such channel exists). The `detail` tells the user to invite the Firetiger app to the channel (e.g. `/invite @Firetiger`). | | `NOT_ALLOWED` | Channel is `#general` or a DM (both are never allowed). | | `RATE_LIMITED` | Slack rate-limited the operation after retries. | | `THREAD_STALE` | `thread_ts` referenced a deleted parent; resend without it. | | `SLACK_API_ERROR` | Any other Slack API error. | The **`#general` ban and the no-DM rule are enforced server-side** and cannot be bypassed by passing a raw channel ID. Beyond those, channel membership governs what the bot can post to — a channel the bot is not in simply does not resolve. ## TestSlackConnection Health-checks a connection without walking the channel directory — `auth.test` plus a scope diff against the app's required scopes. ```bash curl -X POST "{{ site.api_url }}/firetiger.slack.v1.SlackService/TestSlackConnection" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "connection": "connections/slack-prod" }' ``` ```json { "ok": true, "botUser": "firetiger", "team": "Acme", "grantedScopes": ["chat:write", "channels:read", "..."], "missingScopes": [] } ``` `ok` is `true` only when `auth.test` succeeds and no required scopes are missing; otherwise `error` describes the failure. ## ListSlackChannels Lists the channels the connection's bot is a member of (`users.conversations`, cursor-paginated, mirroring Slack). Does not walk the full workspace directory. ```bash curl -X POST "{{ site.api_url }}/firetiger.slack.v1.SlackService/ListSlackChannels" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "connection": "connections/slack-prod", "page_size": 200 }' ``` ```json { "channels": [ { "id": "C0123ABCD", "name": "alerts", "isPrivate": false, "isArchived": false } ], "nextPageToken": "dXNlcjpVMDYxTkZUVDI=" } ``` ## Skills Bundles A Skills Bundle points at a [GitHub connection](connections.txt) and a directory of [agentskills.io](https://agentskills.io)-format skills within one of its repositories. Firetiger fetches those skills and exposes them to your organization's agents on the read-only `/n` (`/run/skills`) mount, alongside the built-in library skills. The resource stores configuration only — credentials live on the referenced connection, and skill content is fetched at agent runtime. **Service**: `firetiger.skills_bundles.v1.SkillsBundlesService` **Resource name pattern**: `skills-bundles/{skills_bundle_id}` **Access**: Read-write **Resource type**: [SkillsBundle](types/skills-bundle.txt) The referenced `connection` must be a GitHub connection; `CreateSkillsBundle` and `UpdateSkillsBundle` reject any other connection type with `INVALID_ARGUMENT`. A built-in skill of the same name always takes precedence over a repository-authored one. ## Example flow **1. Register a skills bundle for a runbooks repository** ```bash curl -X POST "{{ site.api_url }}/firetiger.skills_bundles.v1.SkillsBundlesService/CreateSkillsBundle" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "skills_bundle_id": "team-runbooks", "skills_bundle": { "display_name": "Team Runbooks", "connection": "connections/acme-github", "repository": "acme/runbooks" } }' ``` ```json { "skillsBundle": { "name": "skills-bundles/team-runbooks", "displayName": "Team Runbooks", "connection": "connections/acme-github", "repository": "acme/runbooks", "directory": "skills", "enabled": true, "createTime": "2026-06-20T14:30:00Z", "updateTime": "2026-06-20T14:30:00Z" } } ``` The server fills `directory` (defaults to `skills`) and `enabled` (defaults to `true`). **2. List skills bundles** ```bash curl -X POST "{{ site.api_url }}/firetiger.skills_bundles.v1.SkillsBundlesService/ListSkillsBundles" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"page_size": 25}' ``` ```json { "skillsBundles": [ { "name": "skills-bundles/team-runbooks", "displayName": "Team Runbooks", "connection": "connections/acme-github", "repository": "acme/runbooks", "directory": "skills", "enabled": true, "createTime": "2026-06-20T14:30:00Z", "updateTime": "2026-06-20T14:30:00Z" } ], "nextPageToken": "" } ``` ## Methods | Method | Description | |:-------|:------------| | [CreateSkillsBundle](#createskillsbundle) | Register a new skills bundle | | [GetSkillsBundle](#getskillsbundle) | Retrieve a skills bundle by name | | [UpdateSkillsBundle](#updateskillsbundle) | Update an existing skills bundle | | [DeleteSkillsBundle](#deleteskillsbundle) | Soft-delete a skills bundle | | [ListSkillsBundles](#listskillsbundles) | List skills bundles with filtering and pagination | | [ListSkillsBundleSkills](#listskillsbundleskills) | Resolve the skills discoverable in a provider's repository | | [PreviewSkillsBundleSkills](#previewskillsbundleskills) | Resolve the skills a repository configuration would expose, without saving | --- ## CreateSkillsBundle Register a new skills bundle. The referenced connection must be a GitHub connection. ``` POST /firetiger.skills_bundles.v1.SkillsBundlesService/CreateSkillsBundle ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `skills_bundle_id` | string | Yes | ID for the new provider (alphanumeric, hyphens, underscores) | | `skills_bundle` | [SkillsBundle](types/skills-bundle.txt) | Yes | The provider to create | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.skills_bundles.v1.SkillsBundlesService/CreateSkillsBundle" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "skills_bundle_id": "platform-skills", "skills_bundle": { "display_name": "Platform Skills", "connection": "connections/acme-github", "repository": "acme/platform", "directory": "agent-skills", "branch": "main" } }' ``` --- ## GetSkillsBundle Retrieve a skills bundle by name. ``` POST /firetiger.skills_bundles.v1.SkillsBundlesService/GetSkillsBundle ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the provider | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.skills_bundles.v1.SkillsBundlesService/GetSkillsBundle" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "skills-bundles/team-runbooks"}' ``` --- ## UpdateSkillsBundle Update an existing skills bundle. `update_mask` is required and must list the settable fields to modify. Server-owned fields (`name`, `create_time`, `update_time`, `delete_time`) are ignored if present in the mask; a mask with no settable fields is rejected with `INVALID_ARGUMENT`. ``` POST /firetiger.skills_bundles.v1.SkillsBundlesService/UpdateSkillsBundle ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `skills_bundle` | [SkillsBundle](types/skills-bundle.txt) | Yes | The provider with `name` set and updated fields | | `update_mask` | string | Yes | Comma-separated list of settable fields to update. Required; output-only paths are stripped, and an empty resulting mask is rejected. | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.skills_bundles.v1.SkillsBundlesService/UpdateSkillsBundle" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "skills_bundle": { "name": "skills-bundles/team-runbooks", "enabled": false }, "update_mask": "enabled" }' ``` --- ## DeleteSkillsBundle Soft-delete a skills bundle. The resource remains accessible via Get but is excluded from List results unless `show_deleted` is set, and its skills stop being served. ``` POST /firetiger.skills_bundles.v1.SkillsBundlesService/DeleteSkillsBundle ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Resource name of the provider to delete | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.skills_bundles.v1.SkillsBundlesService/DeleteSkillsBundle" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "skills-bundles/team-runbooks"}' ``` --- ## ListSkillsBundles List skills bundles with optional filtering and pagination. ``` POST /firetiger.skills_bundles.v1.SkillsBundlesService/ListSkillsBundles ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `filter` | string | No | [Filter](index.txt#filtering) expression | | `order_by` | string | No | Field to sort by (e.g. `create_time desc`) | | `page_size` | integer | No | Maximum results per page | | `page_token` | string | No | Token for the next page of results | | `show_deleted` | boolean | No | Include soft-deleted providers | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.skills_bundles.v1.SkillsBundlesService/ListSkillsBundles" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"page_size": 25}' ``` --- ## ListSkillsBundleSkills Resolve the [agentskills.io](https://agentskills.io) skills currently discoverable in a provider's repository directory, fetched live from GitHub. Use this to preview what a configured provider exposes to your agents. The full result set is returned in a single page (`next_page_token` is always empty). ``` POST /firetiger.skills_bundles.v1.SkillsBundlesService/ListSkillsBundleSkills ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `parent` | string | Yes | Resource name of the provider whose skills to list (`skills-bundles/{id}`) | | `filter` | string | No | Accepted for API consistency; ignored (the result set is small and returned in one page) | | `order_by` | string | No | Accepted for API consistency; ignored | | `page_size` | integer | No | Accepted for API consistency; ignored | | `page_token` | string | No | Accepted for API consistency; ignored | **Response body** | Field | Type | Description | |:------|:-----|:------------| | `skills` | [DiscoveredSkill](#discoveredskill)[] | The skills found in the provider's repository directory | | `next_page_token` | string | Always empty; the full set is returned in one page | ### DiscoveredSkill | Field | Type | Description | |:------|:-----|:------------| | `name` | string | The skill's slug (its directory name) | | `description` | string | One-line description from the `SKILL.md` frontmatter | | `body` | string | Full `SKILL.md` content (frontmatter and body) | | `source_ref` | string | Opaque origin, e.g. `github://acme/runbooks@main/skills/deploy` | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.skills_bundles.v1.SkillsBundlesService/ListSkillsBundleSkills" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"parent": "skills-bundles/team-runbooks"}' ``` ```json { "skills": [ { "name": "deploy", "description": "Run a production deployment and verify rollout", "body": "---\nname: deploy\ndescription: Run a production deployment and verify rollout\n---\n\n# Deploy\n…", "sourceRef": "github://acme/runbooks@main/skills/deploy" } ], "nextPageToken": "" } ``` --- ## PreviewSkillsBundleSkills Resolve the [agentskills.io](https://agentskills.io) skills a repository configuration **would** expose, without creating or updating a provider. Backs the UI's live preview as a user edits the connection, repository, directory, or branch before saving. Like `ListSkillsBundleSkills`, it returns the full set in a single page. ``` POST /firetiger.skills_bundles.v1.SkillsBundlesService/PreviewSkillsBundleSkills ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `connection` | string | Yes | Resource name of the GitHub connection that grants repository access (`connections/{id}`) | | `repository` | string | Yes | Repository holding the skills, in `owner/repo` form | | `directory` | string | No | Directory within the repository; defaults to `skills` when empty | | `branch` | string | No | Git ref to read from; empty uses the repository's default branch | **Response body** A `skills` array of [DiscoveredSkill](#discoveredskill) (the same skill shape as [ListSkillsBundleSkills](#listskillsbundleskills)). **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.skills_bundles.v1.SkillsBundlesService/PreviewSkillsBundleSkills" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "connection": "connections/acme-github", "repository": "acme/runbooks", "directory": "skills", "branch": "main" }' ``` ## Roles Roles let you control what members of your organization can do. A role is a named bundle of [permissions](types/role.txt#permission); a member's effective access is the union of the permissions across every role they hold. The authenticated caller can read their own resolved access from the identity service's `GetMyUser` response, whose `permissions` field lists the effective permission keys (e.g. `INTEGRATIONS_WRITE`) granted by their assigned [roles](types/user-notification.txt#user). Every organization starts with a single seeded system role, **Admin**, which holds every permission and cannot be edited or deleted. You create additional roles, choose which permissions each grants, pick one as the default assigned to new members, and assign roles to individual members. **Service**: `firetiger.rbac.v1.RolesService` **Resource name pattern**: `roles/{role_id}` **Access**: Read-write **Resource type**: [Role](types/role.txt) Two rules are enforced on every mutation: - **No privilege escalation.** You can only grant permissions that you hold yourself. Creating or editing a role, changing the default role, or assigning a role whose permissions exceed your own is rejected with `permission_denied`. - **Last-admin guardrail.** An organization must always retain at least one member holding the system Admin role. A change that would remove the final admin is rejected with `failed_precondition`. ## Example flow Create a role, make it the default for new members, then assign it to a user. **1. Create a role** ```bash curl -X POST "{{ site.api_url }}/firetiger.rbac.v1.RolesService/CreateRole" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "role_id": "integrations-owner", "role": { "display_name": "Integrations Owner", "description": "Manages connections and integrations", "permissions": ["INTEGRATIONS_WRITE"] } }' ``` ```json { "role": { "name": "roles/integrations-owner", "displayName": "Integrations Owner", "description": "Manages connections and integrations", "permissions": ["INTEGRATIONS_WRITE"], "system": false, "isDefault": false, "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } } ``` **2. Assign the role to a member** ```bash curl -X POST "{{ site.api_url }}/firetiger.rbac.v1.RolesService/AssignRoles" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "user": "users/u-a1b2c3", "roles": ["roles/integrations-owner"] }' ``` ```json { "roles": ["roles/integrations-owner"] } ``` ## Methods | Method | Description | |:-------|:------------| | [CreateRole](#createrole) | Create a new role | | [GetRole](#getrole) | Retrieve a single role | | [ListRoles](#listroles) | List roles in your organization | | [UpdateRole](#updaterole) | Edit a role's name, description, or permissions | | [DeleteRole](#deleterole) | Soft-delete a role | | [ListUsersWithPermission](#listuserswithpermission) | Find members holding a permission | | [SetDefaultRole](#setdefaultrole) | Choose the default role for new members | | [AssignRoles](#assignroles) | Replace a member's role assignments | --- ## CreateRole Creates a new role in your organization. ``` POST /firetiger.rbac.v1.RolesService/CreateRole ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `role_id` | string | | Optional kebab-case slug for the resource name (`roles/{role_id}`). Must match `^[a-zA-Z0-9][a-zA-Z0-9_-]*$`. If omitted, the server generates one. | | `role` | [Role](types/role.txt) | Yes | The role to create. Only `display_name`, `description`, and `permissions` are read; server-managed fields are ignored. | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.rbac.v1.RolesService/CreateRole" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "role_id": "billing-manager", "role": { "display_name": "Billing Manager", "permissions": ["BILLING_WRITE"] } }' ``` **Response** ```json { "role": { "name": "roles/billing-manager", "displayName": "Billing Manager", "permissions": ["BILLING_WRITE"], "system": false, "isDefault": false, "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } } ``` --- ## GetRole Retrieves a single role by resource name. ``` POST /firetiger.rbac.v1.RolesService/GetRole ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Role resource name (`roles/{role_id}`) | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.rbac.v1.RolesService/GetRole" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "roles/integrations-owner"}' ``` **Response** ```json { "role": { "name": "roles/integrations-owner", "displayName": "Integrations Owner", "permissions": ["INTEGRATIONS_WRITE"], "system": false, "isDefault": false, "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } } ``` --- ## ListRoles Lists roles in your organization. ``` POST /firetiger.rbac.v1.RolesService/ListRoles ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `filter` | string | | [AIP-160](https://google.aip.dev/160) filter expression | | `order_by` | string | | Sort order (e.g. `create_time desc`) | | `page_size` | integer | | Maximum results per page | | `page_token` | string | | Token from a previous `next_page_token` | | `show_deleted` | boolean | | Include soft-deleted roles when `true` | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.rbac.v1.RolesService/ListRoles" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{}' ``` **Response** ```json { "roles": [ { "name": "roles/admin", "displayName": "Admin", "system": true, "isDefault": true, "createTime": "2024-01-01T00:00:00Z", "updateTime": "2024-01-01T00:00:00Z" }, { "name": "roles/integrations-owner", "displayName": "Integrations Owner", "permissions": ["INTEGRATIONS_WRITE"], "system": false, "isDefault": false, "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T14:30:00Z" } ], "nextPageToken": "" } ``` --- ## UpdateRole Edits a role's display name, description, or permissions. The system Admin role cannot be edited (rejected with `failed_precondition`). Server-managed fields (`system`, `is_default`, `name`, timestamps) are ignored even if included in `update_mask`. ``` POST /firetiger.rbac.v1.RolesService/UpdateRole ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `role` | [Role](types/role.txt) | Yes | The role with `name` set and the fields to change | | `update_mask` | string | | Comma-separated field paths to modify (`display_name`, `description`, `permissions`). Omitted or `*` updates all mutable fields. | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.rbac.v1.RolesService/UpdateRole" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "role": { "name": "roles/integrations-owner", "permissions": ["INTEGRATIONS_WRITE", "CHANGE_MONITOR_WRITE"] }, "update_mask": "permissions" }' ``` **Response** ```json { "role": { "name": "roles/integrations-owner", "displayName": "Integrations Owner", "permissions": ["INTEGRATIONS_WRITE", "CHANGE_MONITOR_WRITE"], "system": false, "isDefault": false, "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T15:00:00Z" } } ``` --- ## DeleteRole Soft-deletes a role. Rejected with `failed_precondition` when the role is the system Admin role or the current default role (change the default with [SetDefaultRole](#setdefaultrole) first). Members who still held the role lose its permissions on their next request. ``` POST /firetiger.rbac.v1.RolesService/DeleteRole ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Role resource name (`roles/{role_id}`) | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.rbac.v1.RolesService/DeleteRole" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "roles/integrations-owner"}' ``` **Response** ```json {} ``` --- ## ListUsersWithPermission Returns the members of your organization whose combined role permissions include a given permission. Useful for audits and for answering "who can do X?". ``` POST /firetiger.rbac.v1.RolesService/ListUsersWithPermission ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `permission` | [Permission](types/role.txt#permission) | Yes | The permission to query | | `page_size` | integer | | Maximum results per page | | `page_token` | string | | Token from a previous `next_page_token` | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.rbac.v1.RolesService/ListUsersWithPermission" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"permission": "INTEGRATIONS_WRITE"}' ``` **Response** ```json { "users": ["users/u-a1b2c3", "users/u-d4e5f6"], "nextPageToken": "" } ``` --- ## SetDefaultRole Atomically makes a role the default assigned to new members, clearing the flag from the previous default. Subject to the no-privilege-escalation rule: the target role's permissions must be a subset of your own. ``` POST /firetiger.rbac.v1.RolesService/SetDefaultRole ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `name` | string | Yes | Role resource name to make default (`roles/{role_id}`) | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.rbac.v1.RolesService/SetDefaultRole" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{"name": "roles/integrations-owner"}' ``` **Response** ```json { "role": { "name": "roles/integrations-owner", "displayName": "Integrations Owner", "permissions": ["INTEGRATIONS_WRITE"], "system": false, "isDefault": true, "createTime": "2024-06-15T14:30:00Z", "updateTime": "2024-06-15T15:30:00Z" } } ``` --- ## AssignRoles Replaces a member's entire set of role assignments with the provided list. The set is order-independent and deduplicated by the server. An empty list leaves the member with no roles. Subject to the no-privilege-escalation rule and the last-admin guardrail; there is no separate demote method, remove the Admin role from the list to demote a member. ``` POST /firetiger.rbac.v1.RolesService/AssignRoles ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `user` | string | Yes | Target member resource name (`users/{user}`) | | `roles` | string[] | | Role resource names to assign. Every role must exist and belong to your organization. | **Example** ```bash curl -X POST "{{ site.api_url }}/firetiger.rbac.v1.RolesService/AssignRoles" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "user": "users/u-a1b2c3", "roles": ["roles/integrations-owner", "roles/billing-manager"] }' ``` **Response** ```json { "roles": ["roles/billing-manager", "roles/integrations-owner"] } ``` ## Impact Report Notifications Impact Report notifications control whether the current user's weekly [Impact Report](../guides/impact-reports.txt) is delivered as a Slack DM. Reports are always generated for eligible engineers and visible in the Firetiger UI; this preference only controls Slack delivery. **Service**: `firetiger.impact_reports.v1.ImpactReportNotificationsService` **Resource name pattern**: `impact-report-notification-preferences/{preference}` **Access**: Read-write, scoped to the authenticated user ## Methods | Method | Description | |:-------|:------------| | [GetMyImpactReportNotificationPreference](#getmyimpactreportnotificationpreference) | Read the current user's Impact Report delivery preference | | [UpdateMyImpactReportNotificationPreference](#updatemyimpactreportnotificationpreference) | Update the current user's Impact Report Slack DM toggle | ## ImpactReportNotificationPreference Stores the user's explicit Slack override. An absent `slack_enabled` follows the server default: Slack delivery is on when the user's Change Monitor Slack DMs are verified, off otherwise. | Field | Type | Behavior | Description | |:------|:-----|:---------|:------------| | `name` | string | OUTPUT_ONLY | Resource name (`impact-report-notification-preferences/{preference}`) | | `slack_enabled` | boolean | | Explicit Slack DM override. Unset means "follow the default" | | `create_time` | timestamp | OUTPUT_ONLY | When the preference was created | | `update_time` | timestamp | OUTPUT_ONLY | When the preference was last modified | | `delete_time` | timestamp | OUTPUT_ONLY | When the preference was soft-deleted | --- ## GetMyImpactReportNotificationPreference Read the current user's Impact Report delivery preference along with the resolved effective value (preference override applied on top of the Slack-readiness default). ``` GET /v1/users/me/impact-report-notification-preference ``` **Example** ```bash curl "{{ site.api_url }}/v1/users/me/impact-report-notification-preference" \ -u "$USERNAME:$PASSWORD" ``` **Response** ```json { "preference": { "name": "impact-report-notification-preferences/user-123", "slackEnabled": true }, "effectiveSlackEnabled": true } ``` --- ## UpdateMyImpactReportNotificationPreference Update the current user's Impact Report Slack DM toggle. The write materializes the choice, so it stays stable if the user's Change Monitor Slack setting changes later. ``` PATCH /v1/users/me/impact-report-notification-preference ``` **Request body** | Field | Type | Required | Description | |:------|:-----|:---------|:------------| | `slack_enabled` | boolean | Yes | Whether the weekly Impact Report is delivered as a Slack DM | | `update_mask` | string | No | Comma-separated field mask naming the fields to write. The only supported path is `slack_enabled`. Defaults to `slack_enabled` when the field is present in the body | **Example** (turn Impact Report Slack DMs off) ```bash curl -X PATCH "{{ site.api_url }}/v1/users/me/impact-report-notification-preference" \ -u "$USERNAME:$PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "slack_enabled": false, "update_mask": "slack_enabled" }' ``` **Response** ```json { "preference": { "name": "impact-report-notification-preferences/user-123", "slackEnabled": false }, "effectiveSlackEnabled": false } ``` ## Related - [Impact Reports guide](../guides/impact-reports.txt) — what reports contain and who gets one - [Monitoring Plans](monitoring-plans.txt) — Change Monitor notification setup, including Slack DM verification (the default this preference overrides)