Bots API
Use Sendeasy's AI from your own application — an assistant inside your product, a routine in your backend, a screen that summarizes documents — without going through WhatsApp.
The bot carries the instructions: you create and edit it in the panel, and pass its botId here. Changing how the AI behaves means editing the bot, not changing your code.
Prerequisites
- Project API Token — in Integrations → WABA → API Tokens section, where you create a named token and can revoke it. It is the same token used by the WABA API and the Email API: one per project, not one per product.
- A bot created in the panel, with its instructions and knowledge sources — see /en/bot. Keep the
botId. - Bot credits available in the project plan.
Use GET /api/v1/ai/health to check the integration without spending credits.
Authentication
Required header:
Authorization: Bearer <api_token>
The token identifies the project, and it is what authorizes the botId: a bot from another project is rejected, even if you know its id.
It is the same API Token used by the WABA API. Revoking it in Integrations → WABA breaks every integration that relies on it, not just this one.
Endpoints
| Method | Endpoint | Purpose | Credit |
|---|---|---|---|
POST | /api/v1/ai/completion | Generate text with the bot's prompt | 1 per generation |
POST | /api/v1/ai/extract | Extract content from a PDF | 1 per file |
POST | /api/v1/ai/search | Search the bot's knowledge base | free |
GET | /api/v1/ai/health | Diagnostics: is the integration up? | free |
Limit: 60 requests per minute per API Token (429 ERR_PUBLIC_AI_RATE_LIMIT). Counting is per token, not per IP — several servers sharing one token share the quota, and the same server with different tokens gets separate quotas.
Text generation
POST /api/v1/ai/completion
Payload
| Field | Type | Required | Notes |
|---|---|---|---|
prompt | string | Yes | the request itself, up to 120,000 characters |
botId | string | One of the two | uses that bot's instructions and knowledge |
instructions | string | One of the two | standalone system prompt, up to 60,000 characters |
responseFormat | string | No | text (default) or json |
timeoutMs | number | No | between 1,000 and 90,000; service default if omitted |
Pass botId or instructions. With neither, the route answers 400 instead of spending a credit to return out-of-context text.
Example
curl --location 'https://backend.sendeasy.app/api/v1/ai/completion' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_TOKEN' \
--data '{
"botId": "your-bot-id",
"prompt": "Summarize the return policy in two paragraphs."
}'
Success response:
{
"success": true,
"result": "The return policy allows returns within 30 days..."
}
With "responseFormat": "json", the answer comes parsed in dados instead of result — useful when the prompt asks for a structure and you would rather not parse text:
{
"success": true,
"dados": { "prazo_dias": 30, "exige_nota": true }
}
Every request holds a connection open until the AI answers. Prefer a short timeoutMs on screens where someone is waiting, and leave the 90s ceiling for background routines.
PDF extraction
POST /api/v1/ai/extract
multipart/form-data upload with the file in the file field. PDF only, up to 25 MB.
| Field | Type | Required | Notes |
|---|---|---|---|
file | file | Yes | the PDF, application/pdf |
mode | string | No | text (default) returns the text in result; other modes return the payload in dados |
curl --location 'https://backend.sendeasy.app/api/v1/ai/extract' \
--header 'Authorization: Bearer YOUR_API_TOKEN' \
--form 'file=@"/path/contract.pdf"' \
--form 'mode="text"'
{
"success": true,
"result": "SERVICE AGREEMENT..."
}
Reading falls back to OCR for scanned PDFs, so large files take a while. If the AI cannot recognize the content, the answer is 502 with the reason — resending the same file will not help.
Knowledge base search
POST /api/v1/ai/search
Retrieves excerpts from the bot's sources without generating text. Use it to ground an answer of your own, or to show the user where the information came from.
| Field | Type | Required | Notes |
|---|---|---|---|
botId | string | Yes | which bot to search |
query | string | Yes | what to look for, up to 2,000 characters |
topK | number | No | how many excerpts to return, from 1 to 8 |
filters | object | No | narrows the search to certain documents (see below) |
filters takes two fields, both lists of file-name patterns (% matches any stretch):
| Field | Effect |
|---|---|
incluirArquivos | searches only the matching documents; omitted, it scans the whole base |
excluirArquivos | drops matching documents, even if they were included |
{
"botId": "your-bot-id",
"query": "warranty period",
"filters": {
"incluirArquivos": ["manual-%", "returns-policy.pdf"],
"excluirArquivos": ["draft-%"]
}
}
Useful when the base is large and you already know where the answer lives: without a filter, one long document can take up the result slots and drown out what matters.
curl --location 'https://backend.sendeasy.app/api/v1/ai/search' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_API_TOKEN' \
--data '{
"botId": "your-bot-id",
"query": "warranty period",
"topK": 3
}'
{
"success": true,
"trechos": [
{
"texto": "The legal warranty is 90 days for durable goods...",
"procedencia": "customer-manual.pdf",
"score": 0.82
}
]
}
No credit is charged: this is content retrieval, not generation. If the search fails, the answer carries trechos: [] instead of an error — without context your answer loses grounding, it does not cease to exist.
Integration diagnostics
GET /api/v1/ai/health
Free, and it never calls the model. It answers the two questions behind almost every integration failure: is the AI service up, and is there still balance?
curl --location 'https://backend.sendeasy.app/api/v1/ai/health' \
--header 'Authorization: Bearer YOUR_API_TOKEN'
{
"success": true,
"companyId": 42,
"botrag": { "ok": true, "latenciaMs": 210 },
"creditos": { "disponivel": 4820, "mes": "9/2026" },
"checkedAt": "2026-09-01T12:30:00.000Z"
}
botrag.ok: false— the AI service is unavailable; adetalhefield carries the cause. Nothing to do on your side other than retry later.creditos.disponivel: 0— the project ran out of credits and the generation routes will answer402. Renew the plan or wait for next month's allowance.
Which bots a token can reach
botId is resolved inside your API Token's project. A token from one project cannot use another project's bot, even knowing its id: the route answers 403 without generating anything or touching your balance. A bot that doesn't exist answers 404.
This applies to completion and search — the two routes that resolve a bot. extract doesn't use botId.
A bot's instructions are usually the result of a lot of fine-tuning, so we treat them as project content: no route returns the prompt text, and no token reaches another project's bot.
Credits and refunds
- Every generation (
completion,extract) deducts 1 bot credit from the project. - With no credits, the route answers
402 ERR_NO_BOT_CREDITSbefore any charge. - If generation fails after the charge, the credit is refunded automatically and the route answers
502. searchandhealthcost nothing.
The balance is the same as the support bot's on WhatsApp — there is no separate quota for the API. Heavy usage here leaves the WhatsApp bot without credits, and the other way around.
Error codes
| HTTP | error | When it happens |
|---|---|---|
| 400 | validation message | invalid payload, missing prompt, neither botId nor instructions, file that is not a PDF |
| 400 | INVALID_JSON | body is not valid JSON |
| 400 | FILE_TOO_LARGE | PDF above 25 MB |
| 401 | ERR_API_TOKEN_NOT_PROVIDED / ERR_API_TOKEN_INVALID_FORMAT / ERR_API_TOKEN_INVALID / ERR_API_TOKEN_AUTHENTICATION_FAILED | missing or malformed header, invalid or revoked token |
| 402 | ERR_NO_BOT_CREDITS | project has no bot credits (nothing was charged) |
| 403 | authorization message | botId of a bot that does not belong to the token's project |
| 404 | bot message | botId does not exist |
| 413 | PAYLOAD_TOO_LARGE | JSON body above 10 MB |
| 429 | ERR_PUBLIC_AI_RATE_LIMIT | more than 60 requests per minute |
| 502 | plain-text message | generation failed (credit refunded); when the AI knows why, the reason comes in the message |
| 504 | GATEWAY_TIMEOUT | generation exceeded the time limit |
The 502 is the only one that arrives with a free-form message instead of a code — handle it by status, not by the text, which may change. The others follow the platform's code contract, listed in /en/server/traducoes.
Bots API × bot on WhatsApp
The same bot, two paths — what changes is who drives the conversation:
Bots API (/api/v1/ai/*) | Bot in support | |
|---|---|---|
| Who calls | your application | the ticket, when it enters the queue |
| Conversation | each call stands alone | Sendeasy keeps the history in the ticket |
| Typical use | assistant and automations in your product | support on WhatsApp |
Security: treat the token like a password. Never embed it in browser JavaScript, public repositories or logs — keep the calls on your server, with the token in an environment variable or a secret manager.