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/conversationsEvery 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/workspacesThe workspaces this credential can attend, with its role in each.
GET/visitantesWho 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}/messagesMessage history (visitor, human agent, AI, system).
POST/{id}/messagesSend a message into the conversation as an agent.
POST/{id}/assignAssign the conversation to a human agent or department.
POST/{id}/resolveClose 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-caseStatus 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}/readMark the conversation as read by the team (unread counter).
POST/{id}/reopenReopen a closed conversation, keeping the history. Without this, closing by mistake had no way back.
POST/{id}/archiveArchive or unarchive. It leaves the inbox without being closed or losing anything; body is {valor:true|false}.
POST/{id}/pinPin to the top or release. Pinned ones lead the first page of the inbox. Body: {valor:true|false}.
POST/{id}/muteMute alerts for N hours, or {horas:null} to hear it again.
POST/{id}/unreadLeave 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}/forwardForward 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}/reactionReact 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}/locationSend 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-cardSend 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}/copilotoThe 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}/copilotoAsk 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}/enviarSend 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}/resumenRequest 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}/historialWhat 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-sourcesKnowledge sources attached to this agent.
POST/{id}/knowledge-sourcesAttach a knowledge source to this agent.
GET/{aiAgentId}One AI agent: instructions, confidence threshold, tools and department.

Knowledge (RAG) /knowledge-sources

POST/documentIndex a document as grounded knowledge.
POST/urlIndex a URL.
GET/{id}Source status and metadata.
DELETE/{id}Remove a source and its vectors.
POST/{id}/reindexRe-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}/aprobarApprove a proposal: it becomes active and records who approved it (admin).
POST/{id}/rechazarReject a proposal (admin).
POST/probarPreview: what the fact sheet would answer to a question, with its score and band (optionally including proposals).
POST/recalcularRecompute 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}/credentialsSet a channel's credentials.
PUT/{id}/statusEnable or disable a channel.
POST/Create a channel.
GET/{channelId}One channel with its type, status and settings.
GET/{channelId}/snippetThe single line the client pastes into their site. Everything else is configured here.
GET/{channelId}/configThe chat configuration: what is published (`config`) and what this channel overrides (`settings`). They are different — do not confuse them.
PUT/{channelId}/configSave the configuration. Saving and publishing are one step: if the CDN fails, the PUT fails — no half-done "saved".
POST/{channelId}/config/publishRepublish the configuration to the CDN without changing it.
POST/{channelId}/avatarUpload the assistant’s avatar (≤512 KB). Stored in R2 and served from the CDN.
POST/republicar-todosRepublish every channel in the workspace. The cron does it when the string pack changes; this is to avoid waiting.
PUT/{channelId}/modoThe 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}/autoThe 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}/tarjetaThe 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}/horarioOffice 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/agentsList human agents in the workspace.
POST/agents/{userId}/departmentsPut an agent in a department.
GET/departmentsList departments.
PUT/departments/{id}/routing-rulesSet routing rules for incoming conversations.
POST/agentsInvite a person to the workspace, with their role and departments.
GET/agents/{userId}One team member.
POST/departmentsCreate a department with its routing rules.
GET/departments/{id}One department.

Webhooks, keys, billing, analytics /webhooks · /api-keys · /billing · /analytics

POST/webhooksRegister an endpoint; you receive its signing secret once.
DELETE/webhooks/{id}Remove an endpoint.
POST/api-keysCreate a key (read-only or read-write).
DELETE/api-keys/{id}Revoke a key immediately.
GET/billing/subscriptionPlan, included AI resolutions and usage this period.
GET/analyticsAggregate metrics for the workspace.
GET/webhooksList webhook endpoints.
GET/webhooks/{id}/deliveriesDelivery history for a webhook, with retries. This is what tells you whether the failure is yours or ours.
GET/api-keysList keys. The secret is never returned — only its prefix.

Agent tools /conversations/{id} · /canned-responses · /tags · /presence

GET/conversations/{id}/notesInternal notes on the conversation. The visitor never sees them.
POST/conversations/{id}/notesAdd an internal note.
DELETE/conversations/{id}/notes/{noteId}Delete an internal note.
GET/canned-responsesSaved replies for the workspace, with their shortcut.
POST/canned-responsesCreate a saved reply.
PATCH/canned-responses/{cannedId}Edit a saved reply.
DELETE/canned-responses/{cannedId}Delete a saved reply.
POST/presencePresence 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/presenceIs anyone online right now? This decides whether a handoff waits or is deflected.
GET/ai-usageWorkspace inference usage per day and the conversations that spend the most.
GET/tagsWorkspace tags.
GET/conversations/{id}/tagsTags on a conversation.
POST/conversations/{id}/tagsTag a conversation. Creates the tag if it did not exist.
DELETE/conversations/{id}/tags/{tagId}Remove a tag from the conversation.
POST/conversations/{id}/transferTransfer the conversation to a department. Overrides routing rules: a manual transfer is not undone automatically.
POST/contacts/{contactId}/blockBlock a contact. They get a 403 with no detail: you do not explain to an abuser how they were detected.
GET/canned-responses/categoriesThe categories already used in canned responses (free text), for autocomplete.
POST/canned-responses/{cannedId}/useRecord 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}/attachmentsUpload an attachment as an agent. Strict allowlist and 10 MB: nothing a browser can execute gets in.
GET/conversations/{id}/attachmentsAttachments on a conversation.
GET/attachments/{attachmentId}Download an attachment. Served with hard headers and `nosniff`; only images and audio render inline.
POST/devices/registerRegister a device push token, to alert a person when the AI hands off. Outside the workspace prefix: `/v1/devices/register`.
POST/workspacesCreate a workspace. Outside the prefix: `/v1/workspaces`.
GET/workspaces/{workspaceId}/retencion/ensayoThe 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/visitantesWho 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}/invitarInvite 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-ticketA 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/catalogoWhich 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/llamadasThe 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-cursoThe calls happening right now.
GET/aparcadasParked calls, waiting for someone to pick them up.
GET/grabaciones/{id}/audioThe audio of a recording, served through the API (no public link) and never cached. The phone system logs who listened to it.
GET/extensionesThe extensions the panel can use as a phone (reserved ones are not listed).
POST/webphone-sesionRegister 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.createdA new conversation started on any channel.
message.createdA message landed (visitor, human, AI or system).
handoff.requestedThe AI decided it needs a human — the exact moment, with the summary.
conversation.resolvedA 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.