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

# Go SDK

> knoxcall-go — the official KnoxCall client for Go. OAuth 2.1 under the hood, zero dependencies (stdlib only).

`knoxcall-go` is the official KnoxCall client for Go — stdlib only, context-first, goroutine-safe (the token store uses single-flight refresh).

## Install

<Note>
  Not yet published — install from the monorepo path with a `replace` directive in your `go.mod`, or via a git checkout.
</Note>

```text theme={"dark"}
replace github.com/knoxcall/knoxcall-go => ../knoxcall-go
```

## Create a client

```go theme={"dark"}
import "github.com/knoxcall/knoxcall-go/knoxcall"

// Credentials inline — tenant auto-discovered from the credential.
client, err := knoxcall.New(knoxcall.Options{ClientID: "tk_xxxxxxxx", ClientSecret: "..."})

// Or zero-option with the environment configured
// (KNOXCALL_CLIENT_ID, KNOXCALL_CLIENT_SECRET):
client, err = knoxcall.New(knoxcall.Options{})

// A pre-acquired key also works (kc_… token or legacy tk_… / AKE… key):
client, err = knoxcall.New(knoxcall.Options{APIKey: os.Getenv("KNOXCALL_API_KEY")})
```

Set `Sandbox: true` to target the isolated Test data plane (`sandbox.knoxcall.com` management host, `sandbox-{tenant}.knoxcall.com` proxy host) with a `tk_test_` key. Passing conflicting credential options (e.g. `APIKey` and `ClientID`) is a construction error; explicit options always beat the environment.

<Note>
  DPoP-bound OAuth clients are not yet supported by the Go SDK — if the server issues a DPoP-bound token, the SDK fails fast with a clear error rather than mis-authenticating. Use a Bearer OAuth client, or the Node.js / Python SDK.
</Note>

## Manage resources

Create a route, then list with pagination — single-object methods return the unwrapped object; paginated lists return a typed `Page[T]`; `ListAll` walks every page:

```go theme={"dark"}
ctx := context.Background()

route, err := client.Routes.Create(ctx, knoxcall.CreateRouteInput{Name: "orders", TargetBaseURL: "https://api.example.com"})

page, err := client.Routes.List(ctx, &knoxcall.ListParams{Page: 2, PerPage: 50})
fmt.Println(page.Meta.Total, page.Meta.TotalPages, page.Meta.RequestID)

// Walk every page in one call:
all, err := client.Secrets.ListAll(ctx, nil)
```

The same pattern covers every resource: `Secrets`, `Webhooks`, `Clients`, `OAuthClients`, `Environments`, `APIKeys`, `Account`, `AuditLogs`, `Agents`, `Crypto`, `PKI`, `Vaults`, and `DynamicDB`. All methods take a `context.Context` first.

## Call routes through the proxy

`client.Call()` proxies a request through a KnoxCall route to your upstream and returns the raw `*http.Response` — the upstream's status belongs to you; the SDK never turns it into an error. Reference routes by **slug** (write-once, rename-proof); UUIDs also work.

```go theme={"dark"}
// GET
resp, err := client.Call(ctx, "api-orders", &knoxcall.CallOptions{Path: "/users"})
defer resp.Body.Close()

// POST with a body, targeting a specific environment
resp, err = client.Call(ctx, "api-orders", &knoxcall.CallOptions{
    Method:      "POST",
    Path:        "/v1/charges",
    Body:        map[string]any{"amount": 2000, "currency": "usd"},
    Environment: "staging",
})
```

For a per-call timeout, pass a context with a deadline (`context.WithTimeout`).

### Bound routes

State the route (and optional defaults) once with `client.Route()`, then use plain HTTP verbs:

```go theme={"dark"}
printnode := client.Route("api-printnode", &knoxcall.BoundRouteOptions{Environment: "production"})

resp, err := printnode.Get(ctx, "/computers", nil)
resp, err = printnode.Post(ctx, "/printjobs", &knoxcall.CallOptions{Body: job})
resp, err = printnode.Request(ctx, "DELETE", "/printjobs/42", nil)
// per-call options still override the bound defaults:
resp, err = printnode.Get(ctx, "/computers", &knoxcall.CallOptions{Environment: "staging"})
```

The handle holds no state beyond the defaults — retries, token refresh, and 401 re-mint behave exactly as on `Call()`.

## Verify webhooks

`ConstructWebhookEvent` (also available as `client.Webhooks.ConstructEvent`) verifies the delivery's HMAC-SHA256 signature and parses it into a typed event in one step. Pass the raw body bytes — never re-serialized JSON:

```go theme={"dark"}
func handler(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    event, err := knoxcall.ConstructWebhookEvent(body, r.Header, endpointSecret, nil)
    if err != nil {
        var ve *knoxcall.WebhookSignatureVerificationError
        if errors.As(err, &ve) {
            http.Error(w, "bad signature", http.StatusBadRequest)
            return
        }
    }
    switch event.Event {
    case "audit.event":
        audit, _ := event.AuditData()
        log.Println("audit:", audit.Action)
    default: // request.*
        data, _ := event.RequestData()
        log.Println(data.RouteName, data.Response.Status)
    }
}
```

Formats beyond the default `legacy` header (`stripe`, `github`, `slack`, `aws-sns`, `custom`) are selected with `&knoxcall.ConstructEventOptions{Format: "stripe"}`; the replay window defaults to 300s; `custom` requires `HeaderName`.

## Handle errors

All API failures are typed and unwrap to `*APIError` (status, machine-readable type, human message, `RequestID` for support):

```go theme={"dark"}
_, err := client.Routes.Create(ctx, input)
var rl *knoxcall.RateLimitError
var ve *knoxcall.ValidationError
switch {
case errors.As(err, &rl):
    time.Sleep(time.Duration(rl.RetryAfter) * time.Second)
case errors.As(err, &ve):
    log.Println("validation failed:", ve.Fields)
}
```

Hierarchy: `AuthenticationError` (401), `PermissionDeniedError` (403), `NotFoundError` (404), `ConflictError` (409), `ValidationError` (422), `RateLimitError` (429), `ServerError` (5xx), plus `SignupError`, `WebhookSignatureVerificationError`, and `ConnectionError` / `ConnectionTimeoutError` for transport failures.

## Retries and idempotency

Management requests retry automatically on transport errors and HTTP 408/429/500/502/503/504 (never 409), with exponential half-jitter backoff and `Retry-After` honored up to 30s. Every mutating request carries a ULID `X-Idempotency-Key` that stays stable across retries. A 401 purges the cached token and retries once with fresh credentials. Tune with `Options{RetryMaxAttempts, RetryBaseDelay, RetryMaxDelay}`.

## Full reference

The package README documents every resource method, the ephemeral proxy, field-actions, crypto/PKI/vault operations, and credential-less `Signup()`: [`sdk/knoxcall-go/README.md`](https://github.com/knoxcall/knoxcall/tree/main/sdk/knoxcall-go) in the monorepo.
