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

# JavaScript SDK

> Node.js and browser client for the Octavia AI CMS API.

TypeScript-first, ESM-only, with no runtime dependencies. Works in Node.js 18+ and in
any browser that supports `fetch`.

<Note>
  This is the most complete SDK: it is generated from the OpenAPI spec, so it
  covers every public operation, including AI conversation, meta and tags.
</Note>

## Install

```bash theme={null}
npm install @octaviatech/cms
```

## Initialize

```ts theme={null}
import { CMS } from "@octaviatech/cms";

const cms = CMS.init("sk_live_...", {
  baseUrl: "https://api.octaviatech.app/cms",
  timeoutMs: 30_000,
  throwOnError: false,
});
```

`CMS.init` takes only the API key and options — the key is sent as the `x-api-key`
header on every request. There is no header hook, by design.

## Make a request

Read operations that take filters take a single options object:

```ts theme={null}
const res = await cms.article.getAll({ page: 1, limit: 10, categoryId: "..." });

if (res.ok) {
  console.log(res.data); // the envelope's `data`
  console.log(res.meta); // pagination metadata
} else {
  console.error(res.error.status, res.error.message);
}
```

Single-resource and search operations use the same shape:

```ts theme={null}
const one = await cms.article.getById("6810f2c3a1b2c3d4e5f60718");
const hits = await cms.article.search({ query: "typescript", limit: 5 });
const bySlug = await cms.article.getBySlug("hello-world");
```

## Write operations

```ts theme={null}
const created = await cms.article.create({
  title: "Hello world",
  body: "...",
  categoryId: "6810f2c3a1b2c3d4e5f60712",
  authorId: "6810f2c3a1b2c3d4e5f60719",
});

const updated = await cms.article.update(created.data.id, { title: "Updated" });
await cms.article.delete(created.data.id);
```

## Error handling

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

```ts theme={null}
const res = await cms.article.getById("does-not-exist");

if (!res.ok) {
  console.log(res.error.status); // 404
  console.log(res.error.message); // human readable
  console.log(res.error.payload); // full API body
  console.log(res.error.headers); // response headers, e.g. retry-after
}
```

To throw a typed `ApiError` instead:

```ts theme={null}
import { CMS, ApiError } from "@octaviatech/cms";

const cms = CMS.init(key, { throwOnError: true });

try {
  await cms.article.getById("does-not-exist");
} catch (err) {
  if (err instanceof ApiError) {
    console.log(err.status, err.payload);
  }
}
```

<Note>
  In an async call, `throwOnError` rejects the returned promise rather than
  throwing synchronously — wrap the call in `try`/`catch` or `.catch()`.
</Note>

## Timeouts and cancellation

```ts theme={null}
const cms = CMS.init(key, { timeoutMs: 5_000 });
```

For a per-request deadline, pass an `AbortSignal` as the second argument:

```ts theme={null}
const ac = new AbortController();
setTimeout(() => ac.abort(), 3_000);

const res = await cms.article.getAll({}, { signal: ac.signal });
```

## Escaping the resource wrapper

`cms.raw` is the underlying HTTP client — use it for an operation that has not been
added to a resource yet, or to send a request shape the wrapper does not cover:

```ts theme={null}
const res = await cms.raw.request("GET", "/articles/advancedSearch", {
  query: { body: { minWords: 500 } },
});
```

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