API Reporting
Récupérez vos données d'appels et vos statistiques dans vos propres outils : tableaux de bord BI, CRM, rapports planifiés.
Tous les endpoints de cette page sont en lecture seule. Ils relèvent du scope read — le même qui liste vos comptes et vos agents. Il n'existe pas de scope dédié au reporting : les scopes d'une clé API servent à contrôler ce qui peut coûter de l'argent, ce qu'un rapport ne fait pas.
Statistiques
Les chiffres affichés par la page Statistiques du tableau de bord, sous forme de données. Ils proviennent du même code : les deux ne peuvent pas diverger.
GET /v1/statistics
Auth: API key (read)
| Parameter | Type | Description |
|---|---|---|
period | string | today, 7d (default), 30d, month, year |
from, to | date | YYYY-MM-DD. Both required together; overrides period |
account_id | string | Limit to one account |
agent_id | string | Limit to one agent |
campaign_id | string | Limit to one campaign |
direction | string | INBOUND or OUTBOUND |
{
"data": {
"totals": { "calls": 412, "completed": 380, "completion_rate": 92, "avg_duration": 96, "active_now": 3 },
"deltas": { "calls": 12, "avg_duration": -4, "completion_rate": 1 },
"volume": [{ "bucket": "2026-09-08", "label": "8 Sep", "calls": 57 }],
"duration_histogram": [{ "label": "0-15s", "calls": 31 }],
"status_breakdown": { "COMPLETED": 380, "FAILED": 12 },
"direction_split": { "inbound": 300, "outbound": 112 },
"top_agents": [{ "agent_id": "agt_01hx...", "name": "Reception", "calls": 210, "avg_duration": 88, "completion_rate": 94 }],
"quality_trend": [{ "bucket": "2026-09-08", "score": 82, "calls": 41 }],
"sentiment_distribution": { "positive": 240, "neutral": 130, "negative": 42 }
},
"meta": { "from": "...", "to": "...", "timezone": "Europe/Paris", "bucket": "day" }
}
À propos des fuseaux horaires
Les intervalles sont découpés dans le fuseau horaire de votre organisation, pas en UTC — « les appels du mardi » désigne le mardi chez vous, exactement comme dans le tableau de bord. Le bloc meta indique le fuseau utilisé et la largeur de chaque intervalle.
Lister les appels
Tous les appels de votre organisation, du plus récent au plus ancien, tous comptes confondus. Filtrez, puis paginez avec un curseur.
GET /v1/calls
Auth: API key (read)
| Parameter | Type | Description |
|---|---|---|
from, to | datetime | ISO 8601. At most 92 days apart |
account_id, agent_id, campaign_id | string | Narrow to one of each |
direction | string | INBOUND or OUTBOUND |
status | string | e.g. COMPLETED, FAILED |
disposition | string | e.g. NO_ANSWER, BUSY |
limit | int | 1-500, default 100 |
after | string | Cursor: the next_cursor of the previous page |
{
"data": [
{
"call_id": "cal_01hx...", "account_id": "acc_01hx...", "agent_id": "agt_01hx...",
"agent_version": 7, "campaign_id": null,
"direction": "INBOUND", "status": "COMPLETED", "disposition": null, "ended_by": "agent",
"from_number": "+33612345678", "to_number": "+33988292988",
"duration": 96, "quality_score": 84, "sentiment": "positive",
"recording_available": true,
"queued_at": "...", "answered_at": "...", "ended_at": "...", "created_at": "..."
}
],
"has_more": true,
"next_cursor": "cal_01hx..."
}
Pagination par curseur
Passez le next_cursor d'une réponse en paramètre after de la requête suivante, et arrêtez-vous quand has_more vaut false. Un curseur reste stable pendant que de nouveaux appels arrivent : contrairement à un offset, il ne vous montrera pas deux fois le même appel et n'en sautera pas parce qu'un appel est arrivé entre deux pages.
Un appel en détail
Ajoute le résumé, le résultat des objectifs, les variables collectées par l'agent, les informations de transfert et les coûts.
GET /v1/calls/{call_id}
Auth: API key (read)
{
"data": {
"call_id": "cal_01hx...",
"summary": "The caller asked to move their appointment to Thursday.",
"objectives": [{ "name": "appointment_booked", "result": "achieved", "value": true }],
"variables": { "customer_email": "marie@example.com" },
"transfer": { "destination": "********6789", "periods": [] },
"costs": { "call": 0.0142, "transfer": null, "termination": null, "llm": 0.0031, "total": 0.0173, "currency": "EUR" },
"links": { "transcript": "/api/v1/calls/cal_01hx.../transcript", "recording": "...", "events": "..." }
}
}
costs.termination est le coût opérateur du tronçon sortant d'un appel de campagne décroché (null sur les appels entrants). Il est facturé sur sa propre ligne et inclus dans costs.total : le total correspond donc à la facture.
Événements d'appel
La chronologie d'un appel : initiation, sonnerie, décroché, fin, ainsi que les étapes d'intégration exécutées par le workflow. Les entrées de diagnostic (latence des modèles, détails de la reconnaissance vocale) ne font pas partie de cet endpoint.
GET /v1/calls/{call_id}/events
Auth: API key (read)
{
"data": [
{ "seq": 1, "ts_ms": 0, "type": "call_initiated", "service": "call", "transcript_seq": null, "payload": {} },
{ "seq": 8, "ts_ms": 96120, "type": "call_ended", "service": "call", "transcript_seq": 14,
"payload": { "duration_ms": 96120, "endedBy": "agent" } }
]
}
payload ne contient que ces champs, quand l'événement les possède : duration_ms, endedBy, endReason, node_id, node_name, status et name. Un événement qui n'en a aucun revient avec un payload vide.
Résultats KPI (objectifs)
Les verdicts sur lesquels votre agent est évalué, appel par appel. Chaque objectif revient avec la valeur collectée, s'il a été atteint, et le raisonnement derrière ce verdict.
GET /v1/calls/{call_id}/objectives
Auth: API key (read)
{
"data": {
"call_id": "cal_01hx...",
"status": "evaluated",
"quality_score": 84,
"total": 2, "reached": 1, "evaluated": 2,
"items": [
{
"node_id": "n1", "node_label": "Booking",
"variable_name": "appointment_booked", "label": "Appointment booked",
"type": "boolean", "value": "true", "collected": true,
"result": "achieved",
"rationale": "The caller confirmed Thursday 10am."
}
]
}
}
The same block is on GET /v1/calls/{call_id} under objectives. This endpoint is the light one: it does not compute costs, so it is the one to poll.
List rows carry objectives_total and objectives_reached only — not the rows themselves, so a page of 500 calls stays a page of 500 calls.
Les objectifs arrivent après l'appel
Ils sont calculés par un job lancé une fois la transcription enregistrée : un appel récupéré dès le déclenchement de son webhook ne les aura pas encore. status vous indique dans quel cas vous êtes — et c'est le champ à interroger :
pending— configurés, pas encore calculés. Revenez.evaluated— un verdict existe. Arrêtez d'interroger.none— cet agent n'a pas d'objectifs, ou l'appel n'a jamais abouti. Rien n'arrivera.
unknown n'est pas un échec
result vaut achieved, not_achieved ou unknown. unknown signifie que l'évaluation n'a pas pu trancher : il ne compte donc ni comme atteint ni comme manqué. reached ne compte que les achieved, et evaluated compte achieved plus not_achieved. Divisez par evaluated, pas par total, sinon un objectif indécidable fait chuter votre taux comme s'il avait échoué.
Les valeurs de result restent ces identifiants anglais dans toutes les langues. Ils servent à votre code ; label et rationale sont les parties lisibles par un humain.
Ce sur quoi un agent est évalué
Les définitions des objectifs, pour construire un rapport avant même qu'un appel ne soit évalué — et pour distinguer une colonne absente d'une colonne vide. Les valeurs autorisées d'un objectif enum ne sont disponibles qu'ici.
GET /v1/accounts/{account_id}/agents/{agent_id}/objectives
Auth: API key (read)
{
"data": {
"agent_id": "agt_01hx...",
"objectives": [
{ "variable_name": "appointment_booked", "label": "Appointment booked",
"description": null, "type": "boolean", "enum_values": [],
"kind": "llm_judge", "node_id": null, "node_label": null },
{ "variable_name": "call_reason", "label": "Call reason",
"description": null, "type": "enum", "enum_values": ["sales", "support"],
"kind": "llm_judge", "node_id": "n1", "node_label": "Booking" }
]
}
}
Chaque ligne correspond à une ligne de résultat de GET /v1/calls/{call_id}/objectives par variable_name et node_id. node_id et node_label indiquent à quelle étape du workflow l'objectif est mesuré (tous deux null s'il couvre tout l'appel) ; la même variable définie à deux étapes donne deux lignes. kind indique le mode de mesure : llm_judge (un LLM lit la transcription), tool_called ou variable_captured (réponse exacte tirée des événements enregistrés de l'appel, sans jetons) ou sentiment. Pour un objectif défini sur une étape du workflow, variable_name est le variableName de l'étape.
KPI sur un ensemble d'appels
GET /v1/statistics renvoie un bloc kpis à côté des autres métriques, avec les mêmes filtres.
{
"data": {
"kpis": {
"calls": 412, "has_enough_calls": true,
"avg_quality": 81.4, "quality_n": 380, "quality_undersampled": false,
"objectives_total": 824, "objectives_evaluated": 790,
"objectives_reached": 611, "objectives_rate": 77.3,
"connect_rate": 92.1, "no_answer": 18, "busy": 4, "voicemail": 9
}
}
}
Vérifiez la taille de l'échantillon avant de citer un taux
Chaque moyenne est accompagnée du nombre d'appels qui la sous-tend. has_enough_calls est faux en dessous de 10 appels et quality_undersampled est vrai en dessous de 5 appels notés — un taux d'objectifs de 63 % sur trois appels n'est pas un résultat. Une valeur absente vaut null, jamais 0 : null signifie qu'il n'y a pas de réponse honnête, alors que 0 voudrait dire que la mesure a été faite et qu'elle est nulle.
Limites
- Une requête couvre au maximum 92 jours. Utilisez le curseur pour parcourir un historique plus long.
limitvaut 100 par défaut, avec un plafond à 500.- La limitation de débit est par organisation, comme sur tous les endpoints
/v1. - Les tests Agent Lab ne sont jamais inclus — ce ne sont pas des appels que vous avez passés.
Champs
La réponse contient exactement les champs listés ici. Nous pourrons en ajouter à l'avenir ; nous n'en supprimerons ni n'en détournerons aucun sans une nouvelle version de l'API. Écrivez donc votre intégration de façon à ignorer les champs qu'elle ne connaît pas.