Guía

SwissAI te da agentes de voz que contestan llamadas telefónicas. Los creas desde el panel con un prompt, un idioma y herramientas opcionales; cada agente recibe una URI SIP. Enruta las llamadas a esa URI y el agente contesta, habla con quien llama y llama a tus endpoints HTTP cuando necesita datos o debe actuar.

Todo se configura desde el panel: agentes, herramientas, tus clientes y la facturación.

Agentes

Los agentes se crean y se editan en el panel: nombre, idioma, scope (el prompt), una base de conocimiento opcional, los modelos y las herramientas. Cada agente recibe un id y una SIP URI y puede asignarse a uno de tus clientes.

Ejemplo de scope

Escribe el scope como darías las instrucciones a un compañero nuevo: quién es el agente, qué puede hacer, qué no debe hacer nunca y cómo termina la llamada.

Eres Anna, la asistente de la Clínica Dental Keller en Zúrich.
Atiendes el teléfono cuando la recepción está ocupada o cerrada.

Qué haces:
- reservar, cambiar o cancelar citas, usando las herramientas;
- responder preguntas sobre horarios, dirección y precios (ver la base de conocimiento).

Reglas:
- habla de forma breve y amable, una pregunta cada vez;
- antes de reservar, repite día, hora y nombre y espera un sí claro;
- nunca des consejos médicos: ante dolor o urgencias ofrece la primera
  cita libre y da el número de urgencias;
- si no puedes ayudar, toma nombre y teléfono y di que la clínica devolverá la llamada.

Cierra la llamada repitiendo lo acordado.

Herramientas

Definición de herramienta

Una herramienta es un endpoint HTTP en tu lado, descrito al modelo con un esquema JSON. El modelo decide cuándo llamarla y con qué argumentos; la plataforma ejecuta la petición HTTP.

{
  "name": "book_appointment",
  "description": "Reserva una cita. Llámalo solo cuando quien llama haya confirmado día, hora y nombre.",
  "method": "POST",
  "url": "https://api.example.ch/clinics/{{clinic}}/appointments",
  "headers": { "Authorization": "Bearer sk_…" },
  "content_type": "application/json",
  "body": "{ \"start\": \"{{start}}\", \"name\": \"{{name}}\", \"phone\": \"{{phone}}\" }",
  "schema": {
    "type": "object",
    "properties": {
      "clinic": { "type": "string", "description": "Código de la clínica: siempre 'zurich'" },
      "start":  { "type": "string", "description": "Inicio, yyyy-MM-dd HH:mm, p. ej. 2026-10-06 10:30" },
      "name":   { "type": "string", "description": "Nombre y apellidos de quien llama" },
      "phone":  { "type": "string", "description": "Número de teléfono de quien llama, solo cifras" }
    },
    "required": ["clinic", "start", "name", "phone"]
  }
}
CampoParaDescripción
namemodeloNombre de la función, letras y guiones bajos.
descriptionmodeloQué hace y cuándo llamarla. Escríbelo para el modelo.
schemamodeloEsquema JSON de los argumentos.
response_schemamodeloOpcional. Forma de tu respuesta, para que el modelo sepa leerla.
urlruntimeTu endpoint, en HTTPS.
methodruntimeGET (argumentos en la query string) o POST (argumentos como cuerpo JSON).
content_typeruntimeNormalmente application/json.
headersruntimeOpcional. Cabeceras enviadas en cada llamada, p. ej. Authorization; los valores pueden contener {{parámetros}}.
bodyruntimeOpcional. Plantilla del body con {{parámetros}}. Vacío: los parámetros no usados en url o cabeceras van como query string (GET, DELETE) o body JSON.

Parámetros y placeholders

Escribe {{nombre}} en la url, en los valores de las cabeceras o en el body. Cada placeholder debe declararse en schema.properties con una descripción: el modelo lo rellena a partir de la conversación y el runtime lo sustituye (codificado en la url, escapado en un body JSON). El panel construye el esquema a partir de los placeholders y hace verificar la herramienta a Claude antes de guardarla.

Cómo te llama el agente

Cada petición lleva la cabecera token con el tool_token del agente. Responde 200 con un cuerpo JSON:

Tu endpoint: ejemplos

Esta es la petición que la plataforma envía para la herramienta de arriba, y un endpoint mínimo que la responde. Comprueba primero la cabecera token: es el tool token que configuraste en el agente.

Petición enviada por la plataforma
POST /clinics/zurich/appointments HTTP/1.1
Host: api.example.ch
Authorization: Bearer sk_…
token: 5f1c…            # el tool token del agente
Content-Type: application/json

{ "start": "2026-10-06 10:30", "name": "Anna Keller", "phone": "41791234567" }
Node.js (Express)
app.post('/clinics/:clinic/appointments', express.json(), async (req, res) => {
  // 1. solo tu agente puede llamar
  if (req.get('token') !== process.env.SWISSAI_TOOL_TOKEN) return res.status(401).end();

  const { start, name, phone } = req.body;

  // 2. error de negocio: una frase con la que el agente puede actuar
  if (!(await isFree(req.params.clinic, start)))
    return res.json({ success: false, error: "Esa hora está ocupada. Ofrece las 11:00 o las 14:30 del mismo día." });

  // 3. éxito: el mensaje es lo que el agente dice a quien llama
  await book(req.params.clinic, start, name, phone);
  res.json({ success: true, message: "Cita reservada para el " + start + '.' });
});
PHP
<?php
// appointments.php
header('Content-Type: application/json');
if (($_SERVER['HTTP_TOKEN'] ?? '') !== getenv('SWISSAI_TOOL_TOKEN')) { http_response_code(401); exit; }

$in = json_decode(file_get_contents('php://input'), true);

if (!is_free($in['start'])) {
    echo json_encode(['success' => false, 'error' => "Esa hora está ocupada. Ofrece las 11:00 o las 14:30 del mismo día."]);
    exit;
}
book($in['start'], $in['name'], $in['phone']);
echo json_encode(['success' => true, 'message' => "Cita reservada para el " . $in['start'] . '.']);

La sección Herramientas

Las herramientas están en una única lista, la sección Herramientas del panel, y los agentes las usan por referencia: modificas una herramienta una vez y se actualiza cada agente que la usa. Una herramienta asignada a un cliente solo se puede usar en los agentes de ese cliente; una herramienta sin cliente se puede usar en todos tus agentes. En el editor del agente eliges las herramientas de la lista, o creas una nueva que se añade a ella.

Sandbox: herramientas listas y código de ejemplo

La sandbox es un conjunto de endpoints de ejemplo en funcionamiento, con datos de ejemplo separados para cada cuenta: agenda, mesas de restaurante, almacén, devoluciones de llamada, contactos, pedidos. Sus 22 herramientas ya están en la sección Herramientas: añádelas a un agente y pruébalo en minutos, sin alojar nada. Cada herramienta tiene una guía con sus parámetros, un ejemplo resuelto de petición y respuesta y el código del endpoint que responde, que puedes descargar y usar como punto de partida para el tuyo.

Descargar todo el código (zip)

Cada cuenta tiene su propio entorno de pruebas con los mismos datos iniciales: tres servicios reservables con una cita ya ocupada, ocho mesas de restaurante con una reserva, seis productos, dos contactos y cuatro pedidos. Se crea automáticamente la primera vez que añades una herramienta de la sandbox a un agente, o desde la sección Herramientas, donde también puedes devolverlo a los datos iniciales. Un entorno sin uso durante 30 días se elimina y se puede volver a crear.

Basta con añadir una herramienta de la sandbox a un agente: el panel completa la dirección con la clave de tu entorno. Para llamar a los endpoints por tu cuenta, envía la clave en la cabecera Authorization: Bearer ws_… (en el panel, la guía de cada herramienta muestra el comando con tu clave). Las llamadas son GET, con los argumentos en la query string, o POST, con los argumentos en JSON.

Cada respuesta tiene estado 200 y un cuerpo JSON: success true con un mensaje para decir a quien llama más los datos, o success false con un error escrito como una frase para el agente, por ejemplo qué horarios quedan libres en su lugar. Los días son yyyy-MM-dd, las horas HH:mm, los teléfonos solo cifras. A continuación, cada área con qué es, dónde se usa y todas sus llamadas, cada una con un ejemplo de petición y la respuesta real.

Clientes

Un cliente es uno de tus clientes finales. Asígnale agentes desde el editor del agente; recibe una invitación por email y puede entrar (contraseña, Google o Apple) para ver sus agentes, llamadas y consumo mensual valorado a la tarifa que fijes. El cliente te paga a ti: SwissAI solo muestra las cifras.

Invitar, editar, quitar

Al crear un cliente en el panel se envía la invitación. Al quitarlo, sus agentes vuelven a ti y su acceso queda revocado.

Asignar un agente

En el editor del agente elige el cliente que ve sus llamadas; Uso personal lo deja solo para ti.

Telefonía

Un agente responde a las llamadas que llegan a su URI SIP en nuestra centralita. Hay tres formas de llevar una llamada hasta allí, todas configurables desde el menú Telefonía del panel: una centralita externa habilitada por dirección IP, una numeración y un cliente WebRTC en un sitio web. Desde el panel también puedes llamar a cualquier agente desde el navegador para probarlo; las pruebas cuentan como minutos igual que cualquier otra llamada.

URI SIP y centralitas externas

Cada agente tiene una URI SIP, visible en la lista de agentes, del tipo sip:ag_7f3a2b91@pbx.swissai.dev. Tu centralita envía la llamada a esa dirección: la parte anterior a la @ elige el agente.

Solo se aceptan llamadas de las centralitas que has declarado. En Telefonía › Centralitas externas añade la centralita con un nombre, el cliente al que pertenece (o Uso personal) y la dirección IP desde la que llegan sus llamadas. No hay usuario ni contraseña: la centralita se reconoce por su dirección IP y puede llamar a todos los agentes de ese cliente.

Asterisk (chan_sip)
; extensions.conf: la extensión 200 llama al agente
exten => 200,1,Dial(SIP/ag_7f3a2b91@pbx.swissai.dev,60)
 same => n,Hangup()
Asterisk (PJSIP)
; pjsip.conf: la centralita SwissAI como endpoint de salida, sin autenticación
[swissai]
type=endpoint
context=from-swissai
disallow=all
allow=alaw,ulaw
aors=swissai

[swissai]
type=aor
contact=sip:pbx.swissai.dev:5060

; extensions.conf: la extensión 200 llama al agente
exten => 200,1,Dial(PJSIP/ag_7f3a2b91@swissai,60)
 same => n,Hangup()
FreeSWITCH
<!-- dialplan: la extensión 200 llama al agente -->
<extension name="swissai_agent">
  <condition field="destination_number" expression="^200$">
    <action application="bridge" data="sofia/external/ag_7f3a2b91@pbx.swissai.dev"/>
  </condition>
</extension>
Kamailio / OpenSIPS
# kamailio.cfg / opensips.cfg, en la request route: las llamadas al 200 van al agente
if ($rU == "200") {
    $ru = "sip:ag_7f3a2b91@pbx.swissai.dev:5060";
    t_relay();
    exit;
}

Las llamadas desde una dirección no declarada se rechazan, y una dirección que insiste queda bloqueada durante un tiempo. Si tu centralita cambia de dirección IP, actualízala en el panel antes de enviar llamadas.

Numeraciones

Una numeración lleva a un agente las llamadas telefónicas normales. En Telefonía › Numeraciones cada número muestra la cuenta SIP con la que se registra (usuario, contraseña, host, puerto) y el agente que responde.

Clientes WebRTC (click to call)

Un cliente WebRTC es un botón de llamada en un sitio web: el visitante habla con el agente desde el navegador, sin teléfono. Créalo en Telefonía › Clientes WebRTC: elige el agente y enumera los dominios donde se usa el botón (añade localhost para probar en tu ordenador). Recibes el nombre del cliente (wc_…) y una clave secreta (wk_…), que se muestra una sola vez.

La clave secreta nunca debe llegar al navegador. La página pregunta a tu backend, tu backend nos pide una contraseña temporal con la clave y se la pasa a la página. La contraseña temporal vale 60 segundos y para una sola llamada.

1Tu backend pide la contraseña temporal
curl -X POST https://developers.swissai.dev/api/webrtc/token \
  -H "X-Api-Key: wk_your_secret_key" \
  -H "Content-Type: application/json" \
  -d '{"client": "wc_3f9a1c2b7d4e", "identity": "visitor-1842"}'
// 200
{
  "wss": "wss://pbx.swissai.dev:8089",
  "domain": "pbx.swissai.dev",
  "user": "w4c1f09ab37de5521",
  "pass": "9b2e61f0c4a87d35e1f2a6b90c7d4e88",
  "target": "sip:ag_7f3a2b91@pbx.swissai.dev",
  "expires_in": 60
}
CampoDescripción
clientObligatorio. El nombre del cliente WebRTC que aparece en el panel.
identityOpcional. Tu referencia para el visitante, por ejemplo un id de usuario.
401Cliente desconocido o clave incorrecta.
503Las llamadas desde el navegador no están disponibles para este cliente, por ejemplo porque su agente fue eliminado.
Node.js (Express)
// tu backend: la clave secreta se queda aquí
app.post('/call-token', async (req, res) => {
  const r = await fetch('https://developers.swissai.dev/api/webrtc/token', {
    method: 'POST',
    headers: { 'X-Api-Key': process.env.SWISSAI_CLIENT_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ client: 'wc_3f9a1c2b7d4e' })
  });
  res.status(r.status).json(await r.json());
});
PHP
<?php
// call-token.php: la clave secreta se queda en tu backend
$ch = curl_init('https://developers.swissai.dev/api/webrtc/token');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['X-Api-Key: ' . getenv('SWISSAI_CLIENT_KEY'), 'Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode(['client' => 'wc_3f9a1c2b7d4e']),
]);
$body = curl_exec($ch);
http_response_code(curl_getinfo($ch, CURLINFO_HTTP_CODE));
header('Content-Type: application/json');
echo $body;
2La página inicia la llamada

Carga nuestro script y pásale la respuesta tal cual. onState recibe connecting, calling, connected e idle; onError recibe el motivo cuando la llamada no puede empezar.

<button id="call">Llámanos</button>
<button id="hangup" hidden>Colgar</button>
<p id="status"></p>

<script src="https://developers.swissai.dev/js/call.js"></script>
<script>
const call = document.getElementById('call'), hangup = document.getElementById('hangup'), status = document.getElementById('status');

call.onclick = async () => {
  const params = await (await fetch('/call-token', { method: 'POST' })).json();   // tu backend, paso 1
  SwissCall.start(params, {
    onState: s => { status.textContent = s; call.hidden = s !== 'idle'; hangup.hidden = s === 'idle'; },
    onError: m => { status.textContent = m; }
  });
};
hangup.onclick = () => SwissCall.hangup();
</script>

GetCalls: las llamadas y lo que le cuestan a tu cliente

Con el nombre y la clave secreta de un cliente WebRTC tu backend puede leer las llamadas del cliente al que pertenece (todos los agentes de ese cliente) y lo que cada llamada le cuesta según la tarifa que definiste en Clientes.

curl "https://developers.swissai.dev/api/calls?client=wc_3f9a1c2b7d4e&month=2026-10" \
  -H "X-Api-Key: wk_your_secret_key"
// 200
{
  "month": "2026-10",
  "customer": { "customer_id": 7, "name": "Dental Practice Keller" },
  "currency": "CHF",
  "rate": 0.25,
  "free_minutes": 100,
  "calls": [
    { "call_ref": "c-91f3a2", "agent_id": "ag_7f3a2b91", "agent_name": "Anna", "source": "webrtc",
      "from": "", "started": "2026-10-06 10:41:03", "seconds": 84, "outcome": "appointment",
      "tool_calls": 2, "cost": 0.35 }
  ],
  "total": { "minutes": 312.4, "calls": 148, "billable_minutes": 213, "amount": 53.25 }
}
CampoDescripción
clientObligatorio. El nombre del cliente WebRTC que aparece en el panel.
monthOpcional. yyyy-MM; el mes en curso si falta. Hasta 500 llamadas, de la más reciente a la más antigua.
costLa duración de la llamada al precio por minuto del cliente, antes de los minutos incluidos.
totalEl mes tal como lo ve el cliente en su portal: minutos incluidos descontados, segundos redondeados al minuto sobre el total del mes.
customernull cuando el agente del cliente WebRTC es de uso personal: son las llamadas de tus agentes sin cliente, al precio de la plataforma.

Puedes probar las dos llamadas, con el código para copiar, desde el Área de pruebas del panel.

Chat: mensajes de texto al agente

El mismo agente que contesta al teléfono puede responder por escrito: en tu sitio, en tu aplicación o en un canal de mensajería que gestiones tú. Tu backend envía lo que ha escrito el usuario y recibe la respuesta como texto; mientras responde, el agente puede llamar a sus herramientas como hace por teléfono.

La llamada se hace con el nombre y la clave secreta de un cliente WebRTC, que eligen el agente, y debe salir de tu backend: la clave nunca debe llegar al navegador. No guarda estado: reenvía cada vez los últimos intercambios en history.

curl -X POST https://developers.swissai.dev/api/messages \
  -H "X-Api-Key: wk_your_secret_key" \
  -H "Content-Type: application/json" \
  -d '{
    "client": "wc_3f9a1c2b7d4e",
    "user": "visitor-1842",
    "name": "Laura Meier",
    "message": "¿Sigue libre el jueves a las 10?",
    "history": [
      { "direction": "in",  "text": "Hola, quisiera reservar una revisión." },
      { "direction": "out", "text": "Claro. ¿Qué día le vendría bien?" }
    ]
  }'
// 200
{ "reply": "Sí, el jueves a las 10:00 está libre. ¿Se lo reservo?" }
CampoDescripción
clientObligatorio. El nombre del cliente WebRTC que aparece en el panel.
messageObligatorio. El texto escrito por el usuario, hasta 4000 caracteres.
userOpcional. Tu referencia de quien escribe, por ejemplo un id de usuario o un número de teléfono.
nameOpcional. El nombre de quien escribe, si lo conoces.
historyOpcional. Los últimos intercambios, del más antiguo al más reciente, hasta 20: direction es in para el usuario y out para el agente, text es el mensaje.
replyLa respuesta del agente para mostrar al usuario; vacía cuando el agente no tiene nada que decir.
401Cliente desconocido o clave incorrecta.
402La prueba o el plan no están activos, o el wallet no tiene crédito.
502El agente no pudo responder: inténtalo de nuevo.

Puedes chatear con cualquier agente desde Telefonía › Chat y probar esta llamada, con el código para copiar, desde el Área de pruebas.

WhatsApp Business

Un agente puede responder en WhatsApp de dos maneras. O vinculas el número desde el panel y SwissAI recibe los mensajes y responde, o mantienes tu propia aplicación de Meta y tu webhook en tu sitio y envías cada mensaje al agente con la llamada de chat de arriba.

A. Vincular el número desde el panel

Telefonía › WhatsApp › Vincular con WhatsApp. Se abre una ventana de Meta: inicias sesión con la cuenta de Facebook de tu empresa, eliges el portafolio empresarial (o creas uno) y el número; la vinculación se completa sola. Eliges el agente que responde y puedes cambiarlo después desde la lista.

Dos formas de usar el número: dedicado al agente (el número ya no se usa desde la aplicación del teléfono), o mantener WhatsApp Business en el teléfono, donde responde el agente y tú sigues usando la aplicación. La segunda requiere la aplicación WhatsApp Business actualizada y un número en uso real en ella desde hace al menos una semana; si no, Meta lo rechaza.

Cada mensaje entrante va al agente junto con los últimos 20 intercambios de esa conversación, y la respuesta vuelve por WhatsApp; los mensajes de voz se transcriben. Cada intercambio aparece en los Logs. El agente responde solo mientras la prueba o un plan están activos y el wallet tiene crédito; los mensajes no consumen crédito. Meta factura las conversaciones a sus propias tarifas y exige un método de pago en tu cuenta empresarial antes de que el agente pueda responder.

Desvincula desde la misma página: el número se libera de la Cloud API y el historial de conversaciones del portal se elimina.

B. Tu propia aplicación de Meta y tu webhook

Si ya usas la WhatsApp Cloud API con tu propia aplicación de Meta, mantenla. Tu webhook recibe el mensaje, lo envía al agente con la llamada de chat (user es el número del remitente, name el nombre del perfil, history los últimos intercambios, que guardas tú: la llamada no tiene estado) y envía la respuesta con la Graph API. El agente es el del cliente WebRTC que indicas; su clave secreta se queda en tu servidor.

// tu backend: webhook de Meta → agente SwissAI → Graph API
app.post('/webhook', express.json(), async (req, res) => {
  res.sendStatus(200);  // responde a Meta de inmediato y luego trabaja
  for (const entry of req.body.entry || [])
    for (const change of entry.changes || []) {
      const v = change.value, m = (v.messages || [])[0];
      if (!m || m.type !== 'text') continue;
      const name = ((v.contacts || [])[0]?.profile || {}).name || '';
      const r = await fetch('https://developers.swissai.dev/api/messages', {
        method: 'POST',
        headers: { 'X-Api-Key': process.env.SWISSAI_CLIENT_KEY, 'Content-Type': 'application/json' },
        body: JSON.stringify({
          client: 'wc_3f9a1c2b7d4e',
          user: '+' + m.from,
          name: name,
          message: m.text.body,
          history: lastExchanges(m.from)  // los últimos intercambios que guardaste, del más antiguo al más reciente
        })
      }).then(r => r.json());
      if (!r.reply) continue;
      // la respuesta vuelve con la Graph API, desde tu número
      await fetch('https://graph.facebook.com/v25.0/' + v.metadata.phone_number_id + '/messages', {
        method: 'POST',
        headers: { Authorization: 'Bearer ' + process.env.WA_TOKEN, 'Content-Type': 'application/json' },
        body: JSON.stringify({ messaging_product: 'whatsapp', to: m.from, type: 'text', text: { body: r.reply } })
      });
    }
});

La verificación del webhook (GET con hub.verify_token y hub.challenge), la comprobación de la firma de cada evento (X-Hub-Signature-256) y la suscripción al campo messages son las de la WhatsApp Cloud API: consulta la documentación de Meta. Guarda los últimos intercambios por remitente para enviarlos en history: el agente solo sabe lo que le envías.

Las dos vías usan el mismo agente, con su scope y sus herramientas: elige A si no quieres gestionar un webhook, B si WhatsApp ya forma parte de tu plataforma.

Facturación

Cada cuenta empieza con una prueba gratuita de 30 días que incluye 100 minutos; no hace falta tarjeta. Al terminar la prueba eliges un plan en Facturación. La cuota mensual se carga en el wallet como crédito del plan y cada llamada se descuenta por segundo, a la tarifa por minuto del plan. El crédito del plan se pone a cero en cada renovación. Cuando se agota puedes recargar el wallet: el crédito recargado no caduca y se usa después del crédito del plan. Los números emitidos a petición se cobran del wallet cada mes. Los agentes responden solo con la prueba o un plan en curso y crédito en el wallet.