API Campagnes
Créez des campagnes d'appels sortants, alimentez-les en contacts en continu, lancez-les et mettez-les en pause, lisez leur progression contact par contact, et tenez à jour les listes d'exclusion et d'opposition, tout cela depuis vos propres systèmes. La vue opérateur de la même fonctionnalité se trouve sur la page Campagnes.
Authentification et chemin de base
Les opérations de cette page sont disponibles de deux façons, au comportement identique :
- Avec une clé API, sous
/api/v1/campaigns…— Bearerla_…, la clé devant disposer de la permissioncampaigns:writepour créer, modifier ou piloter une campagne (readsuffit pour lister et consulter). Les écritures acceptent un en-têteIdempotency-Keyafin qu'une requête réessayée ne démarre pas deux fois la campagne. C'est ce qu'il faut utiliser depuis vos propres systèmes ; voir Authentification. - Avec l'authentification de session, sous
/api/campaigns…— cookies + token CSRF du dashboard, rôle User ou Admin requis. C'est ce que le dashboard lui-même utilise.
Les chemins et payloads ci-dessous sont écrits avec le préfixe /api ; pour la version par clé API, ajoutez /v1. Deux différences à connaître : la version par clé API de Mettre à jour une campagne n'accepte que PATCH, là où la route de session accepte PUT et PATCH ; et la version par clé API renvoie la campagne dans sa forme stable et documentée (voir l'objet campagne) plutôt que l'enregistrement complet. Dans les deux cas, chaque objet est limité à votre organisation : un id de campagne, de contact ou d'agent d'une autre organisation renvoie 404.
L'objet campagne
{
"id": "camp_01hx...",
"name": "Spring renewal outreach",
"description": null,
"agent_id": "agt_01hx3k9z...",
"caller_id": "+33988292988",
"prefix": null,
"status": "ARMED",
"paused_reason": null,
"scheduled_at": null,
"started_at": "2026-06-02T08:00:12.000000Z",
"completed_at": null,
"canceled_at": null,
"rate_limit": 5,
"rate_interval": 60,
"max_parallel_calls": 3,
"ringing_timeout_seconds": 35,
"max_attempts_per_contact": 3,
"voicemail_policy": "SKIP",
"timezone": null,
"calling_windows": { "1": [[10, 13], [14, 20]], "2": [[10, 13], [14, 20]], "3": [[10, 13], [14, 20]], "4": [[10, 13], [14, 20]], "5": [[10, 13], [14, 20]] },
"retry_policy": null,
"csv_mapping": { "phone_column": "Phone", "variable_columns": { "First Name": "first_name" } },
"total_contacts": 950,
"processed_count": 210,
"success_count": 131,
"failed_count": 79,
"import_status": "DONE",
"import_report": { "valid": 950, "invalid": 12, "duplicate": 3, "do_not_call": 2, "excluded": 0, "invalid_reasons": { "not_a_number": 12 } },
"created_at": "2026-06-01T15:04:00.000000Z",
"updated_at": "2026-06-02T08:00:12.000000Z"
}
C'est l'enregistrement que renvoient les endpoints de session. Avec une clé API, la même campagne revient sans canceled_at, processed_count, success_count, failed_count, csv_mapping et import_report, et avec deux champs supplémentaires : agent_name (le nom du workflow) et callback_window_days (le nombre de jours pendant lesquels un contact qui rappelle est encore reconnu comme contact de cette campagne). Les compteurs et le rapport d'import sont disponibles via les endpoints de rapport et de contacts ci-dessous.
Statuts
| Statut | Signification |
|---|---|
IDLE | Ne compose pas. Un brouillon quand started_at est null, en pause sinon ; paused_reason vaut BY_OPERATOR, INSUFFICIENT_BALANCE, AGENT_UNAVAILABLE, NO_CALLER_ID ou null. |
ARMED | Activée. Le composeur la reprend chaque minute et compose tout ce qui est dû dans les limites de la cadence, des plages horaires d'appel et de la simultanéité. Une campagne armée dont le scheduled_at est dans le futur attend ; en dehors de ses plages horaires, elle saute ; tant que chaque contact attend l'écoulement d'un délai de nouvelle tentative, elle est silencieuse. Aucun de ces cas n'est un statut distinct. |
COMPLETED | Chaque contact a atteint un état final. Positionné par le composeur, jamais par cette API. Terminal. |
CANCELED | Arrêtée définitivement par un opérateur ou par POST /cancel. Terminal. |
Mettre en pause ou annuler ne raccroche jamais un appel déjà en cours. Les deux actions empêchent seulement le composeur de réclamer de nouveaux contacts. Un agent en pleine conversation continue de parler jusqu'à la fin naturelle de l'appel.
Créer une campagne
POST /api/campaigns
Auth : session du dashboard
{
"name": "Spring renewal outreach",
"agent_id": "agt_01hx3k9z...",
"caller_id": "+33988292988",
"rate_limit": 5,
"rate_interval": 60,
"max_parallel_calls": 3,
"ringing_timeout_seconds": 35,
"max_attempts_per_contact": 3,
"voicemail_policy": "SKIP",
"calling_windows": { "1": [[10, 13], [14, 20]], "2": [[10, 13], [14, 20]] },
"retry_policy": { "BUSY": 900, "NO_ANSWER": 10800 },
"scheduled_at": "2026-06-02T08:00:00+02:00",
"contacts": [
{ "phone": "+33612345678", "variables": { "first_name": "Marie" } }
],
"exclude": ["+33698765432"]
}
| Champ | Obligatoire | Type | Description |
|---|---|---|---|
name | Oui | string | Jusqu'à 255 caractères. |
description | Non | string | Jusqu'à 1000 caractères. |
agent_id | Oui | string | L'agent dont la version live s'exécute sur chaque appel décroché. Ne peut plus être changé une fois la campagne armée. |
caller_id | Oui | string | Le numéro présenté aux personnes que vous appelez, au format E.164. Il doit s'agir de l'un des numéros de votre organisation rattachés à cet agent ; tout autre numéro renvoie 422. C'est ce qui achemine les rappels vers le bon agent. |
prefix | Non | string | Préfixe de numérotation composé uniquement de chiffres, ajouté devant chaque numéro composé. Omettez-le sauf si votre interlocuteur Manivox.ai vous en a donné un. |
rate_limit, rate_interval | Non | integer | Combien d'appels peuvent être démarrés par intervalle de rate_interval secondes. Omettez rate_limit pour n'appliquer aucune limite de cadence. |
max_parallel_calls | Non | integer | Combien d'appels de cette campagne peuvent être en cours en même temps. Omis, il suit la cadence d'appel. Le quota d'appels simultanés de votre organisation, moins une part réservée aux appels entrants, reste toujours un plafond également. |
ringing_timeout_seconds | Non | integer | Combien de temps laisser sonner un téléphone. 35 par défaut. |
max_attempts_per_contact | Non | integer | Tentatives par contact, premier appel compris. 3 par défaut. |
voicemail_policy | Non | string | SKIP (par défaut : le contact est clos quand un répondeur est détecté) ou RETRY (traité comme n'importe quel autre résultat transitoire). MESSAGE est encore accepté pour compatibilité mais se comporte actuellement comme SKIP. |
timezone | Non | string | Fuseau horaire IANA pour les plages horaires d'appel et scheduled_at. Omis (recommandé), le fuseau horaire de l'organisation s'applique, y compris s'il est corrigé plus tard. |
calling_windows | Non | object | Heures pendant lesquelles la campagne peut composer ; voir ci-dessous. Par défaut, les heures de bureau françaises. Envoyez null pour autoriser la numérotation à toute heure. |
retry_policy | Non | object | Délai par résultat avant la tentative suivante, en secondes ; voir ci-dessous. |
scheduled_at | Non | datetime | Heure de démarrage. Envoyez un décalage horaire (2026-06-02T08:00:00+02:00) ; une heure sans décalage est lue dans le fuseau horaire de la campagne. Le renseigner arme la campagne : elle est créée ARMED et attend. Omettez-le pour créer un brouillon IDLE que vous lancez explicitement. |
csv_mapping | Non | object | {"phone_column": ..., "variable_columns": {"Column": "variable_name"}}. N'a de sens que lorsque les contacts proviennent d'un CSV importé dans le dashboard ; les contacts envoyés en ligne portent leurs propres variables. |
contacts | Non | array | Jusqu'à 1000 contacts à ingérer immédiatement ({"phone": ..., "variables": {...}}), normalisés, filtrés et dédoublonnés exactement comme lors d'un ajout. Une liste plus grande passe par des ajouts répétés. |
exclude | Non | array | Jusqu'à 1000 numéros que cette campagne ne doit jamais appeler (sa liste d'exclusion). Appliqué avant l'ingestion des contacts envoyés en ligne. |
Réponse, 201 Created : l'objet campagne, avec status IDLE ou ARMED selon scheduled_at. Les échecs de validation renvoient 422 avec l'objet errors indexé par champ décrit dans Format d'erreur.
Plages horaires d'appel
{
"1": [[10, 13], [14, 20]],
"2": [[10, 13], [14, 20]],
"3": [[10, 13], [14, 20]],
"4": [[10, 13], [14, 20]],
"5": [[10, 13], [14, 20]]
}
Les clés sont les jours de la semaine ISO (1 = lundi … 7 = dimanche) ; les valeurs sont des listes de paires d'heures semi-ouvertes [début, fin), de sorte que [14, 20] signifie « peut composer jusqu'à 19:59 ». L'objet ci-dessus est la valeur par défaut appliquée quand vous omettez le champ. Les heures sont évaluées dans le fuseau horaire de la campagne (celui de l'organisation sauf si vous en définissez un). Un objet vide est refusé : il signifierait « jamais », ce qu'aucun client n'a jamais voulu dire. En dehors de sa plage horaire, une campagne reste ARMED et saute ; elle n'est pas mise en pause.
Politique de nouvelles tentatives
{ "BUSY": 900, "NO_ANSWER": 10800, "UNREACHABLE": 21600, "CONGESTION": 300, "FAILED": 1800 }
Délai en secondes avant qu'un contact soit retenté, par résultat. Seules les clés que vous envoyez remplacent les valeurs par défaut indiquées ci-dessus (15 min, 3 h, 6 h, 5 min, 30 min) ; utilisez null pour « ne jamais retenter ce résultat ». INVALID et REJECTED ne sont jamais retentés quelle que soit la configuration, CARRIER_CAPPED est retenté peu après sans consommer de tentative, et VOICEMAIL suit voicemail_policy. max_attempts_per_contact est le plafond absolu, tous résultats confondus.
Lister les campagnes
GET /api/campaigns
Auth : session du dashboard
Renvoie toutes les campagnes de votre organisation, les plus récentes en premier, chacune avec son agent (id, name) et un calls_count.
Obtenir une campagne
GET /api/campaigns/{id}
Auth : session du dashboard
Renvoie la campagne plus un résumé progress en direct, calculé à partir des lignes de contacts elles-mêmes, jamais à partir de compteurs en cache :
{
"id": "camp_01hx...",
"status": "ARMED",
"progress": {
"total": 950,
"processed": 210,
"outstanding": 740,
"by_status": {
"PENDING": 700, "DIALING": 40, "COMPLETED": 180,
"FAILED": 20, "INVALID": 8, "DO_NOT_CALL": 2
}
}
}
Mettre à jour une campagne
PUT /api/campaigns/{id} # session : PUT ou PATCH
PATCH /api/v1/campaigns/{id} # clé API : PATCH seulement, PUT n'est pas accepté
Auth : session du dashboard, ou clé API avec campaigns:write
Accepte n'importe quel sous-ensemble des champs de création, à l'exception de contacts et exclude (utilisez les endpoints dédiés). status n'est pas modifiable ici ; utilisez les actions de cycle de vie ci-dessous. agent_id ne peut changer que tant que la campagne n'est pas ARMED (422 sinon), et un nouveau caller_id est validé par rapport aux numéros de cet agent. Sur une campagne armée, les changements s'appliquent au prochain cycle de numérotation.
Supprimer une campagne
DELETE /api/campaigns/{id}
Auth : session du dashboard
Supprime la campagne, ses lignes de contacts, sa liste d'exclusion et son CSV importé. Refusé avec 409 tant qu'elle est ARMED : mettez-la d'abord en pause ou annulez-la. Renvoie 204.
Lancer, mettre en pause, reprendre, annuler
POST /api/campaigns/{id}/start
POST /api/campaigns/{id}/pause
POST /api/campaigns/{id}/resume
POST /api/campaigns/{id}/cancel
Auth : session du dashboard
startarme une campagneIDLE, effacepaused_reasonet respecte unscheduled_atfutur. Passez{"start_now": true}pour effacer la planification et composer immédiatement. Lancer une campagne déjàARMEDest idempotent.422sur une campagne terminale.pauseremet une campagneARMEDenIDLEavecpaused_reasonBY_OPERATOR.422sinon.resumeest un alias destart, conservé pour les clients existants.cancelest terminal :422si la campagne l'est déjà.
Chacune renvoie l'objet campagne mis à jour.
Ajouter des contacts
POST /api/campaigns/{id}/contacts
Auth : session du dashboard
Des contacts peuvent être ajoutés à une campagne déjà en train de composer : les campagnes en alimentation continue sont prises en charge par conception. Les nouvelles lignes deviennent composables immédiatement. Seules les campagnes COMPLETED et CANCELED refusent de nouveaux contacts (422).
Le corps de la requête est un tableau JSON brut, pas un objet, d'au plus 1000 contacts par requête :
[
{ "phone": "+33612345678", "variables": { "first_name": "Marie" } },
{ "phone": "0612345679" }
]
Chaque numéro est normalisé en E.164 (les formes +33…, 33… et nationale 0… sont acceptées ; tout ce que la plateforme ne peut pas lire avec certitude est refusé plutôt que deviné), puis vérifié par rapport aux contacts déjà présents dans la campagne, à la liste d'opposition du compte et à la liste d'exclusion de la campagne. variables contient les valeurs par contact que votre agent lit sous la forme {{variable_name}} ; une clé qui entre en collision avec une variable système est ignorée. La réponse rend compte de ce qui est arrivé au lot :
{
"valid": 1,
"invalid": 0,
"duplicate": 0,
"do_not_call": 1,
"excluded": 0,
"invalid_reasons": {}
}
Réponse, 201 Created. Ce même format de rapport est celui que produit un import CSV dans le dashboard.
Lister les contacts d'une campagne
GET /api/campaigns/{id}/contacts
Auth : session du dashboard
| Paramètre | Type | Description |
|---|---|---|
status | string | Filtre par statut de contact : pending, dialing, completed, failed, invalid, do_not_call. Les valeurs inconnues renvoient 422. |
disposition | string | Filtre par dernier résultat : answered, busy, no_answer, unreachable, invalid, rejected, congestion, carrier_capped, voicemail, voicemail_message_left, called_back, failed. |
per_page | integer | Taille de page, 50 par défaut, plafonnée à 200. |
Renvoie une charge utile de paginateur standard (data, total, per_page, current_page…), triée par numéro de ligne d'origine du contact. Chaque contact porte phone, phone_raw, variables, status, attempts, last_disposition, next_attempt_after, last_call_id, callback_count et last_callback_call_id.
Liste d'exclusion de la campagne
GET /api/campaigns/{id}/exclusions
POST /api/campaigns/{id}/exclusions
DELETE /api/campaigns/{id}/exclusions/{phone}
Auth : session du dashboard
Les numéros que cette campagne ne doit pas appeler. Ce n'est délibérément pas la liste d'opposition : laisser des clients existants hors d'une action d'acquisition n'équivaut pas à la déclaration « ne m'appelez plus jamais ». GET renvoie {"campaign_id", "count", "phones": [...]}. POST prend {"phones": ["+33698765432", ...]} (jusqu'à 1000) et renvoie 201 avec added, already_listed, invalid, invalid_examples et contacts_stopped : les contacts correspondants encore en attente de numérotation sont clos immédiatement, même sur une campagne armée ; un contact dont l'appel est en cours garde sa conversation. DELETE avec le numéro en E.164 (encodez le + dans l'URL) renvoie {"status": "deleted"}, ou 404 avec {"status": "not_listed"} ; il ne réactive jamais les contacts arrêtés.
Liste d'opposition
GET /api/do-not-call
POST /api/do-not-call
DELETE /api/do-not-call/{id}
Auth : session du dashboard
La liste d'opposition permanente, par compte. Chaque campagne d'un compte est filtrée avec la liste de ce compte au moment de l'import et à nouveau au moment de la numérotation.
GETliste les entrées, les plus récentes en premier, paginées. Le paramètre facultatifaccount_idrestreint à un compte plus les entrées historiques à l'échelle de l'organisation (exactement ce avec quoi les campagnes de ce compte sont filtrées) ;searchcorrespond à une partie d'un numéro ;sourcevautmanual,importouopt_out.POSTprend{"account_id": "acc_...", "phones": ["+33612345678", "0698765432"], "reason": "CRM sync 2026-06"}:account_idetphones(1 à 10 000) sont obligatoires,reasonest facultatif (255 caractères). Les numéros sont normalisés, les formes issues de journaux d'appels sans le+conviennent donc. Renvoie201avecadded,already_listed,invalid,invalid_examplesetcontacts_stopped: les contacts des campagnes de ce compte encore en attente de numérotation sont clos dans la même requête.DELETE /{id}supprime une entrée ({"status": "deleted"}). Il ne réintègre pas les contacts arrêtés dans une campagne ; réajouter quelqu'un passe par un ajout de contact explicite.