Empezar
Tres cosas y ya estás dentro: una clave, la URL de tu región y el permiso adecuado en esa clave.
- Crea una clave en Ajustes → API y webhooks, marcando solo los permisos que vayas a usar. Se enseña una vez: guárdala donde guardes el resto de secretos.
- Elige tu región arriba. Tu organización vive en una sola y sus datos no salen de ella.
-
Comprueba que funciona con
GET /v1/me, que te dice qué organización eres y qué permisos tiene la clave.
/v1/calls cada pocos segundos llega tarde y gasta de más.
Autenticación
Cada petición lleva la clave en una cabecera:
Authorization: Bearer ak_9f3c1d2b…
Escribe Bearer así, con mayúscula: bearer ak_… se rechaza
como si faltara la cabecera.
Una clave pertenece a la organización, no a una persona: tu
automatización no se cae el día que se va de la empresa quien la configuró. En
el registro de auditoría las acciones salen como
api-key:ak_9f3c1d2b, junto a la persona que responde de esa clave.
Cuando quien creó una clave sale del equipo —expulsión, suspensión o baja—, la clave sigue funcionando y pasa a responder de ella quien lo expulsa o suspende (en una baja voluntaria, el propietario más antiguo). Los administradores reciben un correo con la lista: si esa persona pudo copiar alguna, rótala (crea otra con los mismos permisos, cámbiala en tu integración y revoca la vieja).
Permisos
Sin el permiso, la respuesta es 403 y dice cuál
falta. Da a cada integración lo justo: es lo que hace que revocar una
clave sea una molestia y no una urgencia.
| Permiso | Da acceso a |
|---|---|
calendar:read | Calendars, availability and appointments. |
calendar:write | Create and cancel appointments. |
calls:read | Call list and detail. Metadata only, never the content. |
recordings:read | The recording audio, the transcript and the data the flow saved in the call. |
callbacks:read | The callback queue. |
campaigns:read | Campaigns and their counters. |
reports:read | Aggregated reports. |
extensions:read | The list of extensions. |
extensions:write | Add and remove extensions. |
groups:read | Hunt groups and queues, with their members. |
groups:write | Change who belongs to a group or a queue. |
dnc:write | Add numbers to the Do-Not-Call list. Cannot read it or remove anyone. |
assist:read | The operator assistants' profile: name and configuration. |
assist:write | Each query spends your organization's AI credits. |
chatbots:read | The chatbots and ALL their conversations, including what visitors wrote. |
chatbots:write | Open, write to and close conversations, request a person, rate and confirm bot actions. Each message spends AI credits. |
team:read | Who is on the team (name, email and role), pending invitations and the plan seats. |
team:write | Invite a person by email (uses a plan seat). Never grants the owner role. |
hours:read | The organization opening hours with their holidays and special days. |
hours:write | Change the weekly hours and add or remove holidays and special days. |
numbers:read | Which numbers the phone system answers and which flow each one goes to. Read only. |
flows:read | See what each of the organization's call flows does and which templates exist to create a new one. |
flows:write | Create flows from templates, change the greeting, agent instructions, hours and forwarding, publish them and assign a number. It changes what callers hear. |
audios:read | The organization's audio prompts (queues, voicemail, menus), where each one is used, the available voices and a temporary link to listen to them. |
audios:write | Create an audio from a text with an AI voice (spends AI credits), rename it or delete it. An audio used by a flow, queue or voicemail cannot be deleted. |
agents:read | The AI agents in the library: prompt, rules, model, voice, language and which flows use them. |
agents:write | Create agents and change their prompt, model, voice or language. An agent in use answers real calls: changing it requires explicit confirmation. Requires an administrator with Routing access. |
knowledge:read | The knowledge bases of assistants and chatbots, their documents and their text. |
knowledge:write | Add texts (FAQs, prices, policies) to an assistant or chatbot knowledge base and remove them. It changes what the bot answers. Requires an administrator with Routing access. |
contacts:read | The contact book: name, phone, email, company, tags and notes. This is personal data of your customers. |
contacts:write | Create contacts, change their data and add notes. Contacts cannot be deleted through the API. |
credits:read | The AI credit balance (plan pool and loaded credits) and what was used this month by model. Credits only: no prices or billing. |
live:read | Calls in progress, who is on each one, calls waiting in queue and extension status. Read only: it does not listen in or intervene. |
recordings:read cubre el audio y la transcripción:
con calls:read a secas se leen los metadatos de la llamada, nunca su
contenido.
Región
Si llamas a la región equivocada no recibes una lista vacía
—que parecería un dato y no lo es— sino un 421 con la URL buena:
{
"error": {
"type": "wrong_region",
"message": "esta organización se sirve desde otra región; repite la petición contra base_url",
"base_url": "https://eu.alpire.ai"
}
}
La misma clave vale en las dos: lo que cambia es dónde están los datos.
Formato común
Listados y paginación
{ "data": [ … ], "next_cursor": "01JZK8…" }
Paginación por cursor, no por número de página. Pasa el
next_cursor que te llega y para cuando venga null.
limit por defecto 50, máximo 200. Los catálogos cortos
(/v1/extensions, /v1/groups, /v1/queues,
/v1/calendars, /v1/availability) no paginan: llegan
enteros en data, sin next_cursor.
No hay offset a propósito: la tabla de llamadas crece sin tope y un
OFFSET 50000 lo acabarían pagando también las llamadas en curso, que
comparten esa base de datos. Los ids son ULID, así que ordenar por id
es ordenar por fecha.
Fechas
RFC 3339 y en UTC a la salida, con el desplazamiento escrito
(2026-08-08T09:30:00+00:00; trátalo igual que una Z).
A la entrada se acepta cualquier desplazamiento explícito
(…+02:00). En una query string el + hay que
codificarlo (%2B): sin codificar llega como un espacio y la petición da
400. Lo mismo con los cursores que llevan fecha. Lo que no se acepta es una fecha sin zona: en un producto
que corre en dos continentes, eso es una cita a la hora equivocada esperando a
ocurrir.
Errores
Mira siempre type, nunca message: el texto puede cambiar,
el tipo no. Todo error de /v1 sale con este formato,
también una ruta mal escrita, un verbo equivocado, un JSON mal cerrado o un
timeout. request_id es el mismo valor que la cabecera
X-Request-Id: guárdalo con el error. Cuando un mismo type
tiene varios motivos que tu código tiene que distinguir, llega además
error.code (p. ej. chat_closed o chat_full en
los 409 del chat), y el 409 de una reserva trae
error.alternatives ([{start, end}], huecos cercanos) y
error.hard.
{
"error": {
"type": "bad_request",
"message": "'status' no admite «canceled»; valores válidos: booked, cancelled",
"request_id": "01K5…"
}
}
| HTTP | type | Qué hacer |
|---|---|---|
| 400 | bad_request | Arreglar la petición (cuerpo, Content-Type, parámetros, un filtro con un valor que no existe). No reintentar igual. |
| 401 | unauthorized | Clave ausente, inválida, caducada o revocada. |
| 403 | forbidden | Falta un permiso (el mensaje dice cuál), organización suspendida, plan sin API, plan no operativo (solo escrituras) o, en asistencia, sin créditos de IA. |
| 404 | not_found | No existe — o no es tuyo: misma respuesta a propósito —, o la ruta no es de la API. |
| 405 | method_not_allowed | La ruta existe con otro método. |
| 409 | conflict | Petición válida, pero el mundo dice que no. |
| 409 | idempotency_in_progress | Otra petición con la misma Idempotency-Key sigue en curso: esperar Retry-After. Si llega sin Retry-After, la primera murió hace más de 15 minutos: usa una clave nueva. |
| 413 | payload_too_large | El cuerpo pasa de 1 MiB. |
| 416 | bad_request | Descarga de grabación con un Range que no cabe en el fichero. |
| 421 | wrong_region | Repetir contra el base_url del error. |
| 422 | idempotency_mismatch | Esa Idempotency-Key ya se usó con otra petición. Una clave por operación. |
| 429 | rate_limited | Esperar lo que diga Retry-After. En /v1/assist/ask hay tres motivos distintos (ver su nota). |
| 500 | internal | Fallo nuestro. Reintentar con espera creciente. |
| 502 | provider_error | Falló un tercero: el calendario (Cal.com, Google) o el proveedor del modelo de IA. Reintentar con espera creciente. |
| 503 | unavailable | El servicio de chat de la organización no respondió. Reintentar en unos segundos (con el mismo client_id no se duplica). |
| 504 | timeout | Pasó el tiempo máximo. Una escritura puede haberse completado: repítela con la misma Idempotency-Key. |
Límites
- 600 lecturas (
GET) por minuto y clave. - 60 escrituras (
POST,PUT,DELETE) por minuto y clave. - 60 descargas de grabación cada 5 minutos y clave.
- 1200 peticiones por minuto y dirección IP.
- 10 claves activas por organización. Revocar una libera hueco; las revocadas y las caducadas no cuentan.
Los tres primeros son por clave, no por cuenta: dos
integraciones tuyas no se quitan cupo entre ellas. Hoy se cuentan en cada
nodo de la región, así que con varios nodos detrás del balanceador
X-RateLimit-Remaining puede saltar entre una petición y otra; no
calcules tu ritmo contando peticiones, respeta el 429. Cada petición paga
un solo cubo, y GET /v1/me no gasta ninguno.
No hace falta que lo lleves a ciegas: toda respuesta que paga cupo trae
X-RateLimit-Limit, X-RateLimit-Remaining y
X-RateLimit-Reset (epoch en segundos) del cubo que gobierna esa
petición concreta — el de lectura, el de escritura o el de grabación, que
tienen ventanas distintas. Si te acercas a cero, frena antes de chocar.
Al pasarte recibes 429 con error.type: "rate_limited" y
la cabecera Retry-After con los segundos que faltan de verdad
para que se reponga ese cubo (también en error.retry_after). Respétala
y reintenta: no hace falta backoff propio. El techo por IP es la excepción: su
429 llega antes de mirar ningún cubo, con Retry-After: 60
fijo, sin error.retry_after ni cabeceras X-RateLimit-*.
Idempotencia y tiempos
Toda escritura acepta la cabecera Idempotency-Key: un valor único por
operación (una UUID vale). La primera petición con esa clave se ejecuta y su
respuesta se guarda 24 horas; repetirla con la misma clave y la
misma petición devuelve la misma respuesta, con
Idempotent-Replayed: true, sin volver a ejecutar nada. La misma clave
con otra petición es un 422. Un 400, 401,
403, 429 o 5xx no la consume: corrige o
reintenta con la misma. Un 404 o un 409 sí
son el resultado de esa clave durante las 24 horas: si reintentas cuando cambie la
situación (la llamada ya colgó, eliges otro hueco), usa una clave nueva. Las claves
son por clave de API, no por organización. Genera la clave una vez
por operación, fuera del bucle de reintentos: los ejemplos de abajo la generan en
la propia petición para que se vean completos.
Idempotency-Key: 5f0c2a8e-4b7d-4c1e-9f3a-2d6b8e1c7a90
Una petición tiene 30 s, salvo POST /v1/appointments
y POST /v1/assist/ask, que tienen 90 s. Pasado el
tiempo recibes un 504, pero la escritura no se
cancela: termina igual. Ante un 504 o un corte de red,
repite con la misma Idempotency-Key y recibirás el resultado real; sin
ella, repetir puede duplicar.
fetch() desde el
JavaScript de tu web no va a funcionar — y es a propósito: una clave en el
frontend es una clave pública, y la tuya puede leer llamadas y crear citas. El
patrón correcto es tu backend hablando con nosotros y tu frontend hablando con tu
backend.
Endpoints
Cada uno con sus parámetros, la petición ya montada en el lenguaje que elijas arriba y un ejemplo de respuesta real.
Ningún endpoint coincide. Prueba con el nombre de un parámetro
(cursor, party_size) o con un permiso
(calls:read).
Qué deja rastro
Una acción hecha por la API produce lo mismo que si la hubiera hecho una persona en el panel: mismo webhook, mismo registro de auditoría.
| Acción | Webhook | Auditoría |
|---|---|---|
POST /v1/appointments | appointment.booked (solo agenda nativa) | appointment.create |
POST /v1/appointments/{id}/cancel | appointment.cancelled | appointment.cancel |
POST /v1/extensions | — | extension.create |
DELETE /v1/extensions/{id} | — | extension.delete |
PUT /v1/groups/{id}/members | — | dial_group.update |
PUT /v1/queues/{id}/members | — | queue.update |
POST /v1/dnc | — | dnc.add |
POST /v1/assist/ask | — | assist.ask |
Las citas creadas por API emiten el mismo evento que las del
mostrador o las que reserva el agente por teléfono, pero el data no es
idéntico según el origen (la API y el panel mandan source,
end y resource_id; la voz, call_id): la tabla
campo a campo está en la referencia de
webhooks. Extensiones, grupos y colas no tienen webhook: tampoco lo emite el
panel.