API Appels
==========

Déclenchez des appels sortants, récupérez des enregistrements d'appels, et pollez les statuts d'appels, tout par programme.

Déclencher un appel sortant
---------------------------

Bientôt disponibleLe déclenchement d'appels sortants n'est pas encore disponible de manière générale. L'endpoint ci-dessous décrit la requête et la réponse prévues ; il peut évoluer avant le lancement. La lecture des enregistrements et du statut des appels (plus bas) est disponible dès maintenant.

Crée un appel sortant et le met immédiatement en file pour composition. L'agent spécifié doit disposer d'une **version en ligne** pour traiter l'appel. Le caller ID (`from_number`) est optionnel, si omis, le numéro par défaut du trunk est utilisé. Utilisez un numéro appartenant à votre organisation comme caller ID.

```
POST /calls
```

**Auth :** session du dashboard

Cet endpoint utilise l'**authentification de session** (cookies + token CSRF du dashboard), et non les clés API `la_`. Les clés API ne fonctionnent que sur les endpoints en lecture seule `/v1`.

**Corps de requête**

```
{
"agent_id": "agt_01hx3k9z...",
"to_number": "+33612345678",
"from_number": "+33988292988",
"sip_headers": {
"X-Campaign-Id": "camp_01hx..."
}
}
```

ChampRequisTypeDescription   `agent_id`OuistringID de l'agent à utiliser, il doit disposer d'une version en ligne pour traiter l'appel `to_number`OuistringNuméro de destination au format E.164 (ex : `+33612345678`) `from_number`Nonstring | nullCaller ID affiché au destinataire, utilisez un numéro de votre organisation. Omettre pour utiliser le numéro par défaut du trunk. `sip_headers`Nonobject | nullEn-têtes SIP personnalisés transmis au porteur. Les clés sont les noms d'en-têtes, les valeurs des strings. **Réponse, 201 Created**

```
{
"id": "cal_01hx9mzp...",
"agent_id": "agt_01hx3k9z...",
"call_sid": "pending-01hx9n...",
"direction": "OUTBOUND",
"status": "QUEUED",
"from_number": "+33988292988",
"to_number": "+33612345678",
"queued_at": "2026-05-26T14:30:00.000000Z",
"organization_id": "org_01hx..."
}
```

Après création, l'appel passe à `RINGING` en quelques centaines de millisecondes pendant l'établissement de la branche SIP. Pollez `GET /calls/{id}/status` pour suivre la progression.

**Erreur, 429 Limite concurrente atteinte**

```
{
"error": "Concurrent call limit reached",
"active_calls": 10,
"max_concurrent_calls": 10
}
```

Poller le statut d'un appel
---------------------------

Endpoint léger pour le polling. Retourne uniquement `status` et `duration`, utilisez-le dans une boucle de polling jusqu'à ce que l'appel atteigne un état terminal.

```
GET /calls/{call_id}/status
```

**Auth :** session du dashboard

Les endpoints à authentification de session de cette section vivent sous `/api/...` (authentification par cookie), et **non** sous `/api/v1/...` (qui est l'API à token Bearer `la_`). Par exemple, l'URL complète est `https://www.manivox.ai/api/calls/{id}/status`.

```
{
"status": "IN_PROGRESS",
"duration": null
}

// Une fois terminé :
{
"status": "COMPLETED",
"duration": 142
}
```

**États terminaux**, arrêtez le polling quand vous atteignez l'un d'eux :

StatutSignification   `COMPLETED`L'appel s'est terminé normalement `FAILED`Erreur SIP ou crash d'agent `NO_ANSWER`L'appel sortant n'a pas été décroché `BUSY`Ligne de destination occupée `CANCELED`Appel annulé avant la prise `GET /calls/{id}/status` utilise l'authentification de session (même que le dashboard). Pour une notification serveur-à-serveur en fin d'appel, configurez une **Action de fin d'appel** sur l'agent, la plateforme émet une requête HTTP vers votre endpoint après chaque appel terminé. Voir [Actions de fin d'appel](https://www.manivox.ai/docs/api/call-actions#end-flow-actions).

**Exemple de polling (Python, contexte session navigateur)**

```
import time, requests

BASE = "https://www.manivox.ai"
# Utilisez les cookies de session obtenus depuis le dashboard
COOKIES = {"laravel_session": "YOUR_SESSION_COOKIE"}
HEADERS = {"X-XSRF-TOKEN": "YOUR_XSRF_TOKEN"}

call_id = "cal_01hx9mzp..."
TERMINAL = {"COMPLETED", "FAILED", "NO_ANSWER", "BUSY", "CANCELED"}

while True:
r = requests.get(f"{BASE}/api/calls/{call_id}/status", cookies=COOKIES, headers=HEADERS)
data = r.json()
print(f"Status: {data['status']}")
if data["status"] in TERMINAL:
break
time.sleep(2)

print(f"Appel terminé : {data['status']} ({data['duration']}s)")
```

Récupérer les détails d'un appel
--------------------------------

Retourne l'enregistrement de l'appel, incluant les variables collectées et l'historique des appels d'outils. La transcription tour par tour est disponible dans la vue détaillée de l'appel dans le dashboard.

```
GET /calls/{call_id}
```

**Auth :** session du dashboard

```
{
"id": "cal_01hx9mzp...",
"call_sid": "lk-room-01hx...",
"agent_id": "agt_01hx3k9z...",
"direction": "OUTBOUND",
"status": "COMPLETED",
"from_number": "+33988292988",
"to_number": "+33612345678",
"duration": 142,
"queued_at": "2026-05-26T14:30:00.000000Z",
"ringing_at": "2026-05-26T14:30:00.350000Z",
"answered_at": "2026-05-26T14:30:05.120000Z",
"ended_at": "2026-05-26T14:32:27.890000Z",
"recording_url": "/storage/recordings/cal_01hx9mzp.wav",
"call_variables": {
"caller_name": "Marie Dupont",
"account_number": "ACC-1234",
"intent": "balance_inquiry"
},
"tool_calls": [
{
"action": "get_balance",
"web_service": "CRM API",
"params": { "account_id": "ACC-1234" },
"response": { "balance": 1250.50, "currency": "EUR" },
"duration_ms": 312,
"ts": 18.4
}
],
"agent": {
"id": "agt_01hx3k9z...",
"name": "Customer Support FR"
}
}
```

Lister les appels
-----------------

Retourne les appels paginés pour un compte donné, du plus récent au plus ancien. Plage de dates maximale : 30 jours.

```
GET /v1/accounts/{account_id}/calls
```

**Auth :** clé API (Bearer `la_…`)

ParamètreTypeDéfautDescription   `limit`integer100Nombre max d'enregistrements (max 100) `offset`integer0Enregistrements à sauter `agent_id`string-Filtrer par un agent spécifique `from`ISO 8601-Début de la plage de dates (ex : `2026-05-01T00:00:00`) `to`ISO 8601-Fin de la plage de dates (max 30 jours après `from`) ```
# Appels d'un agent spécifique en mai 2026
curl "https://www.manivox.ai/api/v1/accounts/acc_01hx.../calls?agent_id=agt_01hx...&from=2026-05-01T00:00:00&to=2026-05-31T23:59:59" \
-H "Authorization: Bearer la_YOUR_API_KEY"
```

**Réponse, 200 OK**

```
{
"data": [
{
"call_id": "cal_01hx9mzp...",
"agent_id": "agt_01hx3k9z...",
"direction": "OUTBOUND",
"status": "COMPLETED",
"from_number": "+33988292988",
"to_number": "+33612345678",
"duration": 142,
"recording_available": true,
"answered_at": "2026-05-26T14:30:05+00:00",
"ended_at": "2026-05-26T14:32:27+00:00",
"created_at": "2026-05-26T14:30:00+00:00"
}
],
"total": 247
}
```

ChampTypeDescription   `call_id`stringIdentifiant unique de l'appel (CUID2) `agent_id`stringID de l'agent ayant traité l'appel `direction`enum`INBOUND` ou `OUTBOUND` `status`enumStatut de l'appel (voir le tableau de statuts ci-dessus) `from_number`string | nullNuméro de l'appelant (E.164) `to_number`stringNuméro appelé (E.164) `duration`integer | nullDurée en secondes (null jusqu'à la fin de l'appel) `recording_available`booleanIndique si un enregistrement a été effectué pour l'appel. Récupérez-le via [l'endpoint d'enregistrement](#download-recording), qui renvoie `404` si l'audio a depuis été supprimé par la politique de rétention `answered_at`ISO 8601 | nullQuand l'appel a été décroché `ended_at`ISO 8601 | nullQuand l'appel s'est terminé `created_at`ISO 8601Quand l'enregistrement d'appel a été créé Récupérer le résumé & les variables (handoff)
-------------------------------------------------

Récupère le **résumé** généré d'un appel et les **variables collectées** pendant l'appel, identifié par son call id. Son cas d'usage principal est le **screen-pop CRM lors d'un transfert vers un humain** : quand un agent IA transfère un appel à un humain et que le CRM destinataire a besoin de la conversation jusqu'ici. Mais il fonctionne pour n'importe quel appel de votre organisation, y compris les appels passés déjà terminés.

```
GET /v1/calls/{call_id}/handoff
```

**Auth :** clé API (Bearer `la_…`)

Lors d'un transfert, Manivox place un en-tête SIP `X-Call-Id` portant le call id sur l'`INVITE` sortant. Lisez-le depuis l'appel entrant dans votre CRM pour savoir quel call id récupérer.

Le résumé est généré de manière asynchrone. S'il n'est pas prêt au moment de l'appel, la requête **bloque jusqu'à ~5 secondes** (polling toutes les ~300 ms) en l'attendant, puis retourne `status: "pending"`. Les appels terminés et passés retournent `status: "ready"` immédiatement. Lors d'un transfert, le téléphone de l'humain sonne généralement plus longtemps que la génération ne prend, donc le résumé est presque toujours `ready` au moment où vous le demandez ; si vous obtenez tout de même `pending`, réessayez après ~1 seconde (les variables sont déjà présentes).

**Réponse, 200 OK**

```
{
"call_id": "cal_01hx9mzp...",
"status": "ready",
"summary": {
"summary": "The caller wants to book a table for 4 on Friday at 8pm.",
"key_topics": ["reservation", "friday dinner"],
"action_items": ["Confirm availability for Friday 8pm"],
"sentiment": "positive"
},
"variables": {
"party_size": "4",
"customer_name": "Dupont",
"preferred_time": "20:00"
}
}
```

ChampTypeDescription   `call_id`stringRépète le call id demandé `status`enum`ready` quand le résumé est disponible, `pending` s'il n'était pas prêt dans la fenêtre d'attente `summary`object | nullObjet de résumé structuré (pas une simple chaîne). `null` tant que `status` vaut `pending`. Champs : `summary` (texte), `key_topics` (string\[\]), `action_items` (string\[\]), `sentiment` (`positive` / `neutral` / `negative`) `variables`objectDictionnaire clé/valeur des variables collectées pendant l'appel (définies par workflow). Toujours un objet, `{}` si aucune **Erreur, 404 Not Found**, l'appel n'existe pas pour l'organisation de cette clé API :

```
{ "error": "Call not found" }
```

Un appel peut être transféré plusieurs fois (par exemple, un premier transfert échoue et l'agent continue de parler). Chaque transfert **réutilise le même `call_id`** (le même enregistrement d'appel) et écrase l'instantané avec les variables les plus récentes et un résumé fraîchement régénéré : cet endpoint retourne donc toujours l'état le **plus récent**.

La charge utile est volontairement limitée au **résumé et aux variables**. Elle n'inclut ni la transcription complète ni l'enregistrement — récupérez-les séparément via [Récupérer la transcription](#transcript) et [Télécharger un enregistrement](#download-recording).

Outre le `404` ci-dessus, cet endpoint retourne les erreurs d'authentification et de limite de débit communes à toute l'API : `401` / `403` pour une clé absente, mal formée, invalide, inactive, expirée ou désactivée pour l'organisation, et `429` au-delà de la limite de **120 requêtes/minute par organisation**. Voir [Référence API → Codes de statut HTTP](https://www.manivox.ai/docs/api/index#http-codes).

Récupérer la transcription
--------------------------

Retourne les tours utilisateur/assistant ordonnés d'un appel, identifié par son call id. Fonctionne **en cours d'appel** : la transcription reflète tout ce qui a été dit jusqu'ici et `status` indique que l'appel est toujours en cours, ainsi qu'après la fin de l'appel pour l'enregistrement final. Un cas d'usage courant est un système receveur qui récupère la conversation jusqu'ici juste après un transfert vers un humain, mais cela fonctionne pour n'importe quel appel de votre organisation.

```
GET /v1/calls/{call_id}/transcript
```

**Auth :** clé API (Bearer `la_…`)

ParamètreTypeDéfautDescription   `with_tools`boolean`false`Quand `true`, chaque tour assistant en ayant déclenché un porte aussi ses appels d'outils/API (`tool_calls`) et toute demande d'outil supprimée (`suppressed_tools`), le même niveau de détail que la vue de détail d'appel du dashboard, avec la même rédaction côté serveur, jamais plus que ce que l'UI révèle. ```
curl "https://www.manivox.ai/api/v1/calls/cal_01hx9mzp.../transcript?with_tools=true" \
-H "Authorization: Bearer la_YOUR_API_KEY"
```

**Réponse, 200 OK**

```
{
"call_id": "cal_01hx9mzp...",
"status": "IN_PROGRESS",
"turn_count": 2,
"with_tools": true,
"transcript": [
{
"role": "assistant",
"content": "Un instant, je vérifie.",
"timestamp": "2026-07-05T09:00:00Z",
"tool_calls": [
{
"name": "create_case",
"method": "POST",
"url": "https://crm.example.com/cases",
"status": 200,
"request": { "phone": "+33612345678" },
"response": { "id": 42 },
"duration_ms": 142,
"error": null
}
]
},
{ "role": "user", "content": "Merci.", "timestamp": "2026-07-05T09:00:10Z" }
]
}
```

ChampTypeDescription   `call_id`stringRépète le call id demandé `status`enumLe statut actuel de l'appel (voir le tableau du cycle de vie dans [Vue d'ensemble des appels](https://www.manivox.ai/docs/calls/index#lifecycle)), indique si la transcription ci-dessous est partielle (appel encore en cours) ou définitive `turn_count`integerNombre d'entrées dans `transcript` `with_tools`booleanRépète si `?with_tools=true` a été demandé `transcript`arrayTours ordonnés. Seuls les rôles `user` et `assistant` sont inclus, les lignes internes/système sont filtrées. Chaque entrée a `role`, `content`, `timestamp` et, uniquement avec `with_tools=true`, `tool_calls` / `suppressed_tools` en option **Champs d'une entrée `tool_calls`** (présents uniquement avec `?with_tools=true`, et uniquement sur les tours assistant en ayant déclenché un) :

ChampTypeDescription   `name`stringNom de l'action ou de l'outil `method`string | nullMéthode HTTP, pour les appels Web Service / Composio `url`string | nullURL de la requête, le cas échéant `status`integer | nullCode de statut HTTP retourné `request`object | nullCorps/paramètres de la requête (rédigé côté serveur, comme dans le dashboard) `response`object | nullCorps de la réponse (rédigé côté serveur, comme dans le dashboard) `duration_ms`integer | nullDurée de l'appel en millisecondes `error`string | nullMessage d'erreur si l'appel a échoué Un appel d'outil qui ne se termine qu'après le début du tour utilisateur suivant reste rattaché au tour assistant qui l'a déclenché (le tour assistant précédent le plus proche), comme le fait le dashboard. Les entrées `suppressed_tools` (`name`, `reason`) listent les appels d'outils que le modèle a demandés mais qui n'ont volontairement jamais été exécutés car la conversation était déjà passée au nœud de flux suivant.

**Erreur, 404 Not Found**, l'appel n'existe pas pour l'organisation de cette clé API :

```
{ "error": "Call not found" }
```

Outre le `404` ci-dessus, cet endpoint retourne les erreurs d'authentification et de limite de débit communes à toute l'API : `401` / `403` pour une clé absente, mal formée, invalide, inactive, expirée ou désactivée pour l'organisation, et `429` au-delà de la limite de **120 requêtes/minute par organisation**. Voir [Référence API → Codes de statut HTTP](https://www.manivox.ai/docs/api/index#http-codes).

Télécharger un enregistrement
-----------------------------

Télécharge l'enregistrement audio d'un appel sous forme de fichier WAV.

```
GET /v1/calls/{call_id}/recording
```

**Auth :** clé API (Bearer `la_…`)

Retourne l'audio brut avec ces en-têtes de réponse :

En-têteValeur   `Content-Type``audio/wav` `Content-Disposition``attachment; filename="recording-{call_id}.wav"` (téléchargement de fichier). Avec `?stream`, c'est `inline` à la place, pour une lecture dans le navigateur. `Accept-Ranges``bytes`, les requêtes HTTP `Range` sont supportées (seek / téléchargement partiel). L'endpoint d'enregistrement à authentification de session `GET /api/calls/{id}/recording` suit la convention **inverse** : il sert l'audio en `inline` par défaut et requiert `?download` pour forcer le téléchargement en pièce jointe, alors que cet endpoint V1 sert `attachment` par défaut et utilise `?stream` pour la lecture en ligne.

```
# Télécharger le WAV
curl "https://www.manivox.ai/api/v1/calls/cal_01hx.../recording" \
-H "Authorization: Bearer la_YOUR_API_KEY" \
-o recording.wav
```

**Erreurs, 404 Not Found**

```
// L'appel n'existe pas pour l'organisation de cette clé API
{ "error": "Call not found" }

// L'appel existe mais n'a pas d'enregistrement (ex : enregistrement non activé)
{ "error": "No recording available" }
```

Référence de l'objet appel
--------------------------

ChampTypeDescription   `id`stringIdentifiant unique de l'appel (CUID2) `call_sid`stringIdentifiant interne de room LiveKit `direction`enum`INBOUND` ou `OUTBOUND` `status`enumStatut courant de l'appel, voir le tableau de statuts ci-dessus `from_number`string | nullNuméro de l'appelant (E.164) `to_number`stringNuméro appelé (E.164) `duration`integer | nullDurée en secondes (null jusqu'à la fin de l'appel) `queued_at`ISO 8601Quand l'appel a été créé `ringing_at`ISO 8601 | nullQuand le SIP INVITE a été envoyé `answered_at`ISO 8601 | nullQuand l'appel a été décroché `ended_at`ISO 8601 | nullQuand l'appel s'est terminé `recording_url`string | nullChemin de stockage interne vers l'enregistrement WAV (ex : `/storage/recordings/…wav`), ce n'est pas une URL publique. Pour récupérer l'audio via l'API, utilisez [`GET /v1/calls/{id}/recording`](#download-recording). `call_variables`object | nullVariables collectées et résolues pendant l'appel `tool_calls`array | nullAppels API externes faits pendant l'appel avec paramètres, réponse et timing `sip_headers`object | nullEn-têtes SIP reçus sur les appels entrants