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

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

TypeScript-first, 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("0x-OCT...", {
  timeoutMs: 30_000,
  throwOnError: false,
});
```

The base URL is fixed to the production endpoint, so there is no `baseUrl` option. The
key is sent as the `x-api-key` header on every request. There is no header hook, by
design.

## Make a request

Every method takes a path, then an options object with `query`, `body` and `signal`.
Filters go in under `query`:

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

if (res.ok) {
  console.log(res.data); // the envelope's `data`
  console.log(res.meta); // pagination metadata
} else {
  console.error(res.error?.statusCode, 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: { keyword: "typescript", limit: 5 } });
const bySlug = await cms.article.getBySlug("hello-world");

console.log(one.data.article.slug);
```

`data` is typed from the spec, so a field that can be a string or a map is a union you
narrow before use — a field keyed by language code is a record:

```ts theme={null}
console.log(one.data.article.mainTitle.en);
```

## Write operations

```ts theme={null}
const created = await cms.article.create({
  // The title and body are keyed by language code.
  mainTitle: { en: "Hello world" },
  content: { en: "<p>...</p>" },
  // category is a list of category IDs.
  category: ["6810f2c3a1b2c3d4e5f60712"],
  author: "6810f2c3a1b2c3d4e5f60719",
});

await cms.article.update({
  id: created.data.article._id,
  mainTitle: { en: "Updated" },
});
await cms.article.deleteId(created.data.article._id);
```

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

By default an error comes back as a result, not an exception:

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

if (!res.ok) {
  console.log(res.error?.statusCode); // 404
  console.log(res.error?.message);    // human readable
}
```

Opt in to throwing a typed `ApiError` to get the unparsed body and the response headers,
which is where `retry-after` lives:

```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, err.headers.get("retry-after"));
  }
}
```

<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` in the same options object:

```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. It takes
the same options object:

```ts theme={null}
const res = await cms.raw.request("GET", "/articles/advanceSearch", {
  query: { keyword: "typescript", limit: 5 },
});
```

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