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

# Go SDK

> Typed Go client for the Chief public REST API.

[`chief-go`](https://github.com/Storytell-ai/chief-go) is the official Go client
for the Chief API. Use it to build Chief into a Go service or automation: start
chats over your project, upload and label assets, schedule actions, and page
through results with typed methods instead of hand-rolled HTTP.

It's also the foundation the [`chief` CLI](/tools/cli/overview) and
[MCP server](/mcp/overview) are built on — so anything they do, you can do directly
in Go. It depends only on the standard library, so you can vendor it into external
tools without pulling in third-party packages.

## Install

```bash theme={null}
go get github.com/Storytell-ai/chief-go/chief
```

## Requirements

[Go 1.26 or newer](https://go.dev/doc/install). The client depends only on the
standard library, so it adds no third-party transitive dependencies to your
module.

## Credentials

A Personal Access Token is required and sent as the `X-API-Key` header. Most
routes are project-scoped and also need a project id (`X-Project-Id`). The
`Projects.List` call is the exception — it works with only an API key and
returns the projects the key can reach.

Create a token in the Chief app under [**Settings → API tokens**](https://chief.bot/settings/tokens) — see
[Introduction](/api-reference/introduction) for details on tokens and project
ids.

Pass credentials as options or let the client read them from the environment:

| Option          | Environment variable | Required                                    |
| --------------- | -------------------- | ------------------------------------------- |
| `WithAPIKey`    | `CHIEF_API_KEY`      | yes                                         |
| `WithProjectID` | `CHIEF_PROJECT_ID`   | for project-scoped routes                   |
| `WithBaseURL`   | `CHIEF_BASE_URL`     | no (defaults to `https://api.storytell.ai`) |

## Resources

Each resource is its own service on the client: `client.Chats`,
`client.Assets`, `client.Labels`, `client.Actions`, `client.Sessions`,
`client.Skills`, `client.Memories`, and `client.Projects`.

## Chats

Chat turns run asynchronously: `Create` and `SendMessage` return as soon as the
workflow is accepted. `WaitForResponse` polls `GetMessage` until the turn
finishes — or call `GetMessage` directly to poll on your own schedule.

```go theme={null}
package main

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

	"github.com/Storytell-ai/chief-go/chief"
)

func main() {
	client, err := chief.New(
		chief.WithAPIKey(os.Getenv("CHIEF_API_KEY")),
		chief.WithProjectID(os.Getenv("CHIEF_PROJECT_ID")),
	)
	if err != nil {
		log.Fatal(err)
	}

	ctx := context.Background()

	// Open a chat with its first turn; the turn runs asynchronously.
	created, err := client.Chats.Create(ctx, &chief.CreateChatRequest{
		Prompt: "What changed in the codebase this week?",
	})
	if err != nil {
		log.Fatal(err)
	}

	// Block until the response is populated.
	msg, err := client.Chats.WaitForResponse(ctx, created.ChatID, created.MessageID, 2*time.Minute)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("answer: %s\n", msg.Response)

	// Ask a follow-up in the same chat.
	sent, err := client.Chats.SendMessage(ctx, created.ChatID, &chief.SendMessageRequest{
		Prompt: "Now summarize that in one sentence.",
	})
	if err != nil {
		log.Fatal(err)
	}

	follow, err := client.Chats.WaitForResponse(ctx, created.ChatID, sent.MessageID, 2*time.Minute)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("follow-up: %s\n", follow.Response)
}
```

## Assets

`UploadFile` runs the full three-step flow — create the asset row, `PUT` the
bytes to the signed URL, then complete the upload to start ingest. The returned
bool is `true` when the content was a dedup hit and no bytes were uploaded.

```go theme={null}
// Upload a local file, then block until ingest reaches a terminal status.
asset, deduped, err := client.Assets.UploadFile(ctx, "report.pdf")
if err != nil {
	log.Fatal(err)
}
fmt.Printf("asset %q (dedup hit: %t)\n", asset.AssetID, deduped)

asset, err = client.Assets.WaitForReady(ctx, asset.AssetID, 2*time.Minute)
if err != nil {
	log.Fatal(err)
}
fmt.Printf("status: %s\n", asset.Status)

// Attach a label by name; an unmatched name is auto-created.
label, err := client.Assets.AttachLabel(ctx, asset.AssetID, "quarterly")
if err != nil {
	log.Fatal(err)
}
fmt.Printf("attached label %q (%s)\n", label.Name, label.LabelID)

// List the assets in the project.
page, err := client.Assets.List(ctx, chief.WithLimit(10))
if err != nil {
	log.Fatal(err)
}
for _, a := range page.Data {
	fmt.Printf("%s\t%s\t%s\n", a.AssetID, a.Status, a.Filename)
}
```

## Actions

Actions run a prompt on a schedule or in response to events, optionally emailing
the result.

```go theme={null}
// An action that runs every day at 09:00 and emails its result.
action, err := client.Actions.Create(ctx, &chief.ActionRequest{
	Name:   "daily-digest",
	Prompt: "Summarize assets added to the project in 5 bullet points",
	Schedule: &chief.ScheduleRequest{
		Hour:     "9",
		Weekday:  "*",
		MonthDay: "*",
		Timezone: "America/Sao_Paulo",
	},
	Email: &chief.EmailOutcome{
		To:                   []string{"team@example.com"},
		Subject:              "Daily digest",
		IncludeDateInSubject: true,
	},
})
if err != nil {
	log.Fatal(err)
}
fmt.Printf("created action %q\n", action.ActionID)

// Pause it, then resume it.
_, _ = client.Actions.Disable(ctx, action.ActionID)
_, _ = client.Actions.Enable(ctx, action.ActionID)
```

## Pagination

List calls are cursor-paginated. Size a page with `WithLimit`, then feed the
page's `LastID` to `WithAfterID` to fetch the next one until `HasMore` is false:

```go theme={null}
var after string
for {
	opts := []chief.ListOption{chief.WithLimit(50)}
	if after != "" {
		opts = append(opts, chief.WithAfterID(after))
	}

	page, err := client.Assets.List(ctx, opts...)
	if err != nil {
		log.Fatal(err)
	}
	for _, a := range page.Data {
		fmt.Println(a.AssetID)
	}

	if !page.HasMore {
		break
	}
	after = page.LastID
}
```

`WithBeforeID` pages backward instead. The two cursors are mutually exclusive.

## Errors

Every non-2xx response is returned as an `*APIError` carrying the HTTP status, a
stable machine-readable `Code`, and a user-facing `Humane` message. Helpers
cover the common cases:

```go theme={null}
session, err := client.Sessions.Get(ctx, "missing-id")
if err != nil {
	switch {
	case chief.IsNotFound(err):
		// 404
	default:
		if apiErr, ok := chief.IsAPIError(err); ok {
			log.Printf("chief: %s (%s)", apiErr.Humane, apiErr.Code)
		}
	}
}
```

## Debugging

Pass `WithDebug(true)` when building the client to dump every HTTP request and
response to the standard logger:

```go theme={null}
client, err := chief.New(
	chief.WithAPIKey(os.Getenv("CHIEF_API_KEY")),
	chief.WithProjectID(os.Getenv("CHIEF_PROJECT_ID")),
	chief.WithDebug(true),
)
```

## Versioning

The package follows [semantic versioning](https://semver.org). Pin a version in
your `go.mod` and review the [release notes](https://github.com/Storytell-ai/chief-go/releases)
before upgrading across a major version:

```bash theme={null}
go get github.com/Storytell-ai/chief-go/chief@latest
```

## Reference

* **Package documentation** — [pkg.go.dev/github.com/Storytell-ai/chief-go/chief](https://pkg.go.dev/github.com/Storytell-ai/chief-go/chief)
* **Source and releases** — [github.com/Storytell-ai/chief-go](https://github.com/Storytell-ai/chief-go)
