API Documentation

REST API to integrate AI assistants into your platform. Base URL: configurable per environment.

All requests must include the header Content-Type: application/json unless stated otherwise.

Authentication

Three authentication methods available depending on the integration type.

API Key

For server-to-server integrations. Create your keys in the admin panel. Keys have the ak_ prefix and are only shown once when created.

HTTP
Header: X-API-Key: ak_your_key_here
# o alternativamente
Header: Authorization: Bearer ak_your_key_here

Embed Token

For integration via embedded widget. The token resolves the assistant's configuration from the backend.

HTTP
Header: X-Embed-Token: your_embed_token

JWT (Pre-Auth)

For authenticated integrations. The JWT must be signed with the shared secret configured in the panel.

HTTP
Header: X-Embed-Token: your_embed_token
Header: Authorization: Bearer eyJhbG...

Chat API

Send message (no streaming)

POST/api/v1/chat/message
JSON
// Request Body
{
  "message": "string",
  "assistantId?": "string",
  "conversationId?": "string"
}

// Response
{
  "conversationId": "string",
  "message": "string"
}

Send message (SSE streaming)

POST/api/v1/chat/stream

Response of type text/event-stream. Possible events:

SSE Events
// Request Body
{
  "message": "string",
  "assistantId?": "string",
  "conversationId?": "string"
}

// Event types
data: {"token":"hello","conversationId":"..."}           // text chunk
data: {"type":"thinking","content":"..."}                // tool processing
data: {"type":"action_preview","preview":{...}}          // action preview
data: {"type":"token","content":"..."}                   // text chunk (new format)

Confirm action

POST/api/v1/chat/confirm-action
JSON
{
  "conversationId": "string",
  "confirmAction": "string",
  "confirmPayload": {}
}

List conversations

GET/api/v1/chat/conversations
JSON
// Response
[
  {
    "id": "string",
    "createdAt": "string",
    "updatedAt": "string"
  }
]

Get messages

GET/api/v1/chat/conversations/:id/messages
JSON
// Response
[
  {
    "id": "string",
    "role": "user|assistant",
    "content": "string"
  }
]

Delete conversation

DELETE/api/v1/chat/conversations/:id

Get available actions

GET/api/v1/chat/actions?assistantId=xxx
JSON
// Response
[
  {
    "name": "string",
    "description": "string",
    "parameters": [...]
  }
]

Widget Integration

Embed the assistant on any website with a simple script tag.

Floating widget

HTML
<script src="https://your-api.com/widget/ai-assistant-widget.js"
  data-api-url="https://your-api.com/api"
  data-embed-token="your_token"
  data-mode="floating">
</script>

Inline widget

HTML
<div style="height: 600px;">
  <script src="https://your-api.com/widget/ai-assistant-widget.js"
    data-api-url="https://your-api.com/api"
    data-embed-token="your_token"
    data-mode="inline">
  </script>
</div>

Pre-authenticated

Pass a signed JWT to identify the user without requiring registration.

HTML
<script src="https://your-api.com/widget/ai-assistant-widget.js"
  data-embed-token="your_token"
  data-token="eyJhbG..."
  data-api-url="https://your-api.com/api">
</script>

PRE_AUTH integration (external authentication)

If your users are already authenticated on your platform, you can use PRE_AUTH mode so the widget identifies them automatically without additional registration.

Your backend signs a JWT with a secret shared between your system and the AI platform. The widget receives this JWT via data-token and sends it on every request.

Requirements

1. A shared secret configured on both systems (your backend and the assistant's admin panel).

2. A PRE_AUTH type embed created in the admin panel.

3. An endpoint on your backend that generates and signs the JWT for authenticated users.

Step 1: Generate the JWT on your backend

The JWT must contain the fields: sub (user ID), email, name, and tenantSlug.

Node.js / Express

JavaScript
const jwt = require('jsonwebtoken');
const PRE_AUTH_SECRET = process.env.AI_ASSISTANT_PRE_AUTH_SECRET;

app.get('/api/ai-assistant/token', authMiddleware, async (req, res) => {
  // Verify user has active subscription/access
  const user = req.user;

  const token = jwt.sign({
    sub: String(user.id),
    email: user.email,
    name: user.name,
    tenantSlug: 'your-tenant-slug'
  }, PRE_AUTH_SECRET, { expiresIn: '60m' });

  res.json({ token });
});

Python / Django

Python
import jwt
from django.conf import settings

@login_required
def ai_assistant_token(request):
    token = jwt.encode({
        'sub': str(request.user.id),
        'email': request.user.email,
        'name': request.user.get_full_name(),
        'tenantSlug': 'your-tenant-slug',
        'exp': datetime.utcnow() + timedelta(hours=1)
    }, settings.AI_ASSISTANT_PRE_AUTH_SECRET, algorithm='HS256')

    return JsonResponse({'token': token})

PHP / Laravel

PHP
Route::middleware('auth')->get('/api/ai-assistant/token', function (Request $request) {
    $token = JWT::encode([
        'sub' => (string) $request->user()->id,
        'email' => $request->user()->email,
        'name' => $request->user()->name,
        'tenantSlug' => 'your-tenant-slug',
        'exp' => time() + 3600
    ], config('services.ai_assistant.secret'), 'HS256');

    return response()->json(['token' => $token]);
});

Step 2: Inject the widget with the token

From your frontend, get the token from your backend and pass it to the widget via data-token.

HTML
<!-- 1. Fetch the token from YOUR backend -->
<script>
  fetch('/api/ai-assistant/token', {
    headers: { 'Authorization': 'Bearer ' + yourSessionToken }
  })
  .then(r => r.json())
  .then(data => {
    const script = document.createElement('script');
    script.src = 'https://your-assistant.com/widget/ai-assistant-widget.js';
    script.setAttribute('data-api-url', 'https://your-assistant.com/api');
    script.setAttribute('data-embed-token', 'your-embed-token');
    script.setAttribute('data-token', data.token);
    document.body.appendChild(script);
  });
</script>

Customization (Branding)

Configure the widget's appearance from the admin panel or via the API.

FieldTypeDescription
primaryColorstringWidget's primary color (hex)
avatarUrlstringURL of the assistant's avatar
headerColorstringHeader background color
headerTitlestringTitle shown in the header
welcomeMessagestringWelcome message when opening the chat
fontFamilystringWidget font family
borderRadiusstringBorder radius (e.g. "12px")
positionstringFloating widget position ("bottom-right", "bottom-left")
hideBrandingbooleanHide Paidio branding (Professional+ plans)
JSON
{
  "primaryColor": "#d4a853",
  "avatarUrl": "https://example.com/bot-avatar.png",
  "headerColor": "#0f172a",
  "headerTitle": "Soporte IA",
  "welcomeMessage": "Hola, en que puedo ayudarte?",
  "fontFamily": "Inter, sans-serif",
  "borderRadius": "12px",
  "position": "bottom-right",
  "hideBranding": false
}

Code examples

JavaScript (fetch)

JavaScript
const response = await fetch('https://your-api.com/api/v1/chat/stream', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-API-Key': 'ak_your_key',
  },
  body: JSON.stringify({ message: 'Hola' }),
});

const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  const chunk = decoder.decode(value);
  // Parse SSE lines...
}

Python

Python
import requests

response = requests.post(
    'https://your-api.com/api/v1/chat/message',
    headers={'X-API-Key': 'ak_your_key'},
    json={'message': 'Hola'},
)
print(response.json())

cURL

Bash
curl -X POST https://your-api.com/api/v1/chat/message \
  -H "Content-Type: application/json" \
  -H "X-API-Key: ak_your_key" \
  -d '{"message": "Hola"}'

Rate Limits

The API has a limit of 100 requests per minute per IP. If you exceed the limit, you'll get a 429 Too Many Requests error.

Additionally, your Stripe plan determines the monthly token consumption limit. You can check your current usage in the admin panel.

Errors

The API uses standard HTTP status codes:

CodeStatusDescription
200OKSuccessful request
201CreatedResource created successfully
400Bad RequestInvalid or missing parameters
401UnauthorizedCredentials not provided or invalid
403ForbiddenNo permission for this resource
404Not FoundResource not found
429Too Many RequestsRate limit exceeded
500Internal Server ErrorInternal server error

Error format

JSON
{
  "statusCode": 401,
  "message": "Invalid API key",
  "error": "Unauthorized"
}