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… — Bearer la_…, la clé devant disposer de la permission campaigns:write pour créer, modifier ou piloter une campagne (read suffit pour lister et consulter). Les écritures acceptent un en-tête Idempotency-Key afin 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

StatutSignification
IDLENe 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.
ARMEDActivé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.
COMPLETEDChaque contact a atteint un état final. Positionné par le composeur, jamais par cette API. Terminal.
CANCELEDArrê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"]
}
ChampObligatoireTypeDescription
nameOuistringJusqu'à 255 caractères.
descriptionNonstringJusqu'à 1000 caractères.
agent_idOuistringL'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_idOuistringLe 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.
prefixNonstringPré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_intervalNonintegerCombien 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_callsNonintegerCombien 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_secondsNonintegerCombien de temps laisser sonner un téléphone. 35 par défaut.
max_attempts_per_contactNonintegerTentatives par contact, premier appel compris. 3 par défaut.
voicemail_policyNonstringSKIP (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.
timezoneNonstringFuseau 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_windowsNonobjectHeures 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_policyNonobjectDélai par résultat avant la tentative suivante, en secondes ; voir ci-dessous.
scheduled_atNondatetimeHeure 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_mappingNonobject{"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.
contactsNonarrayJusqu'à 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.
excludeNonarrayJusqu'à 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

  • start arme une campagne IDLE, efface paused_reason et respecte un scheduled_at futur. Passez {"start_now": true} pour effacer la planification et composer immédiatement. Lancer une campagne déjà ARMED est idempotent. 422 sur une campagne terminale.
  • pause remet une campagne ARMED en IDLE avec paused_reason BY_OPERATOR. 422 sinon.
  • resume est un alias de start, conservé pour les clients existants.
  • cancel est terminal : 422 si 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ètreTypeDescription
statusstringFiltre par statut de contact : pending, dialing, completed, failed, invalid, do_not_call. Les valeurs inconnues renvoient 422.
dispositionstringFiltre par dernier résultat : answered, busy, no_answer, unreachable, invalid, rejected, congestion, carrier_capped, voicemail, voicemail_message_left, called_back, failed.
per_pageintegerTaille 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.

  • GET liste les entrées, les plus récentes en premier, paginées. Le paramètre facultatif account_id restreint à un compte plus les entrées historiques à l'échelle de l'organisation (exactement ce avec quoi les campagnes de ce compte sont filtrées) ; search correspond à une partie d'un numéro ; source vaut manual, import ou opt_out.
  • POST prend {"account_id": "acc_...", "phones": ["+33612345678", "0698765432"], "reason": "CRM sync 2026-06"} : account_id et phones (1 à 10 000) sont obligatoires, reason est facultatif (255 caractères). Les numéros sont normalisés, les formes issues de journaux d'appels sans le + conviennent donc. Renvoie 201 avec added, already_listed, invalid, invalid_examples et contacts_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.