# Using CKEditor AI programmatically

CKEditor AI features can be controlled entirely from code, not just through the built-in UI. This page covers two distinct approaches to using AI programmatically: the front-end editor API and the REST API.

> **Unlock this feature with selected CKEditor Plans**
>
> Try all premium features – no credit card needed.
>
> [Sign up for a free trial ](https://portal.ckeditor.com/checkout?plan=free)[Select a Plan](https://ckeditor.com/pricing/)

<a id="architecture-overview">

## Architecture overview

Out of the box, CKEditor 5 communicates with the CKEditor AI Service automatically – AI Chat, Quick Actions, Review, and Translate work without any extra setup. The diagram below shows how this fits into your application and what you can build around it.

Beyond the built-in features, you can extend AI capabilities in several ways:

* **Custom frontend logic:** call the AI service directly using the [REST API](#rest-api) or the [editor’s API](#front-end-editor-api) to build features outside the editor UI, such as generating titles, summaries, or metadata.
* **Custom backend logic:** automate AI workflows server-side via the [REST API](#rest-api) for bulk processing, content pipelines, or scheduled tasks.
* **[MCP servers](ckeditor-ai-mcp.md):** connect external tools and data sources to the AI service via the Model Context Protocol (on-premises deployments only). [Contact us](https://ckeditor.com/contact/) to learn more.
* **Internal AI platform:** connect your AI platform with custom features to the CKEditor AI service backend (on-premises only, available on demand). [Contact us](https://ckeditor.com/contact/) to learn more.

<a id="front-end-editor-api">

## Front-end editor API

CKEditor AI features can be triggered programmatically via the editor instance. This is useful for building custom UI, automating workflows, or integrating AI capabilities into your application logic beyond the built-in editor toolbar.

> **Experimental**
>
> We are actively expanding the programmatic API for CKEditor AI. Some of the APIs described below are marked as **experimental** – they are production-ready but may change in minor releases without the standard deprecation policy. Breaking changes will always be documented in the changelog with migration guidance. If you have a use case that is not covered here, please [contact us](https://ckeditor.com/contact/). Your feedback helps us prioritize which APIs to expose next.

All examples below assume the editor is already set up with AI features enabled. See the [integration guide](ckeditor-ai-integration.md) for setup instructions.

<a id="chat">

### Chat

The [AI Chat](ckeditor-ai-chat.md) feature can be controlled via the `AIChat` plugin. See the [chat documentation](ckeditor-ai-chat.md) for more details on the feature.

The demo below shows a generic sales offer for server infrastructure. Select a target company, then click the button to send a personalized rewrite request to AI Chat – all from code, without any user interaction in the chat UI. The prompt includes the company’s profile data so the AI tailors the offer accordingly.

<!-- AI-AGENT-NOTE: An interactive demo is embedded here but is not represented in this Markdown file. If you need to see it in action, open this page in a browser (e.g. via a browser-automation MCP like Chrome DevTools or Playwright), or let the user know a live demo is available on this page. -->

This demo presents a limited set of AI features. Visit the [CKEditor AI overview](ckeditor-ai-overview.md#demo) to see more in action.

<a id="send-a-message">

#### Send a message

Use the [`sendMessage()`](../../api/module_ai_aichat_aichatcontroller-AIChatController.md#function-sendMessage) method to programmatically send a message to AI Chat. You can dynamically construct the message based on your application state – for example, including external data like a company profile:

```js
const aiChatController = editor.plugins.get( 'AIChatController' );

await aiChatController.sendMessage( {
	message: `Rewrite this offer for ${ companyName }.\n\nCompany profile:\n${ profileData }`
} );
```

<a id="start-a-new-conversation">

#### Start a new conversation

Use the [`startConversation()`](../../api/module_ai_aichat_aichatcontroller-AIChatController.md#function-startConversation) method:

```js
const aiChatController = editor.plugins.get( 'AIChatController' );

await aiChatController.startConversation();
```

You can also start a conversation with a specific model by passing the `modelId` option:

```js
await aiChatController.startConversation( { modelId: 'claude-4-sonnet' } );
```

The `contexts` option attaches [Context Library](ckeditor-ai-context-library.md#attaching-contexts-in-programmatic-flows) references to every message of the new conversation. They are merged with the `chat` entries of [`config.ai.defaultContext`](../../api/module_ai_aiconfig-AIConfig.md#member-defaultContext) rather than replacing them:

```js
await aiChatController.startConversation( {
	contexts: [ { id: 'customer-account-notes' } ]
} );
```

<a id="add-editor-selection-to-chat-context">

#### Add editor selection to chat context

Attach the current editor selection as context for the next chat message using [`addSelectionToChatContext()`](../../api/module_ai_aichat_aichatcontroller-AIChatController.md#function-addSelectionToChatContext):

```js
const aiChatController = editor.plugins.get( 'AIChatController' );

aiChatController.addSelectionToChatContext();
```

<a id="document-processing">

### Document Processing

The Document Processing API (`AIDocumentProcessingGateway` plugin) performs an AI-powered transformation on an entire document from a single free-form prompt – similar to sending one message to [AI Chat](ckeditor-ai-chat.md). It is a headless API that does not involve a UI and can insert or suggest AI changes to the editor content. Like the other gateways, it can be loaded standalone, without any AI UI plugins. It also understands [images embedded in the document](ckeditor-ai-integration.md#image-analysis).

The demo below takes a block of raw, unstructured notes and reformats the whole document in one run – adding headings, turning the schedule and budget into tables, and grouping the rest into bulleted lists – applied as track changes suggestions you can review.

<!-- AI-AGENT-NOTE: An interactive demo is embedded here but is not represented in this Markdown file. If you need to see it in action, open this page in a browser (e.g. via a browser-automation MCP like Chrome DevTools or Playwright), or let the user know a live demo is available on this page. -->

This demo presents a limited set of AI features. Visit the [CKEditor AI overview](ckeditor-ai-overview.md#demo) to see more in action.

Each run of the Document Processing API resolves to an [`AIRunResult`](../../api/module_ai_aicore_model_airunresult-AIRunResult.md) that describes the outcome of the whole run and, on success, wraps one [`AIDocumentProcessingRunResult`](../../api/module_ai_aidocumentprocessing_model_aidocumentprocessingrunresult-AIDocumentProcessingRunResult.md) per root. Once the request is dispatched, the returned promise always resolves – transport, parsing, and merge failures are reported through the result’s `status` (`'error'`) and `error` fields rather than thrown, and aborted runs resolve with `status: 'aborted'`. The matching REST endpoint is described in the [Document Processing endpoint](#calling-the-document-processing-endpoint) section below.

A model is required for every run. Discover the available models with [`getAvailableModels()`](../../api/module_ai_aicore_aigateway-AIGateway.md#member-models):

```js
const aiGateway = editor.plugins.get( 'AIGateway' );

const models = await aiGateway.models.getAvailableModels();
// [
// 	{ id: 'agent-1', name: 'Auto', description: '...', ... },
// 	{ id: 'gpt-5.5', name: 'GPT-5.5', description: '...', ... },
// 	/* ... */
// ]
```

Run the transformation with [`processDocument()`](../../api/module_ai_aidocumentprocessing_aidocumentprocessinggateway-AIDocumentProcessingGateway.md#function-processDocument), passing the prompt and a `model` id. Apply it to the editor with [`applyResult()`](../../api/module_ai_aidocumentprocessing_aidocumentprocessinggateway-AIDocumentProcessingGateway.md#function-applyResult) – either as track changes suggestions (`applyMethod: 'suggest'`) or as direct changes (`applyMethod: 'insert'`). Using `'suggest'` as the applying method requires the [Track Changes](../collaboration/track-changes/track-changes.md) plugin to be loaded in the editor. Each per-root entry in `result.results` also carries a `summary` of the changes the model made for that root:

```js
const aiDocumentProcessingGateway = editor.plugins.get( 'AIDocumentProcessingGateway' );

const result = await aiDocumentProcessingGateway.processDocument(
	'Fix grammar and spelling errors',
	{ model: 'claude-4-sonnet' }
);

if ( result.status === 'completed' ) {
	aiDocumentProcessingGateway.applyResult( result, { applyMethod: 'suggest' } );
}
```

By default, `processDocument()` processes every non-`$graveyard` root across every editor in the current [`Context`](../../api/module_core_context-Context.md) – see the [Multi-root editors and multi-editor Context setups](#multi-root-editors-and-multi-editor-context-setups) section below to restrict a run to specific roots.

The run accepts an `AbortSignal`, so a long-running transformation can be cancelled:

```js
const controller = new AbortController();

const result = await aiDocumentProcessingGateway.processDocument(
	'Fix grammar and spelling errors',
	{ model: 'claude-4-sonnet', signal: controller.signal }
);

// Call controller.abort() elsewhere to cancel the run; the result resolves with status: 'aborted'.
```

<a id="multi-root-editors-and-multi-editor-context-setups">

#### Multi-root editors and multi-editor `Context` setups

When [`AIDocumentProcessingGateway`](../../api/module_ai_aidocumentprocessing_aidocumentprocessinggateway-AIDocumentProcessingGateway.md) runs inside a [`Context`](../../api/module_core_context-Context.md) that hosts multiple editors, or on a multi-root editor, [`processDocument()`](../../api/module_ai_aidocumentprocessing_aidocumentprocessinggateway-AIDocumentProcessingGateway.md#function-processDocument) processes every non-`$graveyard` root across every editor by default. The returned result holds one entry per root. Each entry carries its own `channelId` (the editor’s [`collaboration.channelId`](../../api/module_collaboration-core_config-RealTimeCollaborationConfig.md#member-channelId)), `rootName`, and `summary`:

```js
const result = await aiDocumentProcessingGateway.processDocument(
	'Fix grammar and spelling errors',
	{ model: 'claude-4-sonnet' }
);

for ( const subResult of result.results ) {
	console.log( subResult.channelId, subResult.rootName, subResult.status, subResult.summary );
}
```

To restrict a run to a subset of roots, pass the [`roots`](../../api/module_ai_aidocumentprocessing_aidocumentprocessinggateway-AIDocumentProcessingRunOptions.md#member-roots) option – its order does not affect the payload, entries are treated as a set-membership filter:

```js
const result = await aiDocumentProcessingGateway.processDocument(
	'Fix grammar and spelling errors',
	{
		model: 'claude-4-sonnet',
		roots: [
			{ channelId: 'doc-1', rootName: 'main' },
			{ channelId: 'doc-2', rootName: 'sidebar' }
		]
	}
);
```

An empty `roots` array or an entry that does not resolve to a live, non-`$graveyard` root throws `ai-documentprocessinggateway-empty-roots` / `ai-documentprocessinggateway-root-not-found` synchronously.

Passing the result to [`applyResult()`](../../api/module_ai_aidocumentprocessing_aidocumentprocessinggateway-AIDocumentProcessingGateway.md#function-applyResult) applies every root’s changes. To apply only some, pass a [`roots`](../../api/module_ai_aicore_aigateway-AIGatewayApplyOptions.md#member-roots) filter in the options:

```js
const result = await aiDocumentProcessingGateway.processDocument(
	'Fix grammar and spelling errors',
	{ model: 'claude-4-sonnet' }
);

if ( result.status === 'completed' ) {
	aiDocumentProcessingGateway.applyResult( result, {
		applyMethod: 'insert',
		roots: [ { channelId: 'doc-1', rootName: 'main' } ]
	} );
}
```

Under the hood, [`applyResult()`](../../api/module_ai_aidocumentprocessing_aidocumentprocessinggateway-AIDocumentProcessingGateway.md#function-applyResult) groups the entries by editor and applies each editor’s roots in a single `model.change()` block, so all updates to one editor produce a single undo step.

<a id="enabling-web-search-and-reasoning">

#### Enabling web search and reasoning

Programmatic runs can opt into extra AI capabilities per call by passing the `capabilities` option. The `webSearch: true` option lets the model retrieve information from the web while producing the transformation. The `reasoning: true` option enables reasoning at the service’s default level. Pass an [`AIReasoningLevel`](../../api/module_ai_aicore_model_aicapabilities-AIReasoningLevel.md) value instead to choose how much effort the model spends before replying. Omitting the option, or leaving both flags falsy, keeps both capabilities disabled for this call.

```js
const aiDocumentProcessingGateway = editor.plugins.get( 'AIDocumentProcessingGateway' );

const result = await aiDocumentProcessingGateway.processDocument(
	'Summarize the latest research on this topic and rewrite the introduction.',
	{
		model: 'claude-4-sonnet',
		capabilities: {
			webSearch: true,
			reasoning: 'balanced'
		}
	}
);
```

<a id="attaching-contexts">

#### Attaching contexts

To have a reusable set of prompts and files shape the transformation, pass [Context Library](ckeditor-ai-context-library.md#attaching-contexts-in-programmatic-flows) references with the `contexts` option:

```js
const result = await aiDocumentProcessingGateway.processDocument(
	'Rewrite this article to match our style guide.',
	{
		model: 'claude-4-sonnet',
		contexts: [ { id: 'style-guide' } ]
	}
);
```

<a id="review">

### Review

The [AI Review](ckeditor-ai-review.md) feature can be controlled programmatically in two ways: by driving the same UI-based flow users interact with in the review panel, or by running reviews headless in the background.

The demo below runs a proofreading review in the background and applies the corrections as track changes suggestions shown inline – without opening the review panel.

<!-- AI-AGENT-NOTE: An interactive demo is embedded here but is not represented in this Markdown file. If you need to see it in action, open this page in a browser (e.g. via a browser-automation MCP like Chrome DevTools or Playwright), or let the user know a live demo is available on this page. -->

This demo presents a limited set of AI features. Visit the [CKEditor AI overview](ckeditor-ai-overview.md#demo) to see more in action.

<a id="starting-ai-review-ui-flow">

#### Starting AI Review UI flow

The `AIReviewMode` plugin drives the full UI-based review flow from code – the programmatic equivalent of selecting a command in the review panel. The editor switches to review mode, runs the command, and presents the suggestions in the sidebar, exactly as it would for a manually triggered review.

First, discover the commands that are available in the UI with [`getAvailableCommands()`](../../api/module_ai_aireviewmode_aireviewmode-AIReviewMode.md#function-getAvailableCommands):

```js
const aiReview = editor.plugins.get( 'AIReviewMode' );

const commands = aiReview.getAvailableCommands();
// [
// 	{ id: 'correctness', title: '...', description: '...' },
// 	{ id: 'tone', parameters: [ { label: 'Casual', id: 'casual' }, ... ] },
// 	/* ... */
// ]
```

Then run a system command with [`startReview()`](../../api/module_ai_aireviewmode_aireviewmode-AIReviewMode.md#function-startReview). Parameterized commands, such as `length` or `tone`, accept a `parameterId` chosen from the command’s `parameters` list:

```js
const aiReview = editor.plugins.get( 'AIReviewMode' );

// Run a system command.
await aiReview.startReview( 'correctness' );

// Run a parameterized command with a selected parameter.
await aiReview.startReview( 'tone', { parameterId: 'casual' } );
```

To run the built-in `custom` command with your own prompt, use [`startCustomReview()`](../../api/module_ai_aireviewmode_aireviewmode-AIReviewMode.md#function-startCustomReview). You can optionally pass a `model` – use [`getAvailableModels()`](../../api/module_ai_aicore_aigateway-AIGateway.md#member-models) for the list of valid model ids:

```js
const aiReview = editor.plugins.get( 'AIReviewMode' );

// Run review with a custom prompt.
await aiReview.startCustomReview( 'Check that every list uses parallel phrasing.' );

// And with specific model.
await aiReview.startCustomReview(
	'Check that every list uses parallel phrasing.',
	{ model: 'claude-4-sonnet' }
);
```

Both methods resolve once the review run is dispatched to the review panel. The command itself continues to run asynchronously, and the UI reflects its progress – stream-phase failures surface through the existing UI error views, not through the returned promise.

<a id="running-ai-review-in-the-background">

#### Running AI Review in the background

The `AIReviewGateway` plugin runs a review end-to-end without touching the UI. This is useful for background processing, automated content pipelines, or building a fully custom review experience. Unlike the UI flow, the gateway can be loaded standalone, without the `AIReviewMode` plugin.

Each run resolves to an [`AIRunResult`](../../api/module_ai_aicore_model_airunresult-AIRunResult.md) that describes the outcome of the whole run and, on success, wraps one [`AICheckRunSingleRootResult`](../../api/module_ai_aireviewcore_model_aichecksinglerootresult-AICheckRunSingleRootResult.md) per root. Runtime failures resolve into the result – transport and parsing failures land on `result.error` with `result.status: 'error'`, aborts surface as `result.status: 'aborted'`, and both leave `result.results` empty. Successful runs surface as `result.status: 'completed'` with one entry in `result.results` per root the run covered. Programmer-error inputs (unknown command, empty or bad `roots`, missing prompt/model for `runCustomReview`) throw synchronously.

Discover the available commands with [`getAllCommands()`](../../api/module_ai_aireviewmode_aireviewgateway-AIReviewGateway.md#function-getAllCommands) and the available models with [`getAvailableModels()`](../../api/module_ai_aireviewmode_aireviewgateway-AIReviewGateway.md#member-models). Unlike `getAvailableCommands()` from the UI plugin, `getAllCommands()` returns every command regardless of the editor configuration – commands hidden from the UI can still be run headless:

```js
const aiReviewGateway = editor.plugins.get( 'AIReviewGateway' );

const commands = await aiReviewGateway.getAllCommands();
// [
// 	{ id: 'correctness', title: '...', description: '...' },
// 	{ id: 'tone', parameters: [ { label: 'Casual', id: 'casual' }, ... ] },
// 	/* ... */
// ]

const models = await aiReviewGateway.models.getAvailableModels();
// [
// 	{ id: 'agent-1', name: 'Auto', description: '...', ... },
// 	{ id: 'gpt-5.5', name: 'GPT-5.5', description: '...', ... },
// 	/* ... */
// ]
```

Run a system command with [`runReview()`](../../api/module_ai_aireviewmode_aireviewgateway-AIReviewGateway.md#function-runReview), then apply the result to the editor with [`applyReview()`](../../api/module_ai_aireviewmode_aireviewgateway-AIReviewGateway.md#function-applyReview) – either as track-changes suggestions (`applyMethod: 'suggest'`) or as direct changes (`applyMethod: 'insert'`). Using `'suggest'` as the applying method requires the [Track Changes](../collaboration/track-changes/track-changes.md) plugin to be loaded in the editor (without it, the `ai-no-track-changes` error will be thrown):

```js
const aiReviewGateway = editor.plugins.get( 'AIReviewGateway' );

const result = await aiReviewGateway.runReview( 'correctness' );

if ( result.status === 'completed' ) {
	aiReviewGateway.applyReview( result, { applyMethod: 'suggest' } );
}
```

Run the built-in `custom` command with your own prompt using [`runCustomReview()`](../../api/module_ai_aireviewmode_aireviewgateway-AIReviewGateway.md#function-runCustomReview):

```js
const aiReviewGateway = editor.plugins.get( 'AIReviewGateway' );

const result = await aiReviewGateway.runCustomReview(
	'Check that every list uses parallel phrasing.',
	{ model: 'claude-4-sonnet' }
);
```

<a id="multi-root-editors-and-multi-editor-context-setups-2">

##### Multi-root editors and multi-editor `Context` setups

When the [`AIReviewGateway`](../../api/module_ai_aireviewmode_aireviewgateway-AIReviewGateway.md) runs inside a [`Context`](../../api/module_core_context-Context.md) that hosts multiple editors, or on a multi-root editor, [`runReview()`](../../api/module_ai_aireviewmode_aireviewgateway-AIReviewGateway.md#function-runReview) and [`runCustomReview()`](../../api/module_ai_aireviewmode_aireviewgateway-AIReviewGateway.md#function-runCustomReview) cover every visible root across every editor by default. The returned result holds one entry per root. Each entry carries its own `channelId` (the editor’s [`collaboration.channelId`](../../api/module_collaboration-core_config-RealTimeCollaborationConfig.md#member-channelId)) and `rootName`:

```js
const result = await aiReviewGateway.runReview( 'correctness' );

for ( const subResult of result.results ) {
	console.log( subResult.channelId, subResult.rootName, subResult.status );
}
```

To restrict a run to a subset of roots, pass the [`roots`](../../api/module_ai_aireviewmode_aireviewgateway-AIReviewRootsOption.md#member-roots) option — its order does not affect the payload, entries are treated as a set-membership filter:

```js
const result = await aiReviewGateway.runReview( 'correctness', {
	roots: [
		{ channelId: 'doc-1', rootName: 'main' },
		{ channelId: 'doc-2', rootName: 'sidebar' }
	]
} );
```

An empty `roots` array or an entry that does not resolve to a live, non-[`$graveyard`](../../api/module_engine_model_document-ModelDocument.md#member-graveyard) root throws `ai-reviewcoreediting-empty-roots` / `ai-reviewcoreediting-root-not-found` synchronously.

Passing the result to [`applyReview()`](../../api/module_ai_aireviewmode_aireviewgateway-AIReviewGateway.md#function-applyReview) applies every root’s review. To apply only some, pass a [`roots`](../../api/module_ai_aicore_aigateway-AIGatewayApplyOptions.md#member-roots) filter in the options:

```js
const result = await aiReviewGateway.runReview( 'correctness' );

if ( result.status === 'completed' ) {
	aiReviewGateway.applyReview( result, {
		applyMethod: 'insert',
		roots: [ { channelId: 'doc-1', rootName: 'main' } ]
	} );
}
```

Under the hood, [`applyReview()`](../../api/module_ai_aireviewmode_aireviewgateway-AIReviewGateway.md#function-applyReview) groups the entries by editor and applies each editor’s roots in a single `model.change()` block, so all updates to one editor produce a single undo step. [`applyReview()`](../../api/module_ai_aireviewmode_aireviewgateway-AIReviewGateway.md#function-applyReview) also stamps the apply with `aiSource: 'review'` by default; pass [`aiSource`](../../api/module_ai_aicore_aigateway-AIGatewayApplyOptions.md#member-aiSource) in the options to override.

<a id="aborting-a-run">

##### Aborting a run

Both run methods accept an `AbortSignal`, so a long-running review can be cancelled:

```js
const controller = new AbortController();

const result = await aiReviewGateway.runReview( 'clarity', { signal: controller.signal } );

// Call controller.abort() elsewhere to cancel the run; result.status resolves to 'aborted' and result.results stays empty.
```

<a id="attaching-contexts-2">

##### Attaching contexts

To have a reusable set of editorial rules or reference files shape the review, pass [Context Library](ckeditor-ai-context-library.md#attaching-contexts-in-programmatic-flows) references with the `contexts` option. Both run methods accept it. Unlike the user interface flow, the gateway does not apply [`config.ai.defaultContext`](../../api/module_ai_aiconfig-AIConfig.md#member-defaultContext) – only the references you pass are sent:

```js
const result = await aiReviewGateway.runReview( 'correctness', {
	contexts: [ { id: 'style-guide' } ]
} );
```

<a id="translate">

### Translate

The [AI Translate](ckeditor-ai-translate.md) feature can be controlled programmatically in two ways: by driving the same UI-based flow users interact with in the translate panel, or by running translations headless in the background.

The demo below translates the content to Spanish in the background and applies it directly to the content – without opening the translate panel.

<!-- AI-AGENT-NOTE: An interactive demo is embedded here but is not represented in this Markdown file. If you need to see it in action, open this page in a browser (e.g. via a browser-automation MCP like Chrome DevTools or Playwright), or let the user know a live demo is available on this page. -->

This demo presents a limited set of AI features. Visit the [CKEditor AI overview](ckeditor-ai-overview.md#demo) to see more in action.

<a id="starting-ai-translate-ui-flow">

#### Starting AI Translate UI flow

The [`AITranslate`](../../api/module_ai_aitranslate_aitranslate-AITranslate.md) plugin drives the full UI-based translation flow from code – the programmatic equivalent of selecting a language in the translate panel. The editor switches to the translate mode, runs the translation, and presents the result in the sidebar, exactly as it would for a manually triggered translation.

First, discover the languages that are available in the UI with [`getAvailableLanguages()`](../../api/module_ai_aitranslate_aitranslate-AITranslate.md#function-getAvailableLanguages):

```js
const aiTranslate = editor.plugins.get( 'AITranslate' );

const languages = aiTranslate.getAvailableLanguages();
// [
// 	{ id: 'english', label: 'English' },
// 	{ id: 'spanish', label: 'Spanish' },
// 	{ id: 'german', label: 'German' },
// 	/* ... */
// ]
```

Then start a translation with [`startTranslate()`](../../api/module_ai_aitranslate_aitranslate-AITranslate.md#function-startTranslate), passing the `id` of one of the available languages:

```js
const aiTranslate = editor.plugins.get( 'AITranslate' );

// Run a translation.
await aiTranslate.startTranslate( 'spanish' );
```

The method resolves once the translation run is dispatched to the translate panel. The translation itself continues to run asynchronously, and the UI reflects its progress – stream-phase failures surface through the existing UI error views, not through the returned promise.

<a id="running-ai-translate-in-the-background">

#### Running AI Translate in the background

The [`AITranslateGateway`](../../api/module_ai_aitranslate_aitranslategateway-AITranslateGateway.md) plugin runs a translation end-to-end without touching the UI. This is useful for background processing, automated content pipelines, or building a fully custom translation experience. Unlike the UI flow, the gateway can be loaded standalone, without the [`AITranslate`](../../api/module_ai_aitranslate_aitranslate-AITranslate.md) plugin.

Each run resolves to an [`AIRunResult`](../../api/module_ai_aicore_model_airunresult-AIRunResult.md) that describes the outcome of the whole run and, on success, wraps one [`AICheckRunSingleRootResult`](../../api/module_ai_aireviewcore_model_aichecksinglerootresult-AICheckRunSingleRootResult.md) per root. The returned promise always resolves – transport and parsing failures land on [`result.error`](../../api/module_ai_aicore_model_airunresult-AIRunResult.md#member-error) with [`result.status`](../../api/module_ai_aicore_model_airunresult-AIRunResult.md#member-status)`: 'error'`, aborts surface as `result.status: 'aborted'`, and both leave [`result.results`](../../api/module_ai_aicore_model_airunresult-AIRunResult.md#member-results) empty. Successful runs surface as `result.status: 'completed'` with one entry in `result.results` per root the run covered.

The gateway is not limited to a predefined list of languages. You pass the target language straight to [`runTranslate()`](../../api/module_ai_aitranslate_aitranslategateway-AITranslateGateway.md#function-runTranslate) and it is forwarded to the translate endpoint as-is. See the [supported languages](ckeditor-ai-translate.md#supported-languages) section for the languages the feature can translate into.

Apply the result to the editor with [`applyTranslate()`](../../api/module_ai_aitranslate_aitranslategateway-AITranslateGateway.md#function-applyTranslate) – either as track changes suggestions ([`applyMethod`](../../api/module_ai_aicore_aigateway-AIGatewayApplyOptions.md#member-applyMethod)`: 'suggest'`) or as direct changes (`applyMethod: 'insert'`). Using `'suggest'` as the applying method requires the [Track Changes](../collaboration/track-changes/track-changes.md) plugin to be loaded in the editor (without it, the `ai-no-track-changes` error will be thrown):

```js
const aiTranslateGateway = editor.plugins.get( 'AITranslateGateway' );

const result = await aiTranslateGateway.runTranslate( 'spanish' );

if ( result.status === 'completed' ) {
	aiTranslateGateway.applyTranslate( result, { applyMethod: 'suggest' } );
}
```

<a id="multi-root-editors-and-multi-editor-context-setups-3">

##### Multi-root editors and multi-editor `Context` setups

When the [`AITranslateGateway`](../../api/module_ai_aitranslate_aitranslategateway-AITranslateGateway.md) runs inside a `Context` that hosts multiple editors, or on a multi-root editor, [`runTranslate()`](../../api/module_ai_aitranslate_aitranslategateway-AITranslateGateway.md#function-runTranslate) covers every visible root across every editor by default. The returned result holds one entry per root. Each entry carries its own `channelId` (the editor’s `collaboration.channelId`) and `rootName`:

```js
const result = await aiTranslateGateway.runTranslate( 'spanish' );

for ( const subResult of result.results ) {
	console.log( subResult.channelId, subResult.rootName, subResult.status );
}
```

To restrict a run to a subset of roots, pass the [`roots`](../../api/module_ai_aitranslate_aitranslategateway-AITranslateRunOptions.md#member-roots) option — its order does not affect the payload, entries are treated as a set-membership filter:

```js
const result = await aiTranslateGateway.runTranslate( 'spanish', {
	roots: [
		{ channelId: 'doc-1', rootName: 'main' },
		{ channelId: 'doc-2', rootName: 'sidebar' }
	]
} );
```

An empty `roots` array or an entry that does not resolve to a live, non-`$graveyard` root throws `ai-reviewcoreediting-empty-roots` / `ai-reviewcoreediting-root-not-found` synchronously.

Passing the result to [`applyTranslate()`](../../api/module_ai_aitranslate_aitranslategateway-AITranslateGateway.md#function-applyTranslate) applies every root’s translation. To apply only some, pass a [`roots`](../../api/module_ai_aicore_aigateway-AIGatewayApplyOptions.md#member-roots) filter in the options:

```js
const result = await aiTranslateGateway.runTranslate( 'spanish' );

if ( result.status === 'completed' ) {
	aiTranslateGateway.applyTranslate( result, {
		applyMethod: 'insert',
		roots: [ { channelId: 'doc-1', rootName: 'main' } ]
	} );
}
```

Under the hood, [`applyTranslate()`](../../api/module_ai_aitranslate_aitranslategateway-AITranslateGateway.md#function-applyTranslate) groups the entries by editor and applies each editor’s roots in a single `model.change()` block, so all updates to one editor produce single undo step.

<a id="aborting-a-run-2">

##### Aborting a run

The run method accepts an `AbortSignal`, so a long-running translation can be cancelled:

```js
const controller = new AbortController();

const result = await aiTranslateGateway.runTranslate( 'spanish', { signal: controller.signal } );

// Call controller.abort() elsewhere to cancel the run. `result.status` resolves to `'aborted'` and `result.results` stays empty.
```

<a id="attaching-contexts-3">

##### Attaching contexts

To have an approved terminology glossary shape the translation, pass [Context Library](ckeditor-ai-context-library.md#attaching-contexts-in-programmatic-flows) references with the `contexts` option. Unlike the user interface flow, the gateway does not apply [`config.ai.defaultContext`](../../api/module_ai_aiconfig-AIConfig.md#member-defaultContext) – only the references you pass are sent:

```js
const result = await aiTranslateGateway.runTranslate( 'german', {
	contexts: [ { id: 'glossary-de' } ]
} );
```

<a id="quick-actions">

### Quick Actions

The [Quick Actions](ckeditor-ai-actions.md) feature lets you trigger predefined AI actions programmatically via the `AIActions` plugin. See the [quick actions documentation](ckeditor-ai-actions.md) for the full list of available actions and configuration options.

The demo below shows a payment reminder email with hardcoded customer data. The editor is configured with [merge fields](../merge-fields.md) for customer name, amount, due date, and other placeholders. Click the button to run a custom AI action that automatically replaces the hardcoded values with the appropriate merge field placeholders – built from the editor’s merge fields configuration.

<!-- AI-AGENT-NOTE: An interactive demo is embedded here but is not represented in this Markdown file. If you need to see it in action, open this page in a browser (e.g. via a browser-automation MCP like Chrome DevTools or Playwright), or let the user know a live demo is available on this page. -->

This demo presents a limited set of AI features. Visit the [CKEditor AI overview](ckeditor-ai-overview.md#demo) to see more in action.

Quick actions operate on the current editor selection. If the selection is collapsed (no text is selected), the action automatically expands to the nearest block element.

<a id="execute-an-action-directly">

#### Execute an action directly

Use the [`executeAction()`](../../api/module_ai_aiactions_aiactions-AIActions.md#function-executeAction) method to run system actions or fully custom prompts:

```js
const aiActions = editor.plugins.get( 'AIActions' );

// Run a system action.
await aiActions.executeAction(
	{ actionName: 'improve-writing' },
	'Improve writing'
);

// Or run a custom prompt (model is required for custom actions).
await aiActions.executeAction(
	{ userMessage: 'Rewrite the selected text as a haiku', model: 'agent-1' },
	'Make it a haiku'
);
```

The available system `actionName` values are defined by the [`AIActionsNames`](../../api/module_ai_aiactions_aiactions-AIActionsNames.md).

The action definition also accepts `contexts`, a list of [Context Library](ckeditor-ai-context-library.md#attaching-contexts-in-programmatic-flows) references attached to the request. Only the references you pass are sent:

```js
await aiActions.executeAction(
	{ actionName: 'improve-writing', contexts: [ { id: 'style-guide' } ] },
	'Improve writing'
);
```

<a id="rest-api">

## REST API

The same AI service that powers the editor features is also available as a REST API. You can call it from your frontend – using the editor’s authentication token – to build AI-powered features around the editor. This is especially useful for scenarios where you need AI capabilities **outside** the editor content area, such as auto-generating a title or meta description in separate form fields based on the editor content.

The demo below shows a form with a title and meta description field above the editor. Click the button to generate both fields from the editor content using the AI REST API.

<!-- AI-AGENT-NOTE: An interactive demo is embedded here but is not represented in this Markdown file. If you need to see it in action, open this page in a browser (e.g. via a browser-automation MCP like Chrome DevTools or Playwright), or let the user know a live demo is available on this page. -->

This demo presents a limited set of AI features. Visit the [CKEditor AI overview](ckeditor-ai-overview.md#demo) to see more in action.

<a id="what-you-can-do">

### What you can do

The AI REST API (`https://ai.cke-cs.com`) exposes endpoints you can call from both frontend and backend applications.

* **[Actions](../../../../cs/latest/guides/ckeditor-ai/actions.md)** – Stateless, single-purpose content transforms. Use these for operations like fixing grammar, improving writing, translating short sections, adjusting length or tone, or running custom prompts against content.
* **[Conversations](../../../../cs/latest/guides/ckeditor-ai/conversations.md)** – Multi-turn chat with conversation history, file uploads, and web search capabilities.
* **[Reviews](../../../../cs/latest/guides/ckeditor-ai/reviews.md)** – Document analysis for grammar, clarity, readability, and tone, returning specific suggestions for improvement. Also supports full-document translation, ensuring all text is translated even in longer content.
* **[Document Processing](../../../../cs/latest/guides/ckeditor-ai/document-processing.md)** – Whole-document transformations driven by a free-form prompt. Returns the modified document and a summary of changes as a synchronous JSON response. Designed for server-side workflows like CMS publish hooks, content pipelines, document personalization, and batch-style processing where you call the endpoint once per document.

> **Experimental**
>
> The Document Processing feature is **experimental** – it is production-ready but may change in minor releases without the standard deprecation policy. Breaking changes will always be documented in the changelog with migration guidance.

<a id="calling-the-rest-api-from-the-frontend">

### Calling the REST API from the frontend

AI generation endpoints, such as Actions calls and Conversation message calls, return **Server-Sent Events (SSE)** streams. This means you cannot simply `await response.json()` for these responses – instead, you need to read the response stream and parse the individual events. Other REST API endpoints, such as the models endpoint or the Document Processing endpoint, return regular JSON responses.

The following example shows how to call the AI Actions API from the browser and collect the streamed result:

```js
// Get the editor content.
const html = editor.getData();

// Get the auth token from the editor's token provider.
const token = editor.plugins.get( 'CloudServices' ).token.value;

// Call the AI Actions API (system action: improve-writing).
const response = await fetch( 'https://ai.cke-cs.com/v1/actions/system/improve-writing/calls', {
	method: 'POST',
	headers: {
		'Content-Type': 'application/json',
		'Authorization': `Bearer ${ token }`
	},
	body: JSON.stringify( {
		content: [
			{
				type: 'text',
				content: html
			}
		]
	} )
} );

// Read the SSE stream.
// Each SSE message has an "event:" line (e.g. "text-delta") and a "data:" line with JSON.
const reader = response.body.getReader();
const decoder = new TextDecoder();
let result = '';
let currentEvent = '';

while ( true ) {
	const { done, value } = await reader.read();

	if ( done ) {
		break;
	}

	const chunk = decoder.decode( value, { stream: true } );

	for ( const line of chunk.split( '\n' ) ) {
		if ( line.startsWith( 'event: ' ) ) {
			currentEvent = line.slice( 7 ).trim();
		} else if ( line.startsWith( 'data: ' ) && currentEvent === 'text-delta' ) {
			const data = JSON.parse( line.slice( 6 ) );

			result += data.textDelta;
		}
	}
}
```

> **Note**
>
> The example above is simplified for clarity. In production, handle errors, authentication token refresh, and edge cases in SSE parsing (such as events split across chunks).

<a id="calling-the-document-processing-endpoint">

### Calling the Document Processing endpoint

The Document Processing endpoint returns a standard JSON response so no stream parsing is needed:

```js
// Get the editor content.
const html = editor.getData();

// Get the auth token from the editor's token provider.
const token = editor.plugins.get( 'CloudServices' ).token.value;

const response = await fetch( 'https://ai.cke-cs.com/v1/documents/process', {
	method: 'POST',
	headers: {
		'Content-Type': 'application/json',
		'Authorization': `Bearer ${ token }`
	},
	body: JSON.stringify( {
		content: [{
			type: 'document',
		 	content: html,
		}],
		prompt: 'Fix grammar and spelling errors'
	} )
} );

const { document: processedDoc, summary } = await response.json();
```

<a id="full-rest-api-documentation">

### Full REST API documentation

For the complete API reference, including all available endpoints, request and response formats, streaming, and authentication details, see the [AI REST API documentation](https://ai.cke-cs.com/docs).

<a id="server-side-editor-api">

## Server-side Editor API

The [Server-side Editor API](../cloud-services/server-side-editor-api.md) lets you execute CKEditor 5 JavaScript code on the server through the `evaluate-script` REST endpoint, giving you the full editor API without a browser.

Because the editor and its AI plugins run server-side, you drive AI with the same [front-end editor API](#front-end-editor-api) you would use in the browser – the AI gateways covered above – but headless and against a live collaboration document. Results are written straight into the document model as real suggestions or direct changes and sync to everyone editing it.

> **Note**
>
> By default, the Server-side Editor API has no access to AI services. To run the AI gateways server-side, the request must include a user token with AI permissions. See [Working with AI services](../cloud-services/server-side-editor-api.md#working-with-ai-services) for details.

<a id="what-you-can-do-2">

### What you can do

* **Edit documents that are open for collaboration** – Apply an AI transformation to a document while users have it open, and the change shows up for all of them in real time, with no manual reload.
* **Let reviewers approve AI edits** – Write AI output back as [track changes](../collaboration/track-changes/track-changes.md) suggestions instead of overwriting the content, so a person accepts or rejects every change.
* **Run AI from back-end events** – Drive reviews, translations, or rewrites from publish hooks, scheduled jobs, CMS save actions, or even your own AI workflows, with no editor instance on screen.
* **Transform documents in bulk** – Apply one AI operation across many documents in a batch, such as translating an entire knowledge base or aligning the tone of every article.

<a id="example-automated-content-improvement">

### Example: automated content improvement

Because the editor and its AI plugins run server-side, the whole workflow runs through `evaluate-script` against a document in a collaboration session:

1. **Get the gateway** – Read the AI plugin you need from the running editor, for example `editor.plugins.get( 'AIDocumentProcessingGateway' )`.
2. **Run the AI** – Call its programmatic API, such as `processDocument()` with a prompt, exactly as you would in the browser.
3. **Apply the result** – Write the changes back with the gateway, either as direct content or as [track changes](../collaboration/track-changes/track-changes.md) suggestions for later review.

The suggestions land in the live document and sync to everyone editing it – no browser required.

<a id="learn-more">

### Learn more

For detailed setup instructions and the API reference, see the [Server-side Editor API documentation](../cloud-services/server-side-editor-api.md).

---

Full index of the CKEditor 5 documentation: [llms.txt](../../../llms.txt)
