# Go SDK

> Source: https://truto.one/docs/sdks/go/

`truto-go-sdk` is a Go client for the Truto API. It covers the Unified, Proxy, and Custom APIs, MCP, and most of the Admin API in the [Truto API Reference](/docs/api-reference/overview/introduction). It doesn't wrap [notification destinations](/docs/api-reference/admin/notification-destinations), [SSO connections](/docs/api-reference/admin/sso-connections), [SCIM](/docs/api-reference/admin/scim), [group mappings](/docs/api-reference/admin/group-mappings), or alarms yet; call those with [`client.Do`](#calling-any-endpoint). Admin API responses and most request bodies are typed structs generated from Truto's OpenAPI spec; a few admin methods, such as `Workflows.Update` and `WorkflowRuns.Create`, take a `truto.Record`.

Package: [pkg.go.dev/github.com/trutohq/truto-go-sdk](https://pkg.go.dev/github.com/trutohq/truto-go-sdk). Source: [github.com/trutohq/truto-go-sdk](https://github.com/trutohq/truto-go-sdk).

## Requirements

Go 1.23+. The SDK uses only the standard library.

## Installation

```bash
go get github.com/trutohq/truto-go-sdk@latest
```

```go
import truto "github.com/trutohq/truto-go-sdk"
```

## Quick start

Create an [API token](/docs/guides/api-tokens/creating-api-tokens), then:

```go
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	truto "github.com/trutohq/truto-go-sdk"
)

func main() {
	ctx := context.Background()
	client := truto.NewClient(os.Getenv("TRUTO_API_TOKEN"))

	// Installed integrations in the environment
	for integration, err := range client.EnvironmentIntegrations.List(ctx).Seq() {
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(*integration.ID)
	}

	// Integrated accounts for a tenant
	accounts, err := client.IntegratedAccounts.List(ctx,
		truto.WithQuery(truto.Params{"tenant_id": "acme-1"})).All()
	if err != nil {
		log.Fatal(err)
	}

	// A Unified API call
	contacts := client.Unified.List(ctx, truto.UnifiedResource{
		Model:               "crm",
		Resource:            "contacts",
		IntegratedAccountID: *accounts[0].ID,
	})
	for contact, err := range contacts.Seq() {
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(contact["id"], contact["first_name"])
	}
}
```

## How the client works

- **Context first, options last.** Every method takes a `context.Context` as its first argument and accepts `truto.Option`s at the end. Options passed to `NewClient` apply to every call; options passed to a method apply to that call only.
- **Resources.** Each Admin API resource is a field on `*truto.Client`, such as `client.Tenants` or `client.SyncJobs`. Most expose `List`, `Get`, `Create`, `Update`, and `Delete`.
- **Typed models.** Admin API methods take and return generated structs, such as `truto.TenantCreateRequest` and `truto.IntegratedAccountResponse`. Optional fields are pointers; set them with `truto.Ptr` (see [Typed models](#typed-models)).
- **Records.** Unified, Proxy, and MCP results depend on the integration, so they come back as `truto.Record` (`map[string]any`). Decode one into your own type with `truto.Decode` (see [Decode into your own types](#decode-into-your-own-types)).
- **Query parameters** go in `truto.WithQuery(truto.Params{...})`. Nested maps and slices are encoded the way the API expects (`filter[status]=open`, `ids[0]=a`). Admin list filters on related fields use a dotted key, such as `"integration.name": "hubspot"`.
- **Errors.** A non-2xx response returns a `*truto.Error` carrying the status, the parsed body, and the response headers (see [Errors](#errors)).
- **Base URL.** It defaults to `https://api.truto.one`. Pass `truto.WithBaseURL(url)` to `NewClient` to use a different one.

| Option | What it does |
|---|---|
| `WithQuery(truto.Params{...})` | Query parameters for the call |
| `WithTimeout(d)` | Time limit per attempt (default 30s) |
| `WithMaxRetries(n)` / `WithNoRetry()` | Number of retries (default 3), or none |
| `WithRetryStatusCodes(codes...)` | Extra status codes to retry, added to the defaults |
| `WithDefaultRetryAfter(d)` | Wait before retrying a `429` without `Retry-After` (default 10s) |
| `WithOnRetry(fn)` / `WithOnRateLimited(fn)` | Hooks called before a retry or a rate-limit wait |
| `WithHeader(k, v)` | Extra request header |
| `WithBaseURL(url)`, `WithHTTPClient(c)` | Client setup |

```go
client := truto.NewClient(os.Getenv("TRUTO_API_TOKEN"),
	truto.WithTimeout(20*time.Second),
	truto.WithMaxRetries(5),
)
```

## Errors

```go
_, err := client.IntegratedAccounts.Get(ctx, "missing-id")

var apiErr *truto.Error
switch {
case truto.IsNotFound(err): // also truto.IsUnauthorized, truto.IsRateLimited
	fmt.Println("not found")
case errors.Is(err, truto.ErrTimeout):
	fmt.Println("timed out")
case errors.As(err, &apiErr):
	fmt.Println(apiErr.StatusCode, apiErr.Message(), apiErr.Body)
}
```

`*truto.Error` has `StatusCode`, `Status`, `Body` (the parsed JSON body, or a string when the body isn't JSON), `RawBody`, and `Header`. `Message()` returns the `message` (or `error`) field of the JSON body, so Truto's error text is available without parsing it yourself.

## Retries and timeouts

The Go SDK retries failed requests for you. Keep the [rate limits](/docs/api-reference/overview/errors#rate-limits) in mind: 100 requests per second per API token, and 50 requests per 10 seconds per integrated account.

- **Retried statuses.** `408`, `409`, `425`, `429`, `502`, `503`, and `504`, plus any added with `WithRetryStatusCodes`. Network errors are retried too. `409` is retried by default, unlike the [retry strategy](/docs/api-reference/overview/errors#retry-strategy) advice. Use `truto.WithNoRetry()` where a conflict is expected, such as `Tenants.Create` with an ID that may exist.
- **Waits.** A `429` waits for the `Retry-After` header, or 10 seconds when it is missing. Other retryable statuses back off with full jitter: a random wait up to 500 ms, with the ceiling doubling each time, capped at 10 seconds.
- **Retries.** Up to 3 by default. `WithMaxRetries(n)` changes it; `WithNoRetry()` sends the request once.
- **Timeout.** 30 seconds per attempt by default. A timed-out attempt returns `truto.ErrTimeout` and is not retried. Cancelling the context stops any further attempt.

```go
account, err := client.IntegratedAccounts.Get(ctx, "ia-id",
	truto.WithTimeout(5*time.Second),
	truto.WithMaxRetries(5),
	truto.WithOnRetry(func(r truto.RetryInfo) {
		log.Printf("retry %d after status %d, waiting %s", r.RetryCount, r.StatusCode, r.Wait)
	}),
	truto.WithOnRateLimited(func(r truto.RateLimitInfo) {
		log.Printf("rate limited, waiting %s", r.RetryAfter)
	}),
)
```

The request body is sent again on every attempt. Retry a `Create` or other write only when sending it twice is harmless: a `502` or `504` can arrive after the provider has already applied the change. Pass `truto.WithNoRetry()` on writes that aren't safe to repeat.

## Pagination

`List` methods return a lazy `*truto.Iterator[T]` that follows `next_cursor` for you. No request is sent until you read from it.

Range over it with `Seq()`:

```go
runs := client.SyncJobRuns.List(ctx, truto.WithQuery(truto.Params{"sync_job_id": "sync-job-id"}))
for run, err := range runs.Seq() {
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(*run.ID, *run.Status)
}
```

Collect every page with `All()`, or take the first item with `First()`:

```go
tenants, err := client.Tenants.List(ctx).All()
first, err := client.Tenants.List(ctx).First() // truto.Done if there are none
```

Or fetch one page, or one item, at a time:

```go
it := client.Tenants.List(ctx, truto.WithQuery(truto.Params{"limit": 50}))
for {
	page, err := it.NextPage() // page.Items, page.NextCursor
	if errors.Is(err, truto.Done) {
		break
	}
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(len(page.Items))
}

item, err := it.Next() // one item at a time; truto.Done at the end
```

## Unified API

Accessible via `client.Unified`. Each call takes a `truto.UnifiedResource` naming the model, the resource, and the integrated account. See the [Unified API reference](/docs/api-reference/overview/introduction) for the models and resources available.

```go
contacts := truto.UnifiedResource{Model: "crm", Resource: "contacts", IntegratedAccountID: "766cc1ee-..."}

it := client.Unified.List(ctx, contacts, truto.WithQuery(truto.Params{"limit": 50}))
contact, err := client.Unified.Get(ctx, contacts, "123")
created, err := client.Unified.Create(ctx, contacts, map[string]any{"first_name": "Ada"})
_, err = client.Unified.Update(ctx, contacts, "123", map[string]any{"first_name": "Grace"})
_, err = client.Unified.Delete(ctx, contacts, "123")

// Custom methods: POST /unified/{model}/{resource}/{method}
results, err := client.Unified.CustomMethod(ctx, contacts, "search", map[string]any{"query": "acme"})
```

| Method | Endpoint |
|---|---|
| `List(ctx, r, opts...)` | `GET /unified/{model}/{resource}` |
| `Get(ctx, r, id, opts...)` | `GET /unified/{model}/{resource}/{id}` |
| `Create(ctx, r, body, opts...)` | `POST /unified/{model}/{resource}` |
| `Update(ctx, r, id, body, opts...)` | `PATCH /unified/{model}/{resource}/{id}` |
| `Delete(ctx, r, id, opts...)` | `DELETE /unified/{model}/{resource}/{id}` |
| `CustomMethod(ctx, r, method, body, opts...)` | `POST /unified/{model}/{resource}/{method}` |
| `Meta(ctx, model, resource, integrationName, method, opts...)` | `GET /unified/{model}/{resource}/{integration_name}/meta/{method}` |
| `ModelMeta(ctx, model, integrationName, opts...)` | `GET /unified/meta/{model}/{integration_name}` |

`Meta` returns an integration's mapping metadata (schema and supported parameters) for one unified method. `ModelMeta` returns the resources and methods of a unified model that an integration supports.

```go
meta, err := client.Unified.Meta(ctx, "crm", "contacts", "hubspot", "list")
crmOnHubSpot, err := client.Unified.ModelMeta(ctx, "crm", "hubspot")
```

### Decode into your own types

`truto.Decode` converts a `truto.Record` into your own struct, and `truto.DecodeAll` converts a slice of them:

```go
type Contact struct {
	ID        string `json:"id"`
	FirstName string `json:"first_name"`
}

record, err := client.Unified.Get(ctx, contacts, "123")
c, err := truto.Decode[Contact](*record)

records, err := client.Unified.List(ctx, contacts).All()
all, err := truto.DecodeAll[Contact](records)
```

## Proxy API

Accessible via `client.Proxy`. Calls go to the integration's own API through Truto, with resource names as defined on the integration. Each call takes a `truto.ProxyResource`.

```go
tickets := truto.ProxyResource{Resource: "tickets", IntegratedAccountID: "766cc1ee-..."}

it := client.Proxy.List(ctx, tickets)
ticket, err := client.Proxy.Get(ctx, tickets, "123")
_, err = client.Proxy.Create(ctx, tickets, map[string]any{"subject": "Help"})
_, err = client.Proxy.Update(ctx, tickets, "123", map[string]any{"status": "closed"})
_, err = client.Proxy.Delete(ctx, tickets, "123")
_, err = client.Proxy.CustomMethod(ctx, tickets, "merge", map[string]any{"ids": []string{"1", "2"}})
```

| Method | Endpoint |
|---|---|
| `List(ctx, r, opts...)` | `GET /proxy/{resource}` |
| `Get(ctx, r, id, opts...)` | `GET /proxy/{resource}/{id}` |
| `Create(ctx, r, body, opts...)` | `POST /proxy/{resource}` |
| `Update(ctx, r, id, body, opts...)` | `PATCH /proxy/{resource}/{id}` |
| `Delete(ctx, r, id, opts...)` | `DELETE /proxy/{resource}/{id}` |
| `CustomMethod(ctx, r, method, body, opts...)` | `POST /proxy/{resource}/{method}` |

## Custom API

Accessible via `client.Custom`. Calls a custom endpoint defined on the integration, at `/custom/{path}`. A custom endpoint can return anything, so these calls return a `*truto.RawResponse` with `StatusCode`, `ContentType`, and `Body` (`[]byte`).

```go
res, err := client.Custom.Get(ctx, "766cc1ee-...", "my/custom/path")
fmt.Println(res.ContentType, string(res.Body))

_, err = client.Custom.Post(ctx, "766cc1ee-...", "my/custom/path", map[string]any{"key": "value"})
```

| Method | Endpoint |
|---|---|
| `Get(ctx, integratedAccountID, path, opts...)` | `GET /custom/{path}` |
| `Post(ctx, integratedAccountID, path, body, opts...)` | `POST /custom/{path}` |
| `Put(ctx, integratedAccountID, path, body, opts...)` | `PUT /custom/{path}` |
| `Patch(ctx, integratedAccountID, path, body, opts...)` | `PATCH /custom/{path}` |
| `Delete(ctx, integratedAccountID, path, opts...)` | `DELETE /custom/{path}` |

## MCP

Accessible via `client.MCP`. Talks to an integrated account's MCP server, using the token from [Create MCP server](#mcp-servers).

```go
mcpToken := "your-mcp-token"

_, err := client.MCP.Initialize(ctx, mcpToken, "my-client", "1.0.0")
tools, err := client.MCP.ListTools(ctx, mcpToken)
result, err := client.MCP.CallTool(ctx, mcpToken, "list_tickets", map[string]any{"status": "open"})

// Any other JSON-RPC message
_, err = client.MCP.Call(ctx, mcpToken, map[string]any{"jsonrpc": "2.0", "method": "tools/list", "id": 3})
```

| Method | Description |
|---|---|
| `Initialize(ctx, token, clientName, clientVersion, opts...)` | Sends `initialize` |
| `ListTools(ctx, token, opts...)` | Sends `tools/list` |
| `CallTool(ctx, token, name, args, opts...)` | Sends `tools/call` |
| `Call(ctx, token, payload, opts...)` | Sends a raw JSON-RPC payload to `POST /mcp/{token}` |

## Integrated accounts

Accessible via `client.IntegratedAccounts`. [API reference](/docs/api-reference/admin/integrated-accounts).

```go
it := client.IntegratedAccounts.List(ctx, truto.WithQuery(truto.Params{"tenant_id": "acme-1"}))
account, err := client.IntegratedAccounts.Get(ctx, "ia-id")

_, err = client.IntegratedAccounts.Validate(ctx, "ia-id")
_, err = client.IntegratedAccounts.BulkDelete(ctx, truto.Record{"tenant_id": "acme-1"})
capabilities, err := client.IntegratedAccounts.Capabilities(ctx, "ia-id")
```

| Method | Endpoint |
|---|---|
| `List(ctx, opts...)` | `GET /integrated-account` |
| `Get(ctx, id, opts...)` | `GET /integrated-account/{id}` |
| `Create(ctx, body, opts...)` | `POST /integrated-account` (import an account with existing credentials) |
| `Update(ctx, id, body, opts...)` | `PATCH /integrated-account/{id}` |
| `Delete(ctx, id, opts...)` | `DELETE /integrated-account/{id}` |
| `UpdateCredentials(ctx, id, body, opts...)` | `PATCH /integrated-account/{id}/credentials` |
| `BulkDelete(ctx, body, opts...)` | `POST /integrated-account/bulk-delete` (by `ids` or `tenant_id`) |
| `CreateToken(ctx, id, opts...)` | `POST /integrated-account/token` |
| `RefreshCredentials(ctx, id, opts...)` | `POST /integrated-account/refresh-credentials` |
| `RefreshMappingCache(ctx, body, opts...)` | `POST /integrated-account/refresh-mapping-cache` |
| `CreateFormToken(ctx, id, opts...)` | `POST /integrated-account/form-token` |
| `RunPostInstallActions(ctx, id, opts...)` | `POST /integrated-account/run-post-install-actions` |
| `Validate(ctx, id, opts...)` | `POST /integrated-account/{id}/validate` |
| `Capabilities(ctx, id, opts...)` | `GET /integrated-account/{id}/capabilities` |
| `Tools(ctx, id, filter, opts...)` | `GET /integrated-account/{id}/tools` (deprecated; use `Capabilities`) |

`Tools` takes a `truto.ToolsFilter{Methods: []string{"list", "get"}, Tags: []string{...}}`; pass an empty `truto.ToolsFilter{}` for every tool.

### MCP servers

Each integrated account can have MCP servers. [API reference](/docs/api-reference/admin/mcp).

```go
server, err := client.IntegratedAccounts.CreateMCPServer(ctx, "ia-id", truto.IntegratedAccountMcpCreateRequest{
	Name:   "Support agent",
	Config: truto.IntegratedAccountMcpCreateRequestConfig{Methods: []string{"list", "get"}},
})
servers, err := client.IntegratedAccounts.ListMCPServers(ctx, "ia-id").All()
```

| Method | Endpoint |
|---|---|
| `ListMCPServers(ctx, iaID, opts...)` (an iterator) | `GET /integrated-account/{ia_id}/mcp` |
| `CreateMCPServer(ctx, iaID, body, opts...)` | `POST /integrated-account/{ia_id}/mcp` |
| `GetMCPServer(ctx, iaID, id, opts...)` | `GET /integrated-account/{ia_id}/mcp/{id}` |
| `UpdateMCPServer(ctx, iaID, id, body, opts...)` | `PATCH /integrated-account/{ia_id}/mcp/{id}` |
| `DeleteMCPServer(ctx, iaID, id, opts...)` | `DELETE /integrated-account/{ia_id}/mcp/{id}` |

## Link tokens

Accessible via `client.LinkTokens`. [API reference](/docs/api-reference/admin/link-token).

```go
token, err := client.LinkTokens.Create(ctx, truto.LinkTokenRequest{TenantID: truto.Ptr("acme-1")})
fmt.Println(*token.LinkToken)
```

| Method | Endpoint |
|---|---|
| `Create(ctx, body, opts...)` | `POST /link-token` |

## Session tokens

Short-lived tokens scoped to one tenant, used by the [connections widget](/docs/guides/link-sdk/connections-widget). Accessible via `client.SessionTokens`.

```go
session, err := client.SessionTokens.Create(ctx, truto.SessionTokenRequest{TenantID: "acme-1"})
```

| Method | Endpoint |
|---|---|
| `Create(ctx, body, opts...)` | `POST /session-token` |
| `Revoke(ctx, body, opts...)` | `DELETE /session-token` |
| `Renew(ctx, opts...)` | `POST /session-token/renew` |
| `Me(ctx, opts...)` | `GET /session-token/me` |

## Tenants

Accessible via `client.Tenants`. [API reference](/docs/api-reference/admin/tenants).

```go
_, err := client.Tenants.Create(ctx, truto.TenantCreateRequest{ID: "acme-1", Name: truto.Ptr("Acme")})
_, err = client.Tenants.Update(ctx, "acme-1", truto.TenantUpdateRequest{Name: truto.Ptr("Acme Inc")})
```

| Method | Endpoint |
|---|---|
| `List`, `Get`, `Create`, `Update`, `Delete` | `/tenant`, `/tenant/{id}` |
| `BulkCreate(ctx, body, opts...)` | `POST /tenant/bulk` |

## Integrations

Accessible via `client.Integrations`. [API reference](/docs/api-reference/admin/integrations).

```go
integration, err := client.Integrations.Get(ctx, "integration-id")
capabilities, err := client.Integrations.Capabilities(ctx, "hubspot") // ID or name

_, err = client.Integrations.Install(ctx, "integration-id", true)
_, err = client.Integrations.Uninstall(ctx, "integration-id")
```

| Method | Endpoint |
|---|---|
| `List`, `Get`, `Create`, `Update`, `Delete` | `/integration`, `/integration/{id}` |
| `Capabilities(ctx, idOrName, opts...)` | `GET /integration/{id}/capabilities` |
| `Tools(ctx, idOrName, opts...)` | `GET /integration/{id}/tools` (deprecated; use `Capabilities`) |
| `UnifiedAPIs(ctx, idOrName, opts...)` | `GET /integration/{id}/unified-apis` (deprecated; use `Capabilities`) |
| `Install(ctx, integrationID, isEnabled, opts...)` | `POST /environment-integration` |
| `Uninstall(ctx, integrationID, opts...)` | Finds the integration's environment integration and deletes it |

## Environment integrations

Integrations installed in your environment. Accessible via `client.EnvironmentIntegrations`. [API reference](/docs/api-reference/admin/installed-integrations-environment-integrations).

| Method | Endpoint |
|---|---|
| `List(ctx, opts...)` | `GET /environment-integration` |
| `Get(ctx, id, opts...)` | `GET /environment-integration/{id}` |
| `Create(ctx, body, opts...)` | `POST /environment-integration` |
| `Update(ctx, id, body, opts...)` | `PATCH /environment-integration/{id}` |
| `Delete(ctx, id, opts...)` | `DELETE /environment-integration/{id}` |

## Unified models

Accessible via `client.UnifiedModels`. [API reference](/docs/api-reference/admin/unified-models).

| Method | Endpoint |
|---|---|
| `List`, `Get`, `Create`, `Update`, `Delete` | `/unified-model`, `/unified-model/{id}` |
| `Install(ctx, unifiedModelID, opts...)` | `POST /environment-unified-model` |
| `Uninstall(ctx, unifiedModelID, opts...)` | Finds the model's environment unified model and deletes it |

## Environment unified models

Unified models installed in your environment. Accessible via `client.EnvironmentUnifiedModels`. [API reference](/docs/api-reference/admin/installed-unified-models-environment-unified-models).

| Method | Endpoint |
|---|---|
| `List`, `Get`, `Create`, `Update`, `Delete` | `/environment-unified-model`, `/environment-unified-model/{id}` |

## Unified model mappings

How a unified model's resource methods map to an integration. Both resources expose `List`, `Get`, `Create`, `Update`, and `Delete`.

| Field | Endpoint | Reference |
|---|---|---|
| `client.UnifiedModelResourceMethods` | `/unified-model-resource-method` | [Unified model mappings](/docs/api-reference/admin/unified-model-mappings) |
| `client.EnvironmentUnifiedModelResourceMethods` | `/environment-unified-model-resource-method` | [Environment unified model mappings](/docs/api-reference/admin/installed-unified-models-environment-unified-model-mappings) |

```go
overrides := client.EnvironmentUnifiedModelResourceMethods.List(ctx,
	truto.WithQuery(truto.Params{"environment_unified_model_id": "..."}))
for override, err := range overrides.Seq() {
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(*override.ID)
}
```

## Sync jobs

Each resource below exposes `List`, `Get`, `Create`, `Update`, and `Delete`.

| Field | Endpoint | Extra methods | Reference |
|---|---|---|---|
| `client.SyncJobs` | `/sync-job` | | [Sync jobs](/docs/api-reference/admin/sync-jobs) |
| `client.SyncJobRuns` | `/sync-job-run` | | [Sync job runs](/docs/api-reference/admin/sync-job-runs) |
| `client.SyncJobCronTriggers` | `/sync-job-cron-trigger` | `Schedule(ctx, id)`: `POST /sync-job-cron-trigger/{id}/schedule` | [Sync job cron triggers](/docs/api-reference/admin/sync-job-cron-triggers) |
| `client.SyncJobTemplates` | `/sync-job-template` | | [Sync job templates](/docs/api-reference/admin/sync-job-templates) |
| `client.SyncJobRunStates` | `/sync-job-run-state` | | [Sync job run state](/docs/api-reference/admin/sync-job-run-state) |

```go
run, err := client.SyncJobRuns.Create(ctx, truto.SyncJobRunCreateRequest{
	SyncJobID:           "sync-job-id",
	IntegratedAccountID: "766cc1ee-...",
})
_, err = client.SyncJobRuns.Delete(ctx, *run.ID) // stops the run
```

## Webhooks

Accessible via `client.Webhooks`. [API reference](/docs/api-reference/admin/webhooks).

```go
webhook, err := client.Webhooks.Create(ctx, truto.WebhookCreateRequest{TargetURL: "https://example.com/truto"})
_, err = client.Webhooks.Test(ctx, truto.WebhookTestRequest{ID: *webhook.ID})
```

| Method | Endpoint |
|---|---|
| `List`, `Get`, `Create`, `Update`, `Delete` | `/webhook`, `/webhook/{id}` |
| `Test(ctx, body, opts...)` | `POST /webhook/test` |

## Workflows

Accessible via `client.Workflows` and `client.WorkflowRuns`. API reference: [Workflows](/docs/api-reference/admin/workflows), [Workflow runs](/docs/api-reference/admin/workflowruns).

```go
runs := client.WorkflowRuns.List(ctx, truto.WithQuery(truto.Params{"workflow_id": "workflow-id"}))
for run, err := range runs.Seq() {
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(run.Status)
}
```

| Field | Methods | Endpoint |
|---|---|---|
| `client.Workflows` | `List`, `Get`, `Create`, `Update`, `Delete` | `/workflow`, `/workflow/{id}` |
| `client.WorkflowRuns` | `List`, `Get`, `Create`, `Update`, `Delete` | `/workflow-run`, `/workflow-run/{id}` |

## Datastores

Accessible via `client.Datastores`. [API reference](/docs/api-reference/admin/datastores).

| Method | Endpoint |
|---|---|
| `List`, `Get`, `Create`, `Update`, `Delete` | `/datastore`, `/datastore/{id}` |
| `Test(ctx, id, method, body, opts...)` | `POST /datastore/{id}/test/{method}` |

## Static gates

Accessible via `client.StaticGates`. [API reference](/docs/api-reference/admin/static-gates).

| Method | Endpoint |
|---|---|
| `List`, `Get`, `Create`, `Update`, `Delete` | `/static-gate`, `/static-gate/{id}` |

## Observability destinations

Accessible via `client.ObservabilityDestinations`. [API reference](/docs/api-reference/admin/observability-destinations).

```go
_, err := client.ObservabilityDestinations.Test(ctx, "observability-destination-id",
	truto.ObservabilityDestinationTestRequest{})
```

| Method | Endpoint |
|---|---|
| `List`, `Get`, `Create`, `Update`, `Delete` | `/observability-destination`, `/observability-destination/{id}` |
| `Test(ctx, id, body, opts...)` | `POST /observability-destination/{id}/test` |

## Documentation

Accessible via `client.Documentation`. [API reference](/docs/api-reference/admin/documentation).

| Method | Endpoint |
|---|---|
| `List`, `Get`, `Create`, `Update`, `Delete` | `/documentation`, `/documentation/{id}` |

## Batch requests

Accessible via `client.BatchRequests`. [API reference](/docs/api-reference/admin/batch-requests).

| Method | Endpoint |
|---|---|
| `Create(ctx, body, opts...)` | `POST /batch-request` |

## Analytics

Accessible via `client.Analytics`. [API reference](/docs/api-reference/admin/analytics). Pass query parameters with `truto.WithQuery`.

```go
dashboard, err := client.Analytics.Dashboard(ctx, truto.WithQuery(truto.Params{"range": "7d"}))
series, err := client.Analytics.Timeseries(ctx, truto.WithQuery(truto.Params{
	"environment_id": "environment-id",
	"metric":         "api_calls",
	"from":           "2026-10-01",
	"to":             "2026-10-08",
}))
```

| Method | Endpoint |
|---|---|
| `Status(ctx, opts...)` | `GET /analytics/status` |
| `Metrics(ctx, opts...)` | `GET /analytics/metrics` |
| `Dashboard(ctx, opts...)` | `GET /analytics/dashboard` |
| `Timeseries(ctx, opts...)` | `GET /analytics/timeseries` |
| `Breakdown(ctx, opts...)` | `GET /analytics/breakdown` |

## Environments, API tokens, categories, and logs

| Field | Methods | Endpoint | Reference |
|---|---|---|---|
| `client.Environments` | `List`, `Get`, `Update` | `/environment` | [Environments](/docs/api-reference/admin/environments) |
| `client.APITokens` | `List`, `Get` | `/api-token` | [API tokens](/docs/api-reference/admin/api-tokens) |
| `client.Categories` | `List` | `/category` | [Categories](/docs/api-reference/admin/categories) |
| `client.Logs` | `List` | `/log` | [Logs](/docs/api-reference/admin/logs) |

`client.Logs.List` needs a `log_type` query parameter: `unified_proxy_api`, `rapid_bridge`, `webhook`, `mcp`, or `sync_job_cron_trigger`.

```go
logs := client.Logs.List(ctx, truto.WithQuery(truto.Params{"log_type": "unified_proxy_api"}))
```

`client.Ping(ctx)` calls `GET /ping` to check that your token works.

## Daemons

These use `truto.Record` instead of typed models. See the [daemon guide](/docs/guides/daemon/overview).

| Field | Methods | Endpoint | Reference |
|---|---|---|---|
| `client.Daemons` | `List`, `Get`, `Create`, `Update`, `Delete` | `/daemon`, `/daemon/{id}` | [Daemons](/docs/api-reference/admin/daemons) |
| `client.DaemonJobs` | `List`, `Get`, `Create`, `Update`, `Delete` | `/daemon-job`, `/daemon-job/{id}` | [Daemon jobs](/docs/api-reference/admin/daemon-jobs) |
| `client.DaemonJobRuns` | `List`, `Get`, `Create`, `Update`, `Delete` | `/daemon-job-run`, `/daemon-job-run/{id}` | [Daemon job runs](/docs/api-reference/admin/daemon-job-runs) |
| `client.DaemonJobCronTriggers` | `List`, `Get`, `Create`, `Update`, `Delete`, `Schedule` | `/daemon-job-cron-trigger`, `/daemon-job-cron-trigger/{id}` | [Daemon job cron triggers](/docs/api-reference/admin/daemon-job-cron-triggers) |

## Other resources

These resources are in the SDK but not in the public API reference. They use `truto.Record` instead of typed models.

| Field | Methods | Endpoint |
|---|---|---|
| `client.Users` | `List`, `Get` | `/user`, `/user/{id}` |
| `client.Teams` | `List`, `Get`, `Update` | `/team`, `/team/{id}` |
| `client.SandboxIntegratedAccounts` | `List`, `Get`, `Create`, `Delete` | `/sandbox-integrated-account`, `/sandbox-integrated-account/{id}` |
| `client.IntegratedAccountWebhooks` | `Process(ctx, integratedAccountID, payload)` | `POST /integrated-account-webhook/{id}` |
| `client.EnvironmentIntegrationWebhooks` | `Process(ctx, environmentIntegrationID, payload)` | `POST /environment-integration-webhook/{id}` |

## Calling any endpoint

For an endpoint the SDK doesn't wrap yet, `client.Do` sends a request with the same auth, retries, and error handling, and decodes the JSON response into `out`:

```go
var out map[string]any
err := client.Do(ctx, "GET", "/some/path", nil, &out, truto.WithQuery(truto.Params{"a": 1}))
```

## Typed models

Admin API types such as `truto.IntegratedAccountResponse`, `truto.SyncJobCreateRequest`, and `truto.WebhookSchema` are generated from Truto's OpenAPI spec.

- **Optional fields are pointers.** Set them with `truto.Ptr`: `truto.TenantUpdateRequest{Name: truto.Ptr("Acme")}`.
- **Enums are open string types** with constants, such as `truto.IntegratedAccountStatus`. A value the SDK doesn't know yet still decodes unchanged.
- **Union types**, such as `truto.DatastoreConfig`, hold the raw JSON. Decode a specific shape with the `As...` methods (`cfg.AsDatastoreS3Config()`), and build one with the `truto.DatastoreConfigFrom...` functions.
- **Unknown JSON fields are ignored**, so new API fields don't break decoding.
