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

# Install Go

> Install the Go SDK, initialize the client, and make your first call to the Octavia AI CMS API.

Go 1.21+, a single module with zero dependencies outside the standard library.

Every method returns `CMSResponse[T]`, with `T` the model that endpoint sends — so
`GetById` hands back `CMSResponse[ArticleWrapper]` and `res.Data.Article.Slug` is a
typed field read, not a map lookup.

<Note>
  The resources are generated from the OpenAPI spec, so they cover every public
  operation, including AI conversation and tags.
</Note>

## Install

```bash theme={null}
go get github.com/octaviatech/octavia-ai-cms/tree/main/packages/sdk-go/sdk
```

## Initialize

```go theme={null}
package main

import (
	"log"
	"os"
	"time"

	"https://github.com/octaviatech/octavia-ai-cms/tree/main/packages/sdk-go/sdk"
)

func main() {
	cms, err := sdk.InitCMS(os.Getenv("OCTAVIA_API_KEY"), &sdk.CMSOptions{
		Timeout: 30 * time.Second,
	})
	if err != nil {
		log.Fatal(err)
	}
	_ = cms
}
```

`InitCMS` returns an error, so always check it. The key is sent as the `x-api-key`
header on every request; there is no header option, by design.

For more control, build a `Client` directly:

```go theme={null}
client, err := sdk.NewClient(sdk.ClientConfig{
	BaseURL: sdk.CMSBaseURL,
	ApiKey:  key,
	Timeout: 30 * time.Second,
})
```

`Timeout` defaults to 30 seconds when left zero.

## Make a request

Filters go in as a `map[string]any`, as the last argument:

```go theme={null}
res := cms.Article.GetAll(map[string]any{"page": 1, "limit": 10})

if res.Ok {
	for _, item := range res.Data.ArticleListItem {
		fmt.Println(item.Slug)
	}
	fmt.Println(res.Meta)
} else {
	fmt.Println(res.Error.Message)
}
```

Single-resource and search operations return their own model. Every method takes
the query map as its last argument, so pass `nil` when there is nothing to filter on:

```go theme={null}
one    := cms.Article.GetById("6810f2c3a1b2c3d4e5f60718", nil)
bySlug := cms.Article.GetBySlug("hello-world", nil)
hits   := cms.Article.Search(map[string]any{"keyword": "typescript"})

fmt.Println(one.Data.Article.Slug)
```

<Note>
  A single entity arrives under a key of its own, so it is `res.Data.Article`,
  not `res.Data`. Endpoints that answer with a free-form payload — every delete,
  the archive — use `any` for `Data`.
</Note>

## Write operations

```go theme={null}
created := cms.Article.Create(map[string]any{
	// The title and body are keyed by language code.
	"mainTitle": map[string]string{"en": "Hello world"},
	"content":   map[string]string{"en": "<p>...</p>"},
	// category is a list of category IDs.
	"category": []string{"6810f2c3a1b2c3d4e5f60712"},
	"author":   "6810f2c3a1b2c3d4e5f60719",
}, nil)

id := created.Data.Article.ID

cms.Article.Update(map[string]any{
	"id":        id,
	"mainTitle": map[string]string{"en": "Updated"},
}, nil)
cms.Article.DeleteId(id, nil)
```

Only `mainTitle`, `content` and `category` are required to create; `author`
defaults to the authenticated user. `Update` takes the whole document in its
body, so `id` is a field of that body rather than a separate argument.

## Error handling

`res.Ok` reports the API's own verdict. The `Error` field carries the status and
message the server sent:

```go theme={null}
res := cms.Article.GetById("does-not-exist", nil)

if !res.Ok {
	fmt.Println(res.Error.StatusCode, res.Error.Message)
}
```

`ThrowOnError` does something different from the other SDKs — it **panics**, and the
panic value is a plain string, not a typed error:

```go theme={null}
cms, _ := sdk.InitCMS(key, &sdk.CMSOptions{ThrowOnError: true})

defer func() {
	if r := recover(); r != nil {
		log.Printf("request failed: %v", r)
	}
}()
```

<Warning>
  Prefer checking `res.Ok`. Enabling `ThrowOnError` costs you the typed error —
  the status code and payload are discarded in the panic.
</Warning>

## Escaping the resource wrapper

`cms.Raw` is the underlying client, for an endpoint the resources do not cover.
`Request` is untyped; `RequestInto` decodes into a model you name:

```go theme={null}
res := cms.Raw.Request("GET", "/articles/advanceSearch", map[string]any{"keyword": "typescript", "limit": 5}, nil)

typed := sdk.RequestInto[sdk.ArticleList](cms.Raw, "GET", "/articles/advanceSearch", map[string]any{"keyword": "typescript", "limit": 5}, nil)
fmt.Println(typed.Data.ArticleListItem)
```

`RequestInto` is a package function rather than a method because Go does not allow
a method to introduce its own type parameters.

## Resources

`Article`, `Author`, `Category`, `Subcategory`, `Form`, `FormSubmission`, `Language`,
`Tag`, `Report`, `AI`, `AIConversation`, `Raw`.

<Card title="All operations" icon="list" href="/api-reference/ai-cms/articles/get-all" arrow="true">
  Browse the API reference for the full endpoint list.
</Card>
