> ## 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.

# C# SDK

> .NET client for the Octavia AI CMS API.

Targets .NET 6+. The only SDK that lets you supply your own `HttpClient`, so it fits
into an existing DI container, Polly pipeline, or test harness without extra work.

## Install

```bash theme={null}
dotnet add package Octavia.CmsSdk
```

## Initialize

```csharp theme={null}
using Octavia.Cms;

var cms = CMS.Init(
    Environment.GetEnvironmentVariable("OCTAVIA_API_KEY")!,
    new CMSOptions
    {
        BaseUrl      = "https://api.octaviatech.app/cms",
        Timeout      = TimeSpan.FromSeconds(30),
        ThrowOnError = false,
    });
```

`CMSOptions` is init-only, so it can only be set at construction. The key is sent as
the `x-api-key` header on every request; there is no header option, by design.

## Inject your own HttpClient

```csharp theme={null}
var http = new HttpClient(new RetryHandler());   // any DelegatingHandler
var client = new Client(new ClientConfig { BaseUrl = CMS.BaseUrl, ApiKey = key }, http);
```

Everything else works the same, and a handler gives you retries, logging, and
correlation IDs that the SDK itself does not provide.

## Make a request

Read operations that take filters take a single dictionary:

```csharp theme={null}
var res = await cms.Article.GetAllAsync(new Dictionary<string, object>
{
    ["page"] = 1,
    ["limit"] = 10,
    ["categoryId"] = "...",
});

if (res.Ok)
{
    Console.WriteLine(res.Data);
    Console.WriteLine(res.Meta);
}
else
{
    Console.Error.WriteLine(res.Error?.Message);
}
```

Single-resource and search operations use the same shape:

```csharp theme={null}
var one    = await cms.Article.GetByIdAsync("6810f2c3a1b2c3d4e5f60718");
var hits   = await cms.Article.SearchAsync(new Dictionary<string, object> { ["query"] = "typescript" });
var bySlug = await cms.Article.GetBySlugAsync("hello-world");
```

<Note>
  `res.Data` is a `JsonElement`, not a POCO. Call `.GetProperty("title").GetString()`
  to read a field, or `res.Data.Deserialize<T>()` to bind a whole object.
</Note>

## Write operations

```csharp theme={null}
var created = await cms.Article.CreateAsync(new Dictionary<string, object>
{
    ["title"] = "Hello world",
    ["body"] = "...",
    ["categoryId"] = "6810f2c3a1b2c3d4e5f60712",
    ["authorId"] = "6810f2c3a1b2c3d4e5f60719",
});

await cms.Article.UpdateAsync(created.Data.GetProperty("id").GetString()!, new Dictionary<string, object>
{
    ["title"] = "Updated",
});
await cms.Article.DeleteAsync("6810f2c3a1b2c3d4e5f60718");
```

## Error handling

Errors are return values, not exceptions, unless you opt in:

```csharp theme={null}
var res = await cms.Article.GetByIdAsync("does-not-exist");

if (!res.Ok)
{
    Console.WriteLine(res.Error?.Status);   // 404
    Console.WriteLine(res.Error?.Message);  // human readable
    Console.WriteLine(res.Error?.Payload);  // raw response body, unparsed
}
```

To throw an `ApiError` instead:

```csharp theme={null}
var cms = CMS.Init(key, new CMSOptions { ThrowOnError = true });

try
{
    await cms.Article.GetByIdAsync("does-not-exist");
}
catch (ApiError err)
{
    Console.WriteLine(err.Status);
    Console.WriteLine(err.Payload);
}
```

## Cancellation

```csharp theme={null}
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var res = await cms.Article.GetAllAsync(new Dictionary<string, object>(), cts.Token);
```

## Escaping the resource wrapper

`cms.Raw` is the underlying HTTP client, for calling endpoints the resources do not
cover. It sends only `x-api-key`; the tenant and service state are derived by the
gateway, so there is no header to set by hand:

```csharp theme={null}
var client = new Client(new ClientConfig(CMSConstants.BaseUrl, key));
var res = await client.RequestAsync("GET", "/articles/advanceSearch");
```

## 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>
