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

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

PHP 8.1+, distributed as a Composer package. Uses cURL directly, so there is no PSR-18
client to configure and no HTTP client dependency.

## Install

```bash theme={null}
composer require octavia/cms
```

## Initialize

```php theme={null}
<?php

use Octavia\CmsSDK\CMS;

$cms = CMS::init(getenv('OCTAVIA_API_KEY'), [
    'timeoutMs'    => 30_000,
    'throwOnError' => false,
]);
```

The key is sent as the `x-api-key` header on every request. There is no header option,
by design.

## Make a request

Every method returns an `Envelope` with `success`, `statusCode`, `message` and `data`.
Filters go in as the second argument:

```php theme={null}
$res = $cms->article->getAll(['page' => 1, 'limit' => 10, 'category' => '...']);

if ($res->success) {
    print_r($res->data);
} else {
    echo $res->message;
}
```

`data` is the endpoint's generated model, so fields are properties rather than array
keys. A single entity arrives under a key of its own:

```php theme={null}
$one    = $cms->article->getById('6810f2c3a1b2c3d4e5f60718');
$bySlug = $cms->article->getBySlug('hello-world');
$hits   = $cms->article->search(['keyword' => 'typescript', 'limit' => 5]);

echo $one->data->article->slug;
```

A field typed as a map of language codes is a `MultilingualString`, readable by array
access or as a property:

```php theme={null}
echo $one->data->article->mainTitle['en'];
echo $one->data->article->mainTitle->en;   // the same value
```

<Note>
  The runtime return type is the shared `Envelope`; the payload model is named in the
  `@phpstan-return` annotation on each method, so static analysis still knows what
  `data` holds.
</Note>

## Write operations

```php theme={null}
$created = $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',
]);

$id = $created->data->article->_id;

$cms->article->update([
    'id'        => $id,
    'mainTitle' => ['en' => 'Updated'],
]);
$cms->article->deleteId($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. The delete method is named `deleteId`
because `delete` is a reserved word in PHP.

## Error handling

Errors are return values, not exceptions, unless you opt in. A failed call still comes
back as an `Envelope` with `success` false:

```php theme={null}
$res = $cms->article->getById('does-not-exist');

if (!$res->success) {
    echo $res->statusCode;   // 404
    echo $res->message;      // human readable
}
```

To throw an `ApiError` instead, which carries the unparsed body:

```php theme={null}
use Octavia\CmsSDK\ApiError;

$cms = CMS::init($key, ['throwOnError' => true]);

try {
    $cms->article->getById('does-not-exist');
} catch (ApiError $err) {
    echo $err->status;
    print_r($err->payload);
}
```

<Warning>
  `throwOnError` throws on any non-2xx response. Leaving it off and checking
  `$res->success` is the safer default for batch work.
</Warning>

## Timeouts

```php theme={null}
$cms = CMS::init($key, ['timeoutMs' => 5000]);
```

<Warning>
  `timeoutMs` defaults to `0`, which means **no timeout** — a request will hang until
  the server or PHP's `max_execution_time` stops it. Set it explicitly in production.
</Warning>

## Escaping the resource wrapper

`$cms->raw` is the underlying client, for calling endpoints the resources do not cover.
Its `request` method takes the method, the path, and an options array; it sends only
`x-api-key`, so there is no header to set by hand:

```php theme={null}
$res = $cms->raw->request('GET', '/articles/advanceSearch', ['query' => ['keyword' => 'typescript']]);
```

<Note>
  Method names follow the route segments as generated, including the ones that read as
  verbs and methods together — `idReactionPOST`, `commentsGetAll`. The API reference
  lists every operation under its route.
</Note>

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