---
title: AI Assistant with Cloud API Key
description: How to use Jodit Cloud API key as an AI provider for the aiAssistantPro plugin.
keywords: jodit cloud, ai assistant, streaming, sse, api key
---

# AI Assistant

You can use your Jodit Cloud API key as an AI provider for the `aiAssistantPro` plugin. The Cloud service provides a streaming AI endpoint that is compatible with the Jodit AI Assistant interface.

> **Important:** The AI endpoint validates the HTTP `Referer` header against your key's allowed domains. You must add your website domain to the **Referrers** list in the [Cloud Settings](/jodit/pro/cab/) for your API key, otherwise requests will be rejected with 403 Forbidden. See [API Key Settings](./settings.md) for details.

> **Note:** AI requests consume credits based on the model and usage. For details on rate limits and credit calculations, see [AI Usage Limits](./ai-limits.md).

## Quick Start (Recommended)

The simplest way is to use the built-in `jodit-ai-adapter` provider. It handles streaming, authentication, and error handling automatically:

```javascript
const providerOptions =
	Jodit.defaultOptions.aiAssistantPro.getAIProviderOptions(
		'jodit-ai-adapter',
		{
			url: 'https://cloud.xdsoft.net',
			apiKey: 'YOUR_CLOUD_API_KEY'
		}
	);

Jodit.make('#editor', {
	aiAssistantPro: {
		...providerOptions,
		instructions: 'You are a helpful writing assistant.'
	}
});
```

For all available options (provider switching, custom providers, streaming modes), see the full [Jodit AI Adapter documentation](https://github.com/jodit/jodit-ai-adapter).

---

The sections below describe the manual approach if you need full control over the request handling.

## Endpoint

```
POST {AI_HOST}/ai/request?key=YOUR_API_KEY
```

## Streaming Request Example

The AI endpoint supports Server-Sent Events (SSE) for real-time streaming responses:

```javascript
JoditLoader.ready().then(() => {
	const AI_HOST = 'https://api.xdsoft.net';
	const API_KEY = 'YOUR_API_KEY';

	async function* parseSSE(body) {
		const reader = body.getReader();
		const decoder = new TextDecoder();
		let buf = '';

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

				buf += decoder.decode(value, { stream: true });

				let boundary;
				while ((boundary = buf.indexOf('\n\n')) >= 0) {
					const chunk = buf.slice(0, boundary);
					buf = buf.slice(boundary + 2);

					let event = '';
					let data = '';

					for (const line of chunk.split('\n')) {
						if (line.startsWith('event:')) {
							event = line.slice(6).trim();
						} else if (line.startsWith('data:')) {
							data += line.slice(5).trim();
						}
					}

					if (event && data) {
						yield { event, data };
					}
				}
			}
		} finally {
			reader.releaseLock();
		}
	}

	const editor = Jodit.make('#editor', {
		aiAssistantPro: {
			maxRetries: 0,
			instructions:
				'You are an AI assistant for the Jodit editor. Help users with editing and content creation.',
			async apiRequest(context, signal) {
				const response = await fetch(
					`${AI_HOST}/ai/request?key=${API_KEY}`,
					{
						method: 'POST',
						headers: { 'Content-Type': 'application/json' },
						body: JSON.stringify({
							provider: 'openai',
							context: {
								...context,
								metadata: {
									...context.metadata,
									stream: true
								}
							}
						}),
						signal
					}
				);

				if (!response.ok) {
					throw new Error(
						`AI request failed with status ${response.status}`
					);
				}

				const stream = (async function* () {
					for await (const { data } of parseSSE(response.body)) {
						yield JSON.parse(data);
					}
				})();

				return { mode: 'stream', stream };
			}
		}
	});
});
```

## SSE Event Format

The streaming endpoint returns events in the following format:

```
event: created
data: {"responseId":"...","content":"","finished":false}

event: text-delta
data: {"delta":"Hello"}

event: text-delta
data: {"delta":" world"}

event: completed
data: {"responseId":"...","content":"Hello world","finished":true}
```

## Non-Streaming Request

If you prefer a simpler non-streaming approach:

```javascript
const editor = Jodit.make('#editor', {
	aiAssistantPro: {
		maxRetries: 0,
		instructions: 'You are an AI assistant for the Jodit editor.',
		async apiRequest(context, signal) {
			const response = await fetch(
				`${AI_HOST}/ai/request?key=${API_KEY}`,
				{
					method: 'POST',
					headers: { 'Content-Type': 'application/json' },
					body: JSON.stringify({
						provider: 'openai',
						context
					}),
					signal
				}
			);

			if (!response.ok) {
				throw new Error(
					`AI request failed with status ${response.status}`
				);
			}

			return {
				mode: 'final',
				response: (await response.json()).result
			};
		}
	}
});
```
