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 currently covers 253 endpoints across 27 services, all 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
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).
| Return value | Type | Description |
|---|---|---|
| data | *T | The endpoint’s typed response body (e.g. *ListIncidentsResponse); nil on failure |
*Response | *flashduty.Response | Wraps *http.Response and carries envelope metadata such as RequestID and pagination |
error | error | *ErrorResponse on failure, *RateLimitError on 429 |
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.
| Option | Description |
|---|---|
WithBaseURL(raw string) | Override the API base URL (default https://api.flashcat.cloud). Use it to point at your own gateway for private deployments; an invalid URL errors at the NewClient stage |
WithTimeout(d time.Duration) | Set the overall timeout of the underlying HTTP client |
WithUserAgent(ua string) | Set the User-Agent header carried on every request |
WithHTTPClient(hc *http.Client) | Replace the underlying *http.Client; ignored when nil |
WithTransport(rt http.RoundTripper) | Set a custom http.RoundTripper, the idiomatic hook for middleware such as retry, caching, tracing, and rate limiting; ignored when nil |
WithLogger(l Logger) | Set a custom logger; ignored when nil |
WithRequestHeaders(h http.Header) | Set static headers appended to every request, applied after the SDK’s own headers (Content-Type, Accept, User-Agent) |
WithRequestHook(hook func(*http.Request)) | Register a callback invoked before each request is sent, for injecting per-request headers (such as W3C traceparent) |
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).
| Service field | Description |
|---|---|
client.Incidents | Incidents |
client.Alerts | Alerts |
client.Channels | Channels |
client.Schedules | Schedules |
client.Calendars | Calendars |
client.StatusPages | Status pages |
client.Members | Members |
client.Teams | Teams |
client.RolesPermissions | Roles and permissions |
client.Account | Account |
client.AuditLogs | Audit logs |
client.AlertRules | Alert rules |
client.RuleSets | Rule sets |
client.AlertEnrichment | Alert enrichment |
client.DataSources | Data sources |
client.Integrations | Integrations |
client.ImIntegrations | IM integrations |
client.NotificationTemplates | Notification templates |
client.Changes | Changes |
client.Diagnostics | Diagnostics |
client.Analytics | Analytics |
client.A2aAgents | A2A Agents |
client.McpServers | MCP Servers |
client.Sessions | AI SRE sessions |
client.Skills | Skills |
client.Applications | RUM applications |
client.Issues | RUM issues |
client.Sourcemaps | RUM sourcemaps |
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. 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).
| Method | Returns | Description |
|---|---|---|
.Time() | time.Time | Get the standard time value |
.Unix() | int64 | Get the raw wire value (Timestamp in seconds, TimestampMilli in milliseconds) |
.IsZero() | bool | Whether it’s the unset sentinel (0) |
.String() | string | RFC3339 in the local time zone; "0" when unset |
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).
| Field | Type | Wire field | Description |
|---|---|---|---|
Page | int | p | 1-based page number |
Limit | int | limit | Max items returned per page |
SearchAfterCtx | string | search_after_ctx | The opaque cursor echoed by the previous page, for deep pagination; pass it back to fetch the next page |
*Response carries Total, HasNextPage, and SearchAfterCtx. We recommend walking page by page with the search-after cursor:
Error handling
Any unsuccessful call — whether the envelope carries an error or the HTTP status is non-2xx — returns
*ErrorResponse. It has Code, Message, and RequestID fields; when troubleshooting, give RequestID to the support team to pinpoint the request.
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.
errors.As internally):
| Helper | Description |
|---|---|
IsNotFound(err) | Whether the resource does not exist |
IsRateLimited(err) | Whether requests are too frequent (429) |
IsUnauthorized(err) | Whether unauthorized |
IsAccessDenied(err) | Whether access is denied |
IsInvalidParameter(err) | Whether a parameter is invalid |
ErrorCodeOf(err) | Extract the error code, returning an ErrorCode constant (such as ErrorCodeAccessDenied, ErrorCodeUnauthorized) |
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.
| Option | Default | Description |
|---|---|---|
retry.WithMaxRetries(n int) | 3 | Max retries after the first attempt; a negative number disables retry |
retry.WithMinWait(d time.Duration) | 500ms | Base backoff duration (the wait before the first retry) |
retry.WithMaxWait(d time.Duration) | 30s | Upper bound on a single backoff wait |
retry.WithBase(base http.RoundTripper) | http.DefaultTransport | The underlying RoundTripper that actually performs the request |
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.