Build on the same API we build on
Everything the platform does — conversations, contacts, AI agents, knowledge, analytics — is driven by this REST API. There is no private, better API behind it: this is the one. Base URL: `https://api.inteligenciaviva.com/v1`.
Quickstart: your first request in 60 seconds
Create an API key in your workspace, export it, and list your conversations. Keys are scoped per workspace and can be read-only or read-write.
curl https://api.inteligenciaviva.com/v1/workspaces/WORKSPACE_ID/conversations \
-H "Authorization: Bearer sk_live_..."
Every response is JSON. Collection endpoints return `data` arrays; errors return an `error` object with `code` and `message` and a matching HTTP status.
The free API — no account, no key
The free layer speaks API too. `POST /v1/free/widget-snippet` takes the exact same configuration the visual generator builds on the Free page and returns the ready-to-paste HTML snippet — no authentication, no signup. Use it to batch-generate widgets for client sites, regenerate embeds from CI when schedules change, or simply to touch the API before paying a cent.
curl -X POST https://api.inteligenciaviva.com/v1/free/widget-snippet \
-H "Content-Type: application/json" \
-d @widget-config.json
# → { "snippet": "<link rel=\"stylesheet\" ..." }
Authentication
All requests carry an API key in the `Authorization` header as a Bearer token. Keys are created and revoked from the API itself or in the dashboard, per workspace — revoking a key cuts its access immediately.
Account-level session endpoints (register, login, Google sign-in, ticket exchange, profile, account deletion under `/v1/auth`) power our own apps; for server-to-server integrations always prefer workspace API keys over user sessions.
Authorization: Bearer sk_live_...
Resource reference
All routes below hang from `https://api.inteligenciaviva.com/v1/workspaces/{workspaceId}` unless noted. This is the real mounted surface of the current backend — not aspirational.
Cross-workspace inbox /inbox
| GET | /conversations | Every conversation from ALL workspaces this credential attends, in one list ordered by activity. The only route family that does not hang off /workspaces/{id}. |
| GET | /conversations/{id} | A conversation looked up across all your workspaces, without knowing which one it is in. Returns its workspace_id. |
| GET | /workspaces | The workspaces this credential can attend, with its role in each. |
| GET | /visitantes | Who is browsing RIGHT NOW across all your workspaces, with the page they are reading and whether they are already chatting. A snapshot, not a history: it is not paginated. A workspace that does not answer does not break the list: it goes in `casas_sin_responder`. |
Conversations /conversations
| GET | / | List conversations, filterable by state. |
| GET | /{id} | One conversation with its state, channel, contact and assignment. |
| GET | /{id}/messages | Message history (visitor, human agent, AI, system). |
| POST | /{id}/messages | Send a message into the conversation as an agent. |
| POST | /{id}/assign | Assign the conversation to a human agent or department. |
| POST | /{id}/resolve | Close the conversation. If the AI closed it alone, it counts as a billable resolution. |
| POST | / | Open a conversation from outside the widget (for example, to migrate an existing thread). |
| GET | /{id}/ai-turns | "Where did it get that?" — the log of every AI turn: what it relied on, which model, how long it took and why it handed off. |
| GET | /{id}/portal-case | Status of the case this conversation opened on the Portal support board, with its replies. Queried live: the status changes on the other board. |
| POST | /{id}/read | Mark the conversation as read by the team (unread counter). |
| POST | /{id}/reopen | Reopen a closed conversation, keeping the history. Without this, closing by mistake had no way back. |
| POST | /{id}/archive | Archive or unarchive. It leaves the inbox without being closed or losing anything; body is {valor:true|false}. |
| POST | /{id}/pin | Pin to the top or release. Pinned ones lead the first page of the inbox. Body: {valor:true|false}. |
| POST | /{id}/mute | Mute alerts for N hours, or {horas:null} to hear it again. |
| POST | /{id}/unread | Leave it unread: moves the read marker back behind the customer's last message. |
| POST | /{id}/typing | "Typing…" towards the customer: on WhatsApp Meta receives it, on web chat the visitor’s widget. No body; always 204 (a no-op on channels that do not support it) and 404 if the conversation does not exist. |
| POST | /{id}/messages/{messageId}/forward | Forward a message to another conversation in the same workspace, with its attachment (copied, not shared). Body: `{to_conversation_id}`. It goes out as an agent message. |
| PUT | /{id}/messages/{messageId}/reaction | React with an emoji to a WhatsApp message; it replaces the business’s previous reaction and `{emoji:null}` removes it. WhatsApp only (Meta’s API), and only on a message Meta has already confirmed: otherwise 400. |
| POST | /{id}/location | Send a location over WhatsApp: `{lat, lon, name?, address?, reply_to_message_id?}`. It goes to Meta first: if Meta rejects it, nothing is left in the thread. WhatsApp only. |
| POST | /{id}/contact-card | Send a contact card over WhatsApp: `{name:{formatted_name}, phones?, emails?, reply_to_message_id?}`. Same as location: Meta first, then the thread. WhatsApp only. |
| GET | /{id}/copiloto | The agent’s copilot: what the AI has suggested for this conversation and what was discussed with it. Team-only: the customer never sees it. |
| POST | /{id}/copiloto | Ask the AI something privately about this conversation (`{content}`, up to 4,000 characters). It answers THE AGENT, not the customer: nothing goes out through the channel. 503 `copilot_unavailable` if it cannot answer. |
| POST | /{id}/copiloto/{sugerenciaId}/enviar | Send a copilot suggestion to the customer, as is or edited (optional `{content}`). It goes out as an agent message, with its human author. A suggestion is sent only once: the second try returns 409 `ya_enviada`. |
| POST | /{id}/resumen | Request the conversation summary (open or closed) and update the contact’s profile with it. It costs one KIT task; asking again with no new messages does not charge again. If there is no summary, it returns why (`motivo`, `codigo`). |
Contacts /contacts
| GET | / | List contacts. |
| GET | /{id} | One contact with its identities and conversation history. |
| GET | /{id}/historial | What their recent enquiries were about: the latest conversations with their summary (`?limit=`, up to 25), the totals and the contact’s profile. Opening it does not generate a missing profile, because that costs money: request it explicitly with `?generar=1`. |
AI agents /ai-agents
| GET | / | List the workspace's AI agents. |
| POST | / | Create an AI agent: instructions, department, confidence threshold. |
| PATCH | /{id} | Update instructions, threshold or department. |
| GET | /{id}/knowledge-sources | Knowledge sources attached to this agent. |
| POST | /{id}/knowledge-sources | Attach a knowledge source to this agent. |
| GET | /{aiAgentId} | One AI agent: instructions, confidence threshold, tools and department. |
Knowledge (RAG) /knowledge-sources
| POST | /document | Index a document as grounded knowledge. |
| POST | /url | Index a URL. |
| GET | /{id} | Source status and metadata. |
| DELETE | /{id} | Remove a source and its vectors. |
| POST | /{id}/reindex | Re-read a URL source without deleting it: if the download fails, nothing is touched. |
| GET | / | List knowledge sources with their indexing status. |
Business fact sheet /ficha
| GET | / | List the units (filters ?estado= and ?tipo=) with the band thresholds. |
| POST | / | Create a unit: question, variants, approved answer, URL and language. An admin or an API key publishes it; an agent proposes it. The AI never publishes. |
| GET | /{id} | Get a unit. |
| PATCH | /{id} | Edit a unit; changing the question or the variants recomputes its embedding. |
| DELETE | /{id} | Delete a unit (admin). |
| POST | /{id}/aprobar | Approve a proposal: it becomes active and records who approved it (admin). |
| POST | /{id}/rechazar | Reject a proposal (admin). |
| POST | /probar | Preview: what the fact sheet would answer to a question, with its score and band (optionally including proposals). |
| POST | /recalcular | Recompute embeddings that are missing or no longer match the text (admin). |
Channels /channels
| GET | / | List channels. Live today: web chat and API; more connect as they ship. |
| PUT | /{id}/credentials | Set a channel's credentials. |
| PUT | /{id}/status | Enable or disable a channel. |
| POST | / | Create a channel. |
| GET | /{channelId} | One channel with its type, status and settings. |
| GET | /{channelId}/snippet | The single line the client pastes into their site. Everything else is configured here. |
| GET | /{channelId}/config | The chat configuration: what is published (`config`) and what this channel overrides (`settings`). They are different — do not confuse them. |
| PUT | /{channelId}/config | Save the configuration. Saving and publishing are one step: if the CDN fails, the PUT fails — no half-done "saved". |
| POST | /{channelId}/config/publish | Republish the configuration to the CDN without changing it. |
| POST | /{channelId}/avatar | Upload the assistant’s avatar (≤512 KB). Stored in R2 and served from the CDN. |
| POST | /republicar-todos | Republish every channel in the workspace. The cron does it when the string pack changes; this is to avoid waiting. |
| PUT | /{channelId}/modo | The channel’s attention mode: `{modo:"humano"|"hibrido"|"ia"}`. It decides whether the AI talks to the customer. Works for any channel type (WhatsApp, Telegram, email, widget). Admin only. |
| PUT | /{channelId}/auto | The channel’s automatic messages: `{bienvenida, fueraDeHorario}` (up to 1,000 characters each; `null` deletes). The after-hours one uses the channel’s office hours. Admin only. |
| PUT | /{channelId}/tarjeta | The contact card the agent can send from this channel: `{tarjetaContacto:{name:{formatted_name}, phones?, emails?}}`, with at least one phone or email; `null` removes it. WhatsApp only. Admin only. |
| PUT | /{channelId}/horario | Office hours: `{officeHours:{tz, dias:{lun:[["09:00","17:00"]], …}, festivos:["MM-DD" or "YYYY-MM-DD"]}}`; `null` removes them (24/7). Anything unreadable is rejected with 400 saying what is wrong. Returns `abiertoAhora`, computed by the server. Admin only. |
Human agents & departments /agents · /departments
| GET | /agents | List human agents in the workspace. |
| POST | /agents/{userId}/departments | Put an agent in a department. |
| GET | /departments | List departments. |
| PUT | /departments/{id}/routing-rules | Set routing rules for incoming conversations. |
| POST | /agents | Invite a person to the workspace, with their role and departments. |
| GET | /agents/{userId} | One team member. |
| POST | /departments | Create a department with its routing rules. |
| GET | /departments/{id} | One department. |
Webhooks, keys, billing, analytics /webhooks · /api-keys · /billing · /analytics
| POST | /webhooks | Register an endpoint; you receive its signing secret once. |
| DELETE | /webhooks/{id} | Remove an endpoint. |
| POST | /api-keys | Create a key (read-only or read-write). |
| DELETE | /api-keys/{id} | Revoke a key immediately. |
| GET | /billing/subscription | Plan, included AI resolutions and usage this period. |
| GET | /analytics | Aggregate metrics for the workspace. |
| GET | /webhooks | List webhook endpoints. |
| GET | /webhooks/{id}/deliveries | Delivery history for a webhook, with retries. This is what tells you whether the failure is yours or ours. |
| GET | /api-keys | List keys. The secret is never returned — only its prefix. |
Agent tools /conversations/{id} · /canned-responses · /tags · /presence
| GET | /conversations/{id}/notes | Internal notes on the conversation. The visitor never sees them. |
| POST | /conversations/{id}/notes | Add an internal note. |
| DELETE | /conversations/{id}/notes/{noteId} | Delete an internal note. |
| GET | /canned-responses | Saved replies for the workspace, with their shortcut. |
| POST | /canned-responses | Create a saved reply. |
| PATCH | /canned-responses/{cannedId} | Edit a saved reply. |
| DELETE | /canned-responses/{cannedId} | Delete a saved reply. |
| POST | /presence | Presence heartbeat: declares that a person is available. Session only — an API key that beats is not someone watching the screen, and is rejected with 400. |
| GET | /presence | Is anyone online right now? This decides whether a handoff waits or is deflected. |
| GET | /ai-usage | Workspace inference usage per day and the conversations that spend the most. |
| GET | /tags | Workspace tags. |
| GET | /conversations/{id}/tags | Tags on a conversation. |
| POST | /conversations/{id}/tags | Tag a conversation. Creates the tag if it did not exist. |
| DELETE | /conversations/{id}/tags/{tagId} | Remove a tag from the conversation. |
| POST | /conversations/{id}/transfer | Transfer the conversation to a department. Overrides routing rules: a manual transfer is not undone automatically. |
| POST | /contacts/{contactId}/block | Block a contact. They get a 403 with no detail: you do not explain to an abuser how they were detected. |
| GET | /canned-responses/categories | The categories already used in canned responses (free text), for autocomplete. |
| POST | /canned-responses/{cannedId}/use | Record that a canned response was used (counter and last use). Always 204, whether it exists or not: it is usage telemetry, not an action. |
Attachments, devices and workspaces /conversations/{id}/attachments · /devices · /workspaces
| POST | /conversations/{id}/attachments | Upload an attachment as an agent. Strict allowlist and 10 MB: nothing a browser can execute gets in. |
| GET | /conversations/{id}/attachments | Attachments on a conversation. |
| GET | /attachments/{attachmentId} | Download an attachment. Served with hard headers and `nosniff`; only images and audio render inline. |
| POST | /devices/register | Register a device push token, to alert a person when the AI hands off. Outside the workspace prefix: `/v1/devices/register`. |
| POST | /workspaces | Create a workspace. Outside the prefix: `/v1/workspaces`. |
| GET | /workspaces/{workspaceId}/retencion/ensayo | The purge dry run: how many conversations and attachments (and bytes) retention would remove, without deleting anything. `?days=` asks about a period not saved yet. Admin and `analytics:read` required. Full route: `/v1/workspaces/{workspaceId}/retencion/ensayo`. |
Live visitors and real time /visitantes · /stream-ticket
| GET | /visitantes | Who is browsing this workspace’s sites right now: page, language, since when, and whether they already have the chat open or a conversation. |
| POST | /visitantes/{visitorId}/invitar | Invite a visitor to open the chat, with an optional `{mensaje}` (without it, the widget’s greeting in their language). It creates no conversation and spends no AI: the conversation starts when the visitor replies. 404 `no_esta` if they have left; 409 `ya_conversa` if they are already chatting. |
| POST | /stream-ticket | A ticket to open the stream WebSocket with `?ticket=` without putting the token in the URL. It lasts 60 s and works only once. Works with a session or an API key. |
KIT capabilities /conversations/{id}/kit
| GET | / | The KIT tasks launched in this conversation, with their status. Listing them also checks the pending ones. |
| GET | /catalogo | Which KIT capabilities can be run on an attachment (`?attachment_id=`) or on the thread, with search (`?q=`, `?lang=`). Read-only: it costs nothing. |
| POST | / | Run a KIT capability (`{action, attachment_id?, params?}`) on an attachment or on the thread. The result comes back as an INTERNAL NOTE, never to the customer. It costs money from the KIT wallet, and the same request is never charged twice. 202 with the task queued. |
Phone (VOICE) /voice
| GET | /llamadas | The call log of the workspace’s phone system (`?limite=` up to 200, `?antes_de=` to paginate). Scope `voice:read`; 404 if the workspace has no phone system. |
| GET | /llamadas-en-curso | The calls happening right now. |
| GET | /aparcadas | Parked calls, waiting for someone to pick them up. |
| GET | /grabaciones/{id}/audio | The audio of a recording, served through the API (no public link) and never cached. The phone system logs who listened to it. |
| GET | /extensiones | The extensions the panel can use as a phone (reserved ones are not listed). |
| POST | /webphone-sesion | Register the browser as the phone of an extension (`{extension_id}`): returns a short-lived SIP credential that the phone system rotates on every exchange. Scope `voice:write`. The phone system’s key never leaves the server. |
Real examples
Send an agent message (curl)
curl -X POST \
https://api.inteligenciaviva.com/v1/workspaces/$WS/conversations/$CONV/messages \
-H "Authorization: Bearer $IV_KEY" \
-H "Content-Type: application/json" \
-d '{"content": "Hi! I checked your order — it ships tomorrow."}'List conversations (JavaScript)
const res = await fetch(
`https://api.inteligenciaviva.com/v1/workspaces/${WS}/conversations`,
{ headers: { Authorization: `Bearer ${process.env.IV_KEY}` } }
);
const { data } = await res.json();Verify a webhook signature (Node)
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret, rawBody, header, toleranceSec = 300) {
const { t, v1 } = Object.fromEntries(
header.split(",").map((p) => p.split("="))
);
if (Math.abs(Date.now() / 1000 - Number(t)) > toleranceSec) return false;
const expected = createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
return timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}
Webhooks
Register an HTTPS endpoint and we POST every event to it, signed. The header is `iv-signature: t=<unix>,v1=<hex>`, where `v1` is the HMAC-SHA256 of `<timestamp>.<raw body>` with your endpoint's secret — reject anything older than 5 minutes (replay protection). Failed deliveries are retried automatically with a fresh signature, up to 5 attempts.
| conversation.created | A new conversation started on any channel. |
| message.created | A message landed (visitor, human, AI or system). |
| handoff.requested | The AI decided it needs a human — the exact moment, with the summary. |
| conversation.resolved | A conversation closed; tells you whether the AI closed it alone. |
Every delivery and its status is recorded — a webhook you can't trust is worse than none.
Real-time streaming
For live UIs, open a WebSocket against the workspace stream and receive conversation events as they happen — the same channel our own agent apps use.
wss://api.inteligenciaviva.com/v1/workspaces/{workspaceId}/stream
Errors and limits
Errors are conventional HTTP: `400` malformed request, `401` missing or invalid key, `403` key without scope, `404` resource not in this workspace, `429` rate limited (back off and retry with jitter), `5xx` our fault — retry idempotent requests. The body always carries an `error.code` and a human `error.message`. Rate limits are per key and generous for real integrations.
Frequently asked questions
Is this documentation aspirational or the real surface?
Real. It is written against the mounted routes of the current backend. When something here says "coming", it does not exist yet — we don't document vapor.
Do I need a paid plan to use the API?
Yes — the API is part of the paid platform (Pro includes it). The free Widget and WordPress plugin are client-side and need no API at all.
What's the difference between read-only and read-write keys?
Read-only keys can list and fetch but never mutate — ideal for dashboards and BI. Read-write keys can create messages, contacts, AI agents and configuration.
How do I get notified of new messages — polling or webhooks?
Webhooks (`message.created`) for servers, the WebSocket stream for live UIs. Polling works but you'll hit rate limits before you get real-time.
How exactly do I verify a webhook came from you?
Recompute HMAC-SHA256 of `<t>.<raw body>` with your endpoint secret and compare in constant time with the `v1` value of the `iv-signature` header; reject if `t` is older than 300 seconds. Working code is on this page.
What happens if my webhook endpoint is down?
We record the failed delivery and retry automatically with a fresh timestamp and signature, up to 5 attempts. If the endpoint was deleted or disabled meanwhile, retries stop cleanly.
Can the AI hand off to my own escalation system?
Yes — that's what `handoff.requested` is for. It fires the moment the conversation passes to a person (the visitor asked, there were signs of crisis or frustration, or the answer wasn't backed by the knowledge), with the conversation summary, so you can alert someone or open a ticket in another system.
Which channels can I drive through the API today?
Web chat is live end to end, and everything in this reference works today. Telegram and further messaging channels connect to the same conversation model as they ship — your integration won't change.
Is there an SDK?
A Flutter SDK powers our own agent app and is the base of the white-label Enterprise SDK. For servers, the REST surface is deliberately plain — any HTTP client works without a wrapper.
Where do I report an API bug or a gap in these docs?
Write to l@inteligenciaviva.com. Docs that lie are bugs too — we treat them with the same severity.