Guide

SwissAI vous fournit des agents vocaux qui répondent aux appels téléphoniques. Vous les créez dans le tableau de bord avec un prompt, une langue et des outils optionnels ; chaque agent reçoit une URI SIP. Routez les appels vers cette URI : l'agent décroche, parle à l'appelant et appelle vos endpoints HTTP quand il a besoin de données ou doit agir.

Tout se configure depuis le tableau de bord : agents, outils, vos clients et la facturation.

Agents

Vous créez et modifiez les agents dans le tableau de bord : nom, langue, scope (le prompt), une base de connaissances facultative, les modèles et les outils. Chaque agent reçoit un identifiant et une URI SIP, et peut être attribué à l'un de vos clients.

Exemple de scope

Rédigez le scope comme vous donneriez ses consignes à un nouveau collègue : qui est l'agent, ce qu'il peut faire, ce qu'il ne doit jamais faire et comment l'appel se termine.

Tu es Anna, l'assistante du Cabinet dentaire Keller à Zurich.
Tu réponds au téléphone quand l'accueil est occupé ou fermé.

Ce que tu fais :
- prendre, déplacer ou annuler des rendez-vous, avec les outils ;
- répondre aux questions sur les horaires, l'adresse et les tarifs (voir la base de connaissances).

Règles :
- parle brièvement et poliment, une question à la fois ;
- avant de réserver, répète le jour, l'heure et le nom et attends un oui clair ;
- ne donne jamais de conseil médical : en cas de douleur ou d'urgence, propose le premier
  rendez-vous libre et donne le numéro des urgences ;
- si tu ne peux pas aider, prends le nom et le numéro de téléphone et dis que le cabinet rappellera.

Termine l'appel en répétant ce qui a été convenu.

Outils

Définition d'un outil

Un outil est un endpoint HTTP de votre côté, décrit au modèle par un schéma JSON. Le modèle décide quand l'appeler et avec quels arguments ; la plateforme exécute la requête HTTP.

{
  "name": "book_appointment",
  "description": "Réserve un rendez-vous. À appeler seulement après que l'appelant a confirmé le jour, l'heure et le nom.",
  "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": "Code du cabinet : toujours 'zurich'" },
      "start":  { "type": "string", "description": "Début, yyyy-MM-dd HH:mm, p. ex. 2026-10-06 10:30" },
      "name":   { "type": "string", "description": "Prénom et nom de l'appelant" },
      "phone":  { "type": "string", "description": "Numéro de téléphone de l'appelant, chiffres uniquement" }
    },
    "required": ["clinic", "start", "name", "phone"]
  }
}
ChampPourDescription
namemodèleNom de la fonction, lettres et tirets bas.
descriptionmodèleCe qu'elle fait et quand l'appeler. À écrire pour le modèle.
schemamodèleSchéma JSON des arguments.
response_schemamodèleOptionnel. Forme de votre réponse, pour que le modèle sache la lire.
urlruntimeVotre endpoint, en HTTPS.
methodruntimeGET (arguments dans la query string) ou POST (arguments en corps JSON).
content_typeruntimeGénéralement application/json.
headersruntimeOptionnel. En-têtes envoyés à chaque appel, ex. Authorization ; les valeurs peuvent contenir des {{paramètres}}.
bodyruntimeOptionnel. Modèle de body avec des {{paramètres}}. Vide : les paramètres absents de l'url et des en-têtes partent en query string (GET, DELETE) ou en body JSON.

Paramètres et placeholders

Écrivez {{nom}} dans l'url, les valeurs des en-têtes ou le body. Chaque placeholder doit être déclaré dans schema.properties avec une description : le modèle le remplit à partir de la conversation et le runtime le substitue (encodé dans l'url, échappé dans un body JSON). Le tableau de bord construit le schéma à partir des placeholders et fait vérifier l'outil par Claude avant l'enregistrement.

Comment l'agent vous appelle

Chaque requête porte l'en-tête token avec le tool_token de l'agent. Répondez 200 avec un corps JSON :

Votre endpoint : exemples

Voici la requête que la plateforme envoie pour l'outil ci-dessus, et un endpoint minimal qui y répond. Vérifiez d'abord l'en-tête token : c'est le tool token que vous avez défini sur l'agent.

Requête envoyée par la plateforme
POST /clinics/zurich/appointments HTTP/1.1
Host: api.example.ch
Authorization: Bearer sk_…
token: 5f1c…            # le tool token de l'agent
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. seul votre agent peut appeler
  if (req.get('token') !== process.env.SWISSAI_TOOL_TOKEN) return res.status(401).end();

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

  // 2. erreur métier : une phrase sur laquelle l'agent peut agir
  if (!(await isFree(req.params.clinic, start)))
    return res.json({ success: false, error: "Ce créneau est pris. Proposez 11:00 ou 14:30 le même jour." });

  // 3. succès : le message est ce que l'agent dit à l'appelant
  await book(req.params.clinic, start, name, phone);
  res.json({ success: true, message: "Rendez-vous réservé pour le " + 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' => "Ce créneau est pris. Proposez 11:00 ou 14:30 le même jour."]);
    exit;
}
book($in['start'], $in['name'], $in['phone']);
echo json_encode(['success' => true, 'message' => "Rendez-vous réservé pour le " . $in['start'] . '.']);

La section Outils

Les outils se trouvent dans une seule liste, la section Outils du tableau de bord, et les agents les utilisent par référence : vous modifiez un outil une fois et chaque agent qui l'utilise est mis à jour. Un outil attribué à un client ne peut être utilisé que dans les agents de ce client ; un outil sans client peut être utilisé dans tous vos agents. Dans l'éditeur de l'agent, vous choisissez les outils dans la liste ou vous en créez un nouveau, qui y est ajouté.

Sandbox : outils prêts à l'emploi et code d'exemple

La sandbox est un ensemble d'endpoints d'exemple en service, avec des données d'exemple séparées pour chaque compte : agenda, tables de restaurant, stock, rappels, contacts, commandes. Ses 22 outils sont déjà dans la section Outils : ajoutez-les à un agent et essayez-le en quelques minutes, sans rien héberger. Chaque outil a un guide avec ses paramètres, un exemple complet de requête et de réponse et le code de l'endpoint qui y répond, que vous pouvez télécharger et utiliser comme point de départ pour le vôtre.

Télécharger tout le code (zip)

Chaque compte a son propre environnement de test avec les mêmes données de départ : trois prestations réservables avec un rendez-vous déjà pris, huit tables de restaurant avec une réservation, six produits, deux contacts et quatre commandes. Il est créé automatiquement la première fois que vous ajoutez un outil de la sandbox à un agent, ou depuis la section Outils, où vous pouvez aussi le ramener aux données de départ. Un environnement inutilisé pendant 30 jours est supprimé et peut être recréé.

Il suffit d'ajouter un outil de la sandbox à un agent : le tableau de bord complète l'adresse avec la clé de votre environnement. Pour appeler les endpoints vous-même, envoyez la clé dans l'en-tête Authorization: Bearer ws_… (dans le tableau de bord, le guide de chaque outil montre la commande avec votre clé). Les appels sont des GET, avec les arguments dans la query string, ou des POST, avec les arguments en JSON.

Chaque réponse a le statut 200 et un corps JSON : success true avec un message à dire à l'appelant et les données, ou success false avec une erreur rédigée comme une phrase pour l'agent, par exemple les horaires libres à proposer à la place. Les jours sont au format yyyy-MM-dd, les horaires HH:mm, les numéros de téléphone en chiffres uniquement. Ci-dessous, chaque domaine avec ce qu'il est, où il s'utilise et tous ses appels, chacun avec un exemple de requête et la vraie réponse.

Clients

Un client est l'un de vos clients finaux. Attribuez-lui des agents dans l'éditeur de l'agent ; il reçoit une invitation par e-mail et peut se connecter (mot de passe, Google ou Apple) pour voir ses agents, ses appels et sa consommation mensuelle au tarif que vous fixez. Le client vous paie : SwissAI n'affiche que les chiffres.

Inviter, modifier, retirer

La création d'un client dans le tableau de bord envoie l'invitation. Le retirer vous rend ses agents et révoque son accès.

Attribuer un agent

Dans l'éditeur de l'agent, choisissez le client qui voit ses appels ; Usage personnel garde l'agent pour vous seul.

Téléphonie

Un agent répond aux appels qui atteignent son URI SIP sur notre central. Il y a trois façons d'y amener un appel, toutes configurables depuis le menu Téléphonie du tableau de bord : un PBX externe autorisé par adresse IP, un numéro et un client WebRTC sur un site web. Depuis le tableau de bord, vous pouvez aussi appeler n'importe quel agent depuis le navigateur pour le tester ; les appels de test comptent comme des minutes.

URI SIP et PBX externes

Chaque agent a une URI SIP, visible dans la liste des agents, du type sip:ag_7f3a2b91@pbx.swissai.dev. Votre PBX envoie l'appel à cette adresse : la partie avant le @ choisit l'agent.

Les appels ne sont acceptés que depuis les PBX que vous avez déclarés. Dans Téléphonie › PBX externes, ajoutez le PBX avec un nom, le client auquel il appartient (ou Usage personnel) et l'adresse IP d'où proviennent ses appels. Il n'y a ni nom d'utilisateur ni mot de passe : le PBX est reconnu à son adresse IP et peut appeler tous les agents de ce client.

Asterisk (chan_sip)
; extensions.conf : le poste 200 appelle l'agent
exten => 200,1,Dial(SIP/ag_7f3a2b91@pbx.swissai.dev,60)
 same => n,Hangup()
Asterisk (PJSIP)
; pjsip.conf : le standard SwissAI comme endpoint sortant, sans authentification
[swissai]
type=endpoint
context=from-swissai
disallow=all
allow=alaw,ulaw
aors=swissai

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

; extensions.conf : le poste 200 appelle l'agent
exten => 200,1,Dial(PJSIP/ag_7f3a2b91@swissai,60)
 same => n,Hangup()
FreeSWITCH
<!-- dialplan : le poste 200 appelle l'agent -->
<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, dans la request route : les appels vers 200 vont à l'agent
if ($rU == "200") {
    $ru = "sip:ag_7f3a2b91@pbx.swissai.dev:5060";
    t_relay();
    exit;
}

Les appels provenant d'une adresse non déclarée sont refusés, et une adresse qui insiste est bloquée pendant un moment. Si votre PBX change d'adresse IP, mettez-la à jour dans le tableau de bord avant d'envoyer des appels.

Numéros

Un numéro amène les appels téléphoniques ordinaires à un agent. Dans Téléphonie › Numéros, chaque numéro affiche le compte SIP utilisé pour l'enregistrer (nom d'utilisateur, mot de passe, hôte, port) et l'agent qui répond.

Clients WebRTC (click to call)

Un client WebRTC est un bouton d'appel sur un site web : le visiteur parle à l'agent depuis le navigateur, sans téléphone. Créez-le dans Téléphonie › Clients WebRTC : choisissez l'agent et listez les domaines où le bouton est utilisé (ajoutez localhost pour tester sur votre ordinateur). Vous recevez le nom du client (wc_…) et une clé secrète (wk_…), affichée une seule fois.

La clé secrète ne doit jamais arriver dans le navigateur. La page interroge votre backend, votre backend nous demande un mot de passe temporaire avec la clé, et le transmet à la page. Le mot de passe temporaire est valable 60 secondes et pour un seul appel.

1Votre backend demande le mot de passe temporaire
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
}
ChampDescription
clientObligatoire. Le nom du client affiché dans le tableau de bord.
identityOptionnel. Votre référence pour le visiteur, par exemple un identifiant d'utilisateur.
401Client inconnu ou clé incorrecte.
503Les appels depuis le navigateur ne sont pas disponibles pour ce client, par exemple parce que son agent a été supprimé.
Node.js (Express)
// votre backend : la clé secrète reste ici
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 clé secrète reste sur votre 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 page lance l'appel

Chargez notre script et passez-lui la réponse telle quelle. onState reçoit connecting, calling, connected et idle ; onError reçoit la raison quand l'appel ne peut pas démarrer.

<button id="call">Appelez-nous</button>
<button id="hangup" hidden>Raccrocher</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();   // votre backend, étape 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 : les appels et ce qu'ils coûtent à votre client

Avec le nom et la clé secrète d'un client WebRTC, votre backend peut lire les appels du client auquel ce client WebRTC appartient (tous les agents de ce client) et ce que chaque appel lui coûte selon le tarif défini dans Clients.

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 }
}
ChampDescription
clientObligatoire. Le nom du client affiché dans le tableau de bord.
monthOptionnel. yyyy-MM ; le mois en cours s'il est absent. Jusqu'à 500 appels, du plus récent au plus ancien.
costLa durée de l'appel au prix à la minute du client, avant les minutes incluses.
totalLe mois tel que le client le voit dans son portail : minutes incluses déduites, secondes arrondies à la minute sur le total du mois.
customernull quand l'agent du client WebRTC est à usage personnel : ce sont les appels de vos agents sans client, au prix de la plateforme.

Vous pouvez essayer les deux appels, avec le code à copier, depuis la Zone de test du tableau de bord.

Chat : messages texte à l'agent

Le même agent qui répond au téléphone peut répondre par écrit : sur votre site, dans votre application ou sur un canal de messagerie que vous gérez. Votre backend envoie ce que l'utilisateur a écrit et reçoit la réponse sous forme de texte ; en répondant, l'agent peut appeler ses outils comme au téléphone.

L'appel se fait avec le nom et la clé secrète d'un client WebRTC, qui désignent l'agent, et doit partir de votre backend : la clé ne doit jamais arriver dans le navigateur. Il ne conserve aucun état : renvoyez à chaque fois les derniers échanges dans 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": "Jeudi à 10 h est-il encore libre ?",
    "history": [
      { "direction": "in",  "text": "Bonjour, je voudrais réserver un contrôle." },
      { "direction": "out", "text": "Bien sûr. Quel jour vous conviendrait ?" }
    ]
  }'
// 200
{ "reply": "Oui, jeudi à 10 h 00 est libre. Je vous le réserve ?" }
ChampDescription
clientObligatoire. Le nom du client affiché dans le tableau de bord.
messageObligatoire. Le texte écrit par l'utilisateur, jusqu'à 4000 caractères.
userOptionnel. Votre référence pour la personne qui écrit, par exemple un identifiant ou un numéro de téléphone.
nameOptionnel. Le nom de la personne qui écrit, si vous le connaissez.
historyOptionnel. Les derniers échanges, du plus ancien au plus récent, jusqu'à 20 : direction vaut in pour l'utilisateur et out pour l'agent, text est le message.
replyLa réponse de l'agent à afficher à l'utilisateur ; vide quand l'agent n'a rien à dire.
401Client inconnu ou clé incorrecte.
402L'essai ou l'abonnement n'est pas actif, ou le wallet n'a pas de crédit.
502L'agent n'a pas pu répondre : réessayez.

Vous pouvez discuter avec n'importe quel agent depuis Téléphonie › Chat, et essayer cet appel, avec le code à copier, depuis la Zone de test.

WhatsApp Business

Un agent peut répondre sur WhatsApp de deux façons. Soit vous reliez le numéro depuis le tableau de bord et SwissAI reçoit les messages et répond, soit vous gardez votre propre application Meta et votre webhook sur votre site et envoyez chaque message à l'agent avec l'appel de chat ci-dessus.

A. Relier le numéro depuis le tableau de bord

Téléphonie › WhatsApp › Relier avec WhatsApp. Une fenêtre Meta s'ouvre : vous vous connectez avec le compte Facebook de votre entreprise, choisissez le portefeuille d'entreprise (ou en créez un) et le numéro ; la liaison se termine toute seule. Vous choisissez l'agent qui répond et pouvez le changer ensuite depuis la liste.

Deux façons d'utiliser le numéro : dédié à l'agent (le numéro n'est plus utilisé depuis l'application du téléphone), ou garder WhatsApp Business sur le téléphone, où l'agent répond et vous continuez à utiliser l'application. La seconde demande l'application WhatsApp Business à jour et un numéro réellement utilisé dessus depuis au moins une semaine, sinon Meta le refuse.

Chaque message entrant va à l'agent avec les 20 derniers échanges de cette conversation, et la réponse repart sur WhatsApp ; les messages vocaux sont transcrits. Chaque échange apparaît dans les Logs. L'agent ne répond que pendant l'essai ou un abonnement actif, avec du crédit dans le wallet ; les messages ne consomment pas de crédit. Meta facture les conversations à ses propres tarifs et exige un moyen de paiement sur votre compte d'entreprise avant que l'agent puisse répondre.

Détachez depuis la même page : le numéro est libéré de la Cloud API et l'historique des conversations sur le portail est supprimé.

B. Votre propre application Meta et votre webhook

Si vous utilisez déjà la WhatsApp Cloud API avec votre propre application Meta, gardez-la. Votre webhook reçoit le message, l'envoie à l'agent avec l'appel de chat (user est le numéro de l'expéditeur, name le nom du profil, history les derniers échanges, que vous conservez vous-même : l'appel n'a pas d'état) et envoie la réponse avec la Graph API. L'agent est celui du client WebRTC que vous indiquez ; sa clé secrète reste sur votre serveur.

// votre backend : webhook Meta → agent SwissAI → Graph API
app.post('/webhook', express.json(), async (req, res) => {
  res.sendStatus(200);  // répondre tout de suite à Meta, puis travailler
  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)  // les derniers échanges que vous avez conservés, du plus ancien au plus récent
        })
      }).then(r => r.json());
      if (!r.reply) continue;
      // la réponse repart avec la Graph API, depuis votre numéro
      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 vérification du webhook (GET avec hub.verify_token et hub.challenge), le contrôle de la signature de chaque événement (X-Hub-Signature-256) et l'abonnement au champ messages sont ceux de la WhatsApp Cloud API : voir la documentation de Meta. Conservez les derniers échanges par expéditeur pour les envoyer dans history : l'agent ne sait que ce que vous lui envoyez.

Les deux voies utilisent le même agent, avec son scope et ses outils : choisissez A si vous ne voulez pas gérer de webhook, B si WhatsApp fait déjà partie de votre plateforme.

Facturation

Chaque compte démarre avec un essai gratuit de 30 jours qui inclut 100 minutes ; aucune carte n'est nécessaire. Après l'essai, vous choisissez un forfait dans Facturation. L'abonnement mensuel est chargé dans le wallet comme crédit du forfait et chaque appel en est déduit à la seconde, au tarif à la minute du forfait. Le crédit du forfait est remis à zéro à chaque renouvellement. Quand il est épuisé, vous pouvez recharger le wallet : le crédit rechargé n'expire pas et s'utilise après celui du forfait. Les numéros attribués sur demande sont débités du wallet chaque mois. Les agents répondent uniquement pendant l'essai ou un forfait en cours, avec du crédit dans le wallet.