Overview
go-flashduty is the official open-source Go client for Flashduty, covering every REST endpoint of the Flashduty Open API. It follows the same design as go-github — service groups, typed requests and responses, a composable transport layer — and stays strictly 1:1 with the OpenAPI spec: each method maps to exactly one HTTP call, returns (*T, *Response, error), and performs no implicit cross-endpoint aggregation or enrichment.
The SDK provides typed API operations generated from the Flashduty OpenAPI spec, covered by unit tests, and end-to-end verified against the live API.
The SDK is deliberately “thin.” Consumer-side logic such as short-ID resolution and cross-endpoint orchestration belongs in the caller (CLI / MCP), not stuffed into the SDK or shoehorned into an endpoint. This keeps the SDK strictly one-to-one with the API — predictable, generatable, and verifiable.
github.com/flashcatcloud/go-flashduty, the package name is flashduty, and the source is open-sourced under Apache-2.0 at flashcatcloud/go-flashduty.
Open API reference
Request parameters and response fields for every endpoint.
Command-line tool
The CLI for operating Flashduty directly from your terminal.
Installation
1
Requires Go 1.24+
Make sure your local Go toolchain is at least 1.24.
2
Get the dependency
3
Import the package
Quick start
Here is a minimal runnable example: construct the client, list incidents in the “Triggered” state, and handle the returned triple
(data, *Response, error).
Create a client
NewClient takes an app_key plus zero or more Options. An empty app_key returns an error directly. The default Base URL is https://api.flashcat.cloud, the default HTTP timeout is 30 seconds, and the default User-Agent is go-flashduty.
Private deployment: point the client at your own Flashduty gateway address with
WithBaseURL — everything else stays exactly the same.Services and methods
Endpoints are grouped by service and hang off the client: the call convention is uniformly
client.<Service>.<Method>(ctx, req), returning (*T, *Response, error). For example, client.Incidents.List(ctx, req) or client.Sessions.Info(ctx, req).
client.StatusPages.DraftCreate (POST /status-page/draft/create) stores a status page event draft for a human to review and publish from the console; it is never directly visible to the public: draft is arbitrary JSON stored verbatim, up to 64 KB serialized, with page_id, type (incident or maintenance), name, and message validated; change_id (> 0 appends an update to an existing event), status, and affected_components are optional, and for a new maintenance you can set start_time / end_time (Unix seconds) for the window. The request-level source is an opaque marker of the drafting origin (up to 64 characters, e.g. ai_sre:sess_xxx); the response returns a draft_id matching draft_[A-Za-z0-9]{22}, which the console review link carries.
client.Knowledge covers the 9 operations under /safari/knowledge/*: on the knowledge side PackReadGet (get the account knowledge), PackReadList (list knowledge), PackWriteEnsure (ensure the knowledge exists), PackWriteUpdate (change its scope), and PackWriteDelete (delete it); on the file side FileReadGet, FileReadList, FileWritePut (upload/overwrite), and FileWriteDelete. Exported types include KnowledgePackItem, KnowledgeFileItem, KnowledgeWarning, and the various Knowledge*Request / Knowledge*Response structs.
client.Artifacts (AI SRE artifacts) covers the 11 operations under /safari/artifact/*: on the gallery read side ReadGet (get a single published artifact by ID), ReadList (list artifacts visible to the caller, with title substring search and scope (all / personal / team) plus team_ids filtering), and ReadGetFileState (probe up to 50 presented-file IDs (pf_ prefix) in one call for ones that already have a live published artifact); on the file side ReadSign (issue short-lived download/preview URLs for a presented file, valid 5 minutes, expires_in is fixed at 300) and ReadStream (download or preview the file’s bytes with a signed token — the success body is a file, not a JSON envelope, with the raw bytes on Response.Raw); on the write side WritePublish (publish a session-produced file to the gallery), WriteUpdate (rename the artifact or transfer it between personal and team scope), and WriteDelete (detach it from the gallery; the source file stays with its session); public sharing WriteShareEnable (turn on anonymous public sharing and return the public link — anyone with the link can view it, no login required), WriteShareRevoke (turn sharing off; the link stops resolving immediately), and WriteShareSync (refresh the public snapshot with the latest content — when share_enabled is true and share_file_id differs from file_id, the snapshot is stale and calling this refreshes it). Exported types include PublishedArtifactItem, ArtifactShareState, SignedUrLs, and the various Artifact*Request / Artifact*Response structs.
client.Members.MemberNotify (POST /member/notify, memberNotify) sends an email to account members on behalf of the caller, and is callable only with a credential minted for an AI SRE session — any other credential, a plain app_key included, is rejected with AccessDenied (check it with IsAccessDenied / ErrorCodeOf). Request fields: subject (required, 1–200 characters) and html (required, the entire email body the recipients see, up to 102,400 bytes), while person_ids is optional (at most 20, no duplicates; omitted or empty sends to the caller only). script, meta, link, and base tags and on* event handlers are silently stripped from html before sending; a style, svg, iframe, object, embed, form, input, or button tag, an img without an https src, or a link outside http/https/mailto instead fails the call with InvalidParameter before anything is queued, naming each offending construct. Delivery is asynchronous, so accepted only means the email was queued; the response returns one entry per recipient in recipients[] (person_id plus status), where status is accepted or skipped, and a skipped entry carries a reason of not_member, no_email, email_disabled, duplicate, rate_limited (at most 20 emails per recipient per hour), or send_failed.
client.Diagnostics.QueryData runs a synchronous query via POST /monit/query/data and returns a stable query_result.v1 structured result (result.kind is one of frames, records, or samples). This API requires monit-edge v0.65.0 or later. For log-pattern and metric-trend analysis, use client.DataSources.ToolsInvoke with prometheus.metric_trends, loki.log_patterns, or victorialogs.log_patterns.
client.Diagnostics.QueryExplore (POST /monit/query/explore, monit-read-query-explore) runs an explore query against a configured data source and returns the data source’s native explore_result.v1 shape: ExploreData.format is always explore_result.v1; result.kind is frames (columnar tables or time series, see ExploreFrame / ExploreField), samples (instant values with labels, ExploreSample), or logs (log entries as ExploreLogEntry, at most 1,000, with has_more set when truncated and applied_limit reporting the effective cap); and execution.effective_step_seconds echoes the step actually used after applying max_data_points and min_step_seconds. Request fields: datasource_id (from /monit/datasource/list, and it must belong to the authenticated account), expr (a query expression in the data source’s native language — PromQL, LogsQL, SQL, and so on), args (macro substitutions keyed by variable name, used for Grafana-style variables; required in the request, so pass an empty map[string]string{} when you have none — a nil Go map serializes to null, which does not satisfy the schema), and execution (QueryExploreExecution: kind is instant, range, or window; to_ms is required for all three; range additionally requires from_ms and max_data_points, window additionally requires from_ms; min_step_seconds is accepted only for range). How it differs from QueryData: reach for QueryExplore when you need the data source’s native result shape (raw logs included) and QueryData when you need the stable query_result.v1 contract. This API requires the data source view (monit) permission and monit-edge v0.68.0 or later in supported deployments.
Beyond application management, client.Applications (RUM applications) covers the five RUM remote configuration methods: RemoteConfigReadGet (POST /rum/application/remote-config/get, read the live configuration and its version), RemoteConfigReadHistoryList (/rum/application/remote-config/history/list, page through published history versions), RemoteConfigReadPreview (/rum/application/remote-config/preview, evaluate a draft configuration against a simulated client’s env / app_version / sdk and return the matching rule index hit_rule_index plus the values finally delivered, without publishing), RemoteConfigWriteUpdate (/rum/application/remote-config/update, publish a complete new configuration), and RemoteConfigWriteHistoryRevert (/rum/application/remote-config/history/revert, republish a history version’s content as a new version). Main types: RemoteConfig (enabled, activation, default, rules, custom, refresh_on_foreground), RemoteConfigValues (the four SDK knobs a configuration can set), RemoteConfigRule (match conditions plus the set values), RemoteConfigHistoryItem (with version, reason, content_hash, and updated_by), and the GetRemoteConfigRequest / ListRemoteConfigHistoryRequest / PreviewRemoteConfigRequest / UpdateRemoteConfigRequest / RevertRemoteConfigRequest structs with their matching *Response types.
client.DataSources.ToolsInvoke (POST /monit/datasource/tools/invoke, monit-datasource-tools-invoke) executes one deterministic tool against a configured datasource: tool is a single tool name prefixed by the datasource type (e.g. mysql.overview), and params is the tool-specific JSON parameters (omitted means {}; an explicit null is invalid). Alongside diagnostic tools, the entry supports <type>.query query tools (prometheus, mysql, postgres, oracle, clickhouse, elasticsearch, loki, victorialogs, sls, tencent_cls); the /monit/query/data entry stays unchanged. It requires all currently online routable Edge sessions in the cluster to support the v0.71.0 base invoke protocol (individual tools may require a newer implementation), and there is no tool catalog, no automatic replay, and no fallback to legacy diagnose. The request body limit is 128 KiB, the complete success response limit is 10 MiB, and the tool timeout is at most 25 seconds; the response is a DatasourceToolResult (data is tool-specific JSON, never null, summary is optional, and a truncated object with reason indicates truncation).
To call a <type>.query tool, build the request with NewDatasourceQueryInvokeRequest(datasourceID, tool, params) and pass the matching typed struct for that tool: PrometheusQueryParams, MySQLQueryParams, PostgresQueryParams, OracleQueryParams, ClickHouseQueryParams, ElasticsearchQueryParams, LokiQueryParams, VictoriaLogsQueryParams, SLSQueryParams, or TencentCLSQueryParams. All of them pair expr with an execution (DatasourceQueryExecution: kind is instant, range, or window, and from_ms / to_ms are Unix-millisecond *int64 values; range additionally requires max_data_points and accepts min_step_seconds to raise the computed step). Optional fields are pointers — a nil pointer omits the key and lets the server apply its default. Query-tool params may not be empty: NewDatasourceQueryInvokeRequest(..., nil) returns an error, unlike the general “omitted means {}” rule. The execution mode is constrained by datasource type: Prometheus and Loki use instant or range (instant carries only to_ms); the SQL types (MySQL, Postgres, Oracle, ClickHouse, and the SQL-flavored Elasticsearch) plus SLS and Tencent CLS use window; VictoriaLogs uses window for raw logs and instant / range for stats queries. limit and direction on Loki, VictoriaLogs, SLS, and Tencent CLS bound raw-log retrieval only, and SLSQueryParams also requires project / logstore while TencentCLSQueryParams requires region / topic_id / syntax.
client.DataSources.ReadPrometheusLabelValues(ctx, dataSourceID, labelName) (GET /monit/prometheus/api/v1/label/{label_name}/values, monit-prometheus-read-label-values) lists every value of one label on a Prometheus-compatible data source through the monitoring proxy: the label name goes in the URL path (the SDK escapes it), and the target data source is selected by the X-DSID request header — the SDK sets it from the dataSourceID argument (a 0 ID or an empty labelName errors immediately), with the ID taken from /monit/datasource/list and required to be a Prometheus-compatible data source of the current account. It is the only method whose success body is not the Flashduty {request_id, data} envelope: the response decodes straight into PrometheusLabelValuesResponse (data carries the label-value strings when status is success; errorType and error are present when it is error). Failures before the data source is reached (X-DSID missing or invalid, data source absent, or not a Prometheus type) come back as text/plain, while the data source’s own failures use its own JSON error shape; the SDK treats both alike and carries the raw body as-is on *ErrorResponse.Message. Supported deployments require monit-edge v0.35.0 or later.
For client.DataSources, the payload selects a type-specific configuration block by type_ident. Fifteen type_ident values are allowed: prometheus, loki, mysql, oracle, postgres, clickhouse, elasticsearch, sls, tencent_cls, victorialogs, plus the new diagnostic-only types redis_node, redis_sentinel, mongodb_mongod, mongodb_mongos, and kafka — diagnostic-only types always have alerting_enabled false (which does not block non-alerting queries or tools) and reject true. Connection address rules: Redis/MongoDB diagnostic types take a single host:port (bracket IPv6), with no URI, userinfo, or query; kafka takes 1–32 unique comma-separated host:port bootstrap addresses (at most 4096 characters after normalization, and the payload has no broker list); for mongodb_mongod / mongodb_mongos the configuration block’s auth_source defaults to admin, username and password must be configured together, and client certificates are unsupported; the Redis node configuration’s database defaults to 0. Sensitive fields such as the diagnostic types’ password and Kafka’s tls_key support ${env:NAME} references: literal values are omitted from responses (only ${env:...} references are echoed back), omitting the fields on update preserves the stored values, and explicitly sending an empty string clears them.
enabled and alerting_enabled are independent: enabled (whether business execution is enabled) defaults to true on create; alerting_enabled (whether alert evaluation is allowed; alerting also requires enabled true and an alerting-capable type) defaults to true for alerting types and false for diagnostic-only types on create. On update, omitting either preserves the current value and an explicit null is invalid; disabling (enabled false) is rejected with a conflict when enabled rules reference the datasource. Also note that payload is always null in /monit/datasource/list responses (the list query does not read the payload column); it is populated in create/update/info responses.
All identifiers, service field names, and method names match the generated code. For exactly which methods each service has and their request and response types, rely on
services_gen.go and the per-service files, plus the Open API reference.Response timestamps
Time fields in responses are no longer bare integers but self-describing
Timestamp (Unix seconds) or TimestampMilli (milliseconds) types (types shared by requests and responses are the exception — see the warning below). They serialize to RFC3339 strings in the local time zone, so JSON, logs, and LLM-facing output are directly readable; the raw epoch is still one method call away.
- Serialization (outbound): a non-zero value serializes to a quoted RFC3339 string (
TimestampMilliuses RFC3339Nano to preserve millisecond precision). A zero value serializes to the bare integer0— an “unset” sentinel rather than a 1970 date, and dropped byjson:",omitempty". - Deserialization (inbound): it accepts both numeric epoch (the raw wire form) and RFC3339 strings (so a serialized value round-trips losslessly), and also accepts
null(→ 0).
Pagination
All list endpoints share
ListOptions, which you embed in the request struct. Zero values are omitted and never override server defaults (the backend defaults to p=1, limit=20).
On the response side,
*Response carries Total, HasNextPage, and SearchAfterCtx. We recommend walking page by page with the search-after cursor:
Error handling
An unsuccessful call returned by the Flashduty API — whether the envelope carries an error or the HTTP status is non-2xx — returns
*ErrorResponse. It has Code, Message, an optional Reason, and RequestID fields; when troubleshooting, give RequestID to the support team to pinpoint the request. Reason carries the server-supplied optional cause (the envelope’s DutyError has the same field, JSON reason,omitempty); when non-empty it is also appended to the end of the error string, as , reason X.
When the API returns 429, the error is promoted to *RateLimitError: it embeds *ErrorResponse (so errors.As for *ErrorResponse still matches) and additionally carries a RetryAfter hint.
For generated endpoints, if you receive a non-2xx response with a non-JSON body, the SDK returns a plain
error, not *ErrorResponse. This indicates that a gateway, load balancer, proxy, or other intermediary produced the response, usually because the request exceeded an intermediary timeout. Retry the request or split a long-running batch into smaller batches, and do not assume that errors.As(err, &apiErr) matches this error.The hand-written client.DataSources.ReadPrometheusLabelValues method is the exception: it parses no envelope and instead puts a non-2xx response’s raw body verbatim into *ErrorResponse.Message, treating the platform’s text/plain 4xx/5xx bodies and the data source’s own JSON error shape alike (429 is still promoted to *RateLimitError). errors.As(err, &apiErr) therefore does match there — Code is usually empty, so read Message and Response.StatusCode to identify the cause.errors.As internally):
Retry
The core client has no built-in automatic retry. Compose the optional
retry subpackage through the transport layer — a safe-by-default retrying http.RoundTripper.
Features of github.com/flashcatcloud/go-flashduty/retry:
- Retry conditions: HTTP 429, any 5xx (status ≥ 500), and transport errors. Other 4xx and all 2xx/3xx return immediately.
- Backoff policy: deterministic exponential backoff (
MinWait * 2^attempt, capped atMaxWaiteach time); no random jitter. When a valid integerRetry-Afterheader is present, it takes precedence (also capped atMaxWait). - Safe replay: retries only when the request body is replayable (
req.Bodyis nil orreq.GetBodyis non-nil), rebuilds the body on a clone of the request for each retry, and never mutates the caller’s original*http.Request. All requests the SDK builds setGetBody, so POST bodies are safely replayable. - Respects cancellation: if the request context is canceled while waiting out a backoff, it returns the context error immediately.
Streaming export
client.Sessions.Export exports the full event transcript of an AI SRE session, returning an io.ReadCloser (an NDJSON stream, application/x-ndjson) rather than a JSON envelope. The first line is always a session_meta envelope, and each subsequent line is a session event; when req.IncludeSubagents is true, each subagent_dispatch line is followed by the subagent’s own full event stream.
Because the response body can be large, read it line by line and write directly to a file — do not buffer the whole transcript into memory. The returned io.ReadCloser is the live HTTP response body, held by the caller and which you must Close (a defer close is correct). Pair it with NewExportScanner to scan line by line and DecodeExportLine to decode a line into an ExportLine:
NewExportScanner is configured with a per-line buffer large enough to hold the wider event lines in a transcript (such as tool output or LLM calls), free of the default 64KB token limit. On any non-2xx status, the response body is still a regular JSON error envelope — Export reads and closes it and returns a typed error (*ErrorResponse, or *RateLimitError on 429), with the io.ReadCloser being nil, consistent with the other generated endpoints.