go-flashduty is the official Go software development kit (SDK) for the Flashduty Open API. Version 0.5.3 covers all 253 REST endpoints across 27 services. Each method sends exactly one HTTP request.
We built the SDK on three rules:
- Generate the code from the OpenAPI spec. Don't write it by hand.
- Map each method to one API endpoint, strictly 1:1.
- Put convenience logic in the layers above the SDK, not inside it.
We think these three rules keep a large SDK maintainable. Install it with go get github.com/flashcatcloud/go-flashduty. The rest of this post explains each rule.
Why we don't hand-write 253 endpoints
A hand-written SDK can look more polished. You can tune each method name and add small helpers. But with hundreds of endpoints and a backend that keeps changing, hand-writing has two problems.
The first is inconsistency. Is the pagination field Limit or PageSize? How do you wrap errors? How do you handle time fields? Where does request_id go in the response? These small choices drift with each author and over time. After three months, you have three pagination styles and two error types, and nobody knows which one is right.
The second is lag. Every new endpoint or changed field needs someone to remember to update the SDK, and people often forget. The SDK stays half a version behind the API, and the docs don't match the code. You find out when a user files an issue.
So we've generated the SDK from the OpenAPI spec since the project started. Service groups, method signatures, and request and response types all come from that one spec. The generator enforces consistency, so people don't have to. Every method looks the same:
client, err := flashduty.NewClient("YOUR_APP_KEY")
if err != nil {
log.Fatal(err)
}
list, resp, err := client.Incidents.List(context.Background(), &flashduty.ListIncidentsRequest{
Progress: "Triggered",
ListOptions: flashduty.ListOptions{Limit: 20},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("request_id=%s total=%d has_next=%t\n", resp.RequestID, resp.Total, resp.HasNextPage)
for _, inc := range list.Items {
fmt.Printf("[%s] %s\n", inc.IncidentSeverity, inc.Title)
}
Each service gets a group of methods, like client.Incidents and client.Alerts. Every method returns (*T, *Response, error). Learn one endpoint and you can use all 253. In a large SDK, that uniform structure is the most valuable property.
One method, one request
Code generation gets the endpoints in. The harder question is how much logic goes inside each method.
Our answer is as little as possible. Each method sends one HTTP request. It doesn't quietly call other endpoints, and it doesn't add extra wrapping. This rule looks simple, but it's the one we enforce most strictly.
Short IDs are a common example. Many Flashduty resources have two IDs: a full ID and a shorter one that's easier for people. A convenience-focused SDK might take your short ID, call a resolve endpoint to get the full ID, and then call the endpoint you wanted.
We don't do that in the SDK, for two reasons.
First, it makes your metrics inaccurate. When you call Incidents.Info, you expect one request. If the SDK sends a second request in the background, your call counts, latency, rate-limit usage, and error attribution all stop matching what you expect. Your dashboards show twice the expected requests.
Second, the SDK keeps growing. Short-ID resolution is only the first step. Next someone asks for auto-pagination, then for fetching related resources. Each request sounds reasonable. Together they turn the SDK into a fat client with unpredictable behavior and code that's hard to remove.
So we set a layering rule. The SDK maps 1:1 to the API. Short-ID resolution, auto-pagination, and cross-resource stitching live in the layers above it: flashduty-cli and the MCP server. We also don't use an endpoint for work outside its design for convenience.
With this split, the SDK stays thin, predictable, and easy to test, because its job is small and fixed. Convenience logic changes often, so it lives in the layers above, where fast iteration is normal and a mistake won't break the SDK.
Thin but complete
"Thin" means the SDK doesn't make business decisions for you. It doesn't mean you get raw HTTP. The SDK still handles everything that makes a single call easy to use. Three examples follow.
Time fields. The API sends Unix timestamps as integers, which are hard to read in logs. So the SDK types response time fields as Timestamp. By default, they print as RFC 3339 strings that you can read in JSON, in logs, and in input to a large language model (LLM). When you need the raw value, call a method:
inc := list.Items[0]
fmt.Println(inc.StartTime) // 2026-05-23T13:37:11+08:00
epoch := inc.StartTime.Unix() // 1779514631 (raw wire value)
t := inc.StartTime.Time() // time.Time
Errors. Instead of comparing error strings, you call typed check functions. These functions use errors.As, so they also recognize wrapped errors:
if flashduty.IsNotFound(err) { /* ... */ }
if flashduty.IsRateLimited(err) { /* ... */ }
Retries. We kept retries out of the core package on purpose. They live in an optional retry subpackage that you add as http.RoundTripper middleware:
client, err := flashduty.NewClient("YOUR_APP_KEY",
flashduty.WithTransport(retry.New(
retry.WithMaxRetries(3),
)),
)
This design follows the 1:1 rule too. The core client sends one request. Retries, caching, and tracing plug in as optional transport middleware that you add when you need it.
The spec is the source of truth
The last rule is about maintenance. An SDK is usually correct on release day. The hard part is keeping it correct a year later.
Our approach: generate the code from the OpenAPI spec, never the other way around. When a backend endpoint changes, we change the spec first, then regenerate the SDK. The process has no manual step for syncing 253 methods.
Two layers of tests add checks on top. Every method has unit tests that check the shape of the generated code. The whole SDK also runs end-to-end tests against the live API, to confirm requests and responses match the server. Code generation keeps the SDK consistent with itself. End-to-end tests keep it consistent with the real service. Without either layer, the SDK eventually drifts from the real service.
Get started
If you work with Flashduty from Go, you can use go-flashduty for automation scripts, integrations, or a reliable tool set for an AI agent. The code is in the go-flashduty repository on GitHub.


