- Home
- Extend
- Architecture and APIs
- AI API reference
AI API reference
DXPR Builder exposes three AI routes. The browser
calls them, and the controllers proxy the request
through the Drupal AI module to the DXPR provider
(ai_provider_dxpr), so the API key never reaches
the browser. Routes are defined in
dxpr_builder.routing.yml; behaviour below comes
from src/Controller/AiChatController.php and
src/Controller/AiImageController.php.

Access requirements (all three routes)
| Requirement | Source |
|---|---|
HTTP method POST |
methods: [POST] in the route |
Permission edit with dxpr builder |
_permission |
| Billable user (valid licence seat) | _dxpr_builder_billable_user, checked by DxprBuilderBillableUserAccessCheck |
CSRF token in the token query parameter |
_csrf_token: 'TRUE' |
| Logged-in Drupal session cookie | Drupal access checks run per user |
The token is Drupal's route CSRF token for the
route path. DXPR Builder generates it server side
and embeds it in drupalSettings.dxprBuilder.aiAgent
as endpointUrl, imageEndpointUrl and
imageEditEndpointUrl (see
DxprBuilderFormatter). Read one of those values
from a page where the editor is loaded to obtain a
working URL. A failed access check returns Drupal's
standard 403 response.
The controllers always instantiate the dxpr
provider; the provider set at
/admin/config/ai/settings is not used.
Chat
POST /dxpr_builder/ajax/ai/chat?token=...
Used by text commands, page creation and prompt
suggestions. The controller reads a JSON body and
calls $provider->chat().
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
messages |
array of {role, content} |
yes | role is system, user or assistant |
model |
string | no | Defaults to kavya-m1. Must be on the provider's Allowed models list |
stream |
boolean | no | Defaults to true |
prediction |
object | no | Passed through, for example {"type":"content","content":"<p>...</p>"} |
providers |
string | no | Comma-separated provider IDs, for example openai,mistral |
allowed_html_tags |
string | no | Passed through; the builder sends Bootstrap 5 HTML |
allowed_html_classes |
string | no | Passed through; the builder sends Bootstrap 5 Classes |
web_search |
boolean | no | Only false is forwarded |
response_format |
object | no | Passed through, for example a json_schema object |
The controller always adds jsonrpc: false to the
provider configuration. Before the call it merges
the domains from AI Output Filtering > Allowed
domains into the AI module's hostname filter for
the duration of the request (* switches the
filter to full trust).
Response
Streamed (stream true, the default):
200, Content-Type: text/event-stream. Each
event is data: {json} followed by a blank line,
and the stream ends with data: [DONE].
data: {"choices":[{"delta":{"content":"Hello"}}],"id":"...","model":"kavya-m1","usage":{...}}
data: [DONE]
id, object, created, model,
original_model and usage are copied from the
provider's raw chunk when present.
Non-streamed (stream false): 200,
application/json:
{"choices":[{"message":{"role":"assistant","content":"..."}}]}
Errors
| Status | Body |
|---|---|
400 |
Plain text Invalid request: messages required |
403 |
{"error":{"code":"model_not_allowed","message":"The selected model is not allowed. Please refresh the page to update your model options."}} when ai_provider_dxpr is installed and model is not on its Allowed models list |
500 |
{"error":{"message":"The request could not be completed."}} (details in the dxpr_builder log channel) |
Example
const url = drupalSettings.dxprBuilder.aiAgent.endpointUrl;
const res = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "kavya-m1",
stream: false,
messages: [
{ role: "system", content: "You are a copywriter." },
{ role: "user", content: "Shorten: <p>Our team ...</p>" }
]
})
});
const data = await res.json();
console.log(data.choices[0].message.content);
Image generation
POST /dxpr_builder/ajax/ai/image?token=...
Calls $provider->textToImage().
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
prompt |
string | yes | Text description of the image |
model |
string | no | Defaults to kavya-image |
size |
string | no | The builder sends 1024x1024, 1536x1024 or 1024x1536; omitted for Auto |
quality |
string | no | Passed to the provider unchanged |
background |
string | no | Passed to the provider unchanged |
Response
200, application/json. Only the first image
is returned, base64 encoded. The DXPR provider
requests webp output.
{"data":[{"b64_json":"UklGRi..."}]}
Errors
| Status | Body |
|---|---|
400 |
{"error":{"message":"Invalid request: prompt required"}} |
500 |
{"error":{"message":"No image was generated"}} |
500 |
{"error":{"message":"Image generation failed. Please try again."}} |
Example
curl -X POST "https://example.com/dxpr_builder/ajax/ai/image?token=TOKEN" \
-H "Content-Type: application/json" \
-H "Cookie: SESSxxx=..." \
-d '{"prompt":"Flat illustration of a rocket","size":"1536x1024"}'
Image editing
POST /dxpr_builder/ajax/ai/image/edit?token=...
Calls $provider->imageToImage() with the
uploaded image and the prompt.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
prompt |
string | yes | Editing instruction |
image |
string | yes | Base64 image data without a data: prefix; strict decoding |
mimeType |
string | no | Defaults to image/png; the subtype becomes the file extension |
model |
string | no | Defaults to kavya-image |
size, quality, background |
string | no | As for image generation |
Response
Same shape as image generation:
{"data":[{"b64_json":"..."}]}.
Errors
| Status | Body |
|---|---|
400 |
{"error":{"message":"Invalid request: prompt and image required"}} |
400 |
{"error":{"message":"Invalid image data: base64 decode failed"}} |
500 |
{"error":{"message":"No image was generated"}} |
500 |
{"error":{"message":"Image editing failed. Please try again."}} |
Example
const url = drupalSettings.dxprBuilder.aiAgent.imageEditEndpointUrl;
const res = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
prompt: "Remove the background",
image: base64String,
mimeType: "image/jpeg"
})
});
const { data } = await res.json();
Storing a generated image
The routes return image bytes only. The builder
converts b64_json to a webp file and uploads it
to POST /dxpr_builder/ajax/file_upload
(drupalSettings.dxprBuilder.fileUploadUrl, form
field upload[]), which responds with the file
url and fid. The prompt becomes the file name
(first 80 characters) and the alt attribute.