> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flashduty.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Go SDK

> go-flashduty — the official Go client for the Flashduty Open API

[`go-flashduty`](https://github.com/flashcatcloud/go-flashduty) is the official Go client for the Flashduty Open API — a thin, strongly-typed SDK. It is generated from the same OpenAPI specification this documentation is built on, covers every Open API endpoint, and is validated by unit tests and end-to-end against the live API.

If your service is written in Go, use the SDK to call Flashduty rather than hand-writing HTTP requests — you get typed request parameters, response structs, and error codes out of the box, with no manual JSON assembly or response parsing.

<Tip>
  The SDK shares the same endpoint semantics as the Open API. Every request/response structure documented here maps to a corresponding type in the SDK. Authentication uses the same [APP Key](/en/openapi/introduction#authentication).
</Tip>

## Install

Requires Go 1.24 or later.

```bash theme={null}
go get github.com/flashcatcloud/go-flashduty
```

## Quick start

Create a client with your APP Key and call endpoints grouped by service (`client.Incidents`, `client.Alerts`, …). Each method maps to exactly one HTTP call and returns `(*T, *Response, error)`:

```go theme={null}
package main

import (
	"context"
	"fmt"
	"log"

	flashduty "github.com/flashcatcloud/go-flashduty"
)

func main() {
	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)
	}
}
```

## Design

<CardGroup cols={2}>
  <Card title="Thin and typed" icon="feather">
    Every method maps to exactly one HTTP call and returns `(*T, *Response, error)`. No hidden cross-endpoint enrichment.
  </Card>

  <Card title="Service-grouped" icon="layer-group">
    Endpoints are organized into services on the client (`client.Incidents`, `client.Alerts`, …), generated from the OpenAPI specification.
  </Card>

  <Card title="Composable transport" icon="puzzle-piece">
    Cross-cutting concerns (retry, caching, tracing, rate-limit handling) compose as `http.RoundTripper` middleware via `WithTransport`.
  </Card>

  <Card title="Human-readable timestamps" icon="clock">
    Response time fields are typed `Timestamp` / `TimestampMilli`, rendering as RFC3339 in JSON, logs, and LLM-facing output — while the raw epoch is one call away.
  </Card>
</CardGroup>

## Options

`NewClient` accepts a set of options:

```go theme={null}
client, err := flashduty.NewClient("YOUR_APP_KEY",
	flashduty.WithBaseURL("https://api.flashcat.cloud"),
	flashduty.WithTimeout(10*time.Second),
	flashduty.WithUserAgent("my-app/1.0"),
	flashduty.WithHTTPClient(customHTTPClient),
	flashduty.WithTransport(customRoundTripper),
	flashduty.WithLogger(myLogger),
	flashduty.WithRequestHeaders(staticHeaders),
	flashduty.WithRequestHook(func(req *http.Request) { /* e.g. inject traceparent */ }),
)
```

## Errors and rate limits

The SDK returns typed errors you can unwrap with `errors.As`, plus convenience predicates that see through wrapped errors:

```go theme={null}
_, _, err := client.Incidents.Info(ctx, &flashduty.IncidentInfoRequest{IncidentID: "does-not-exist"})

var apiErr *flashduty.ErrorResponse
if errors.As(err, &apiErr) {
	fmt.Println(apiErr.Code, apiErr.RequestID)
}

var rl *flashduty.RateLimitError
if errors.As(err, &rl) {
	time.Sleep(rl.RetryAfter)
}

// Convenience predicates
if flashduty.IsNotFound(err) { /* ... */ }
if flashduty.IsRateLimited(err) { /* ... */ }
switch flashduty.ErrorCodeOf(err) {
case flashduty.ErrorCodeAccessDenied, flashduty.ErrorCodeUnauthorized:
	// handle auth failures
}
```

Error codes map one-to-one to the [Error Code List](/en/openapi/introduction#error-code-list).

## Timestamps

Time fields on responses are typed `Timestamp` (Unix seconds) or `TimestampMilli` (milliseconds). They marshal to an RFC3339 string in the local timezone and unmarshal from either a numeric epoch or an RFC3339 string, so a value round-trips cleanly. The zero value stays the numeric `0` sentinel (never a 1970 date) and is dropped by `omitempty`.

```go theme={null}
inc := list.Items[0]
fmt.Println(inc.StartTime)          // 2026-05-30T14:37:11+08:00  (String / fmt / TOON)
b, _ := json.Marshal(inc.StartTime) // "2026-05-30T14:37:11+08:00"
epoch := inc.StartTime.Unix()       // 1779514631  (raw wire value)
t := inc.StartTime.Time()           // time.Time
```

<Note>
  Request time fields stay plain `int64` — the API expects a numeric epoch on the wire. Most endpoints take **seconds**, but RUM and webhook-history endpoints take **milliseconds**.
</Note>

## Retries

Automatic retries are **not** built into the core. Compose them at the transport layer with the optional `retry` subpackage — a safe-by-default retrying `http.RoundTripper` (retries 429 and 5xx, honors `Retry-After`, deterministic exponential backoff, and only replays requests whose body is replayable, which all SDK requests are):

```go theme={null}
import "github.com/flashcatcloud/go-flashduty/retry"

client, err := flashduty.NewClient("YOUR_APP_KEY",
	flashduty.WithTransport(retry.New(
		retry.WithMaxRetries(3),
	)),
)
```

## Resources

<CardGroup cols={2}>
  <Card title="GitHub repository" icon="github" href="https://github.com/flashcatcloud/go-flashduty">
    Source, full README, and issues. Licensed under Apache-2.0.
  </Card>

  <Card title="API Catalog" icon="list" href="/en/openapi/api-catalog">
    Browse every endpoint — each maps to a typed method in the SDK.
  </Card>
</CardGroup>
