> ## Documentation Index
> Fetch the complete documentation index at: https://docs.voicehire.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Ajouter des candidats

> Ajoute jusqu'à 100 candidats à une campagne et les contacte selon contact_mode : appel immédiat (call), SMS d'invitation (sms) ou email d'invitation (email), avec failover SMS/email

## Paramètres

<ParamField path="campaign_id" type="string" required>
  Identifiant unique de la campagne
</ParamField>

## Corps de la requête

<ParamField body="contact_mode" type="string" default="call">
  Méthode de contact :

  * `call` (défaut) — Appel téléphonique automatique immédiat. `email` requis pour chaque candidat.
  * `sms` — SMS d'invitation à passer l'entretien, avec lien personnalisé (0,25 crédit). Le candidat clique et lance lui-même l'entretien quand il le souhaite. **Failover email** : si le `phone` est absent ou invalide mais qu'un `email` valide est fourni, l'invitation part par email (gratuit).
  * `email` — Email d'invitation à passer l'entretien, avec lien personnalisé (gratuit). **Failover SMS** : si l'`email` est absent ou invalide mais qu'un `phone` valide est fourni, l'invitation part par SMS (0,25 crédit).

  Pour `sms` et `email`, chaque candidat doit fournir au moins une coordonnée valide (`phone` ou `email`), sinon il est rejeté.
</ParamField>

<ParamField body="candidates" type="array" required>
  Liste des candidats à ajouter (maximum 100)

  <Expandable title="Propriétés de chaque candidat">
    <ParamField body="first_name" type="string" required>
      Prénom du candidat
    </ParamField>

    <ParamField body="last_name" type="string" required>
      Nom de famille du candidat
    </ParamField>

    <ParamField body="phone" type="string">
      Numéro de téléphone (format E.164, principalement FR). Requis si `contact_mode = "call"`. Pour `sms`/`email`, au moins un de `phone` ou `email` doit être valide.

      Formats acceptés :

      * `0612345678`
      * `06 12 34 56 78`
      * `+33612345678`
    </ParamField>

    <ParamField body="email" type="string">
      Adresse email du candidat. Requis si `contact_mode = "call"`. Pour `sms`/`email`, au moins un de `phone` ou `email` doit être valide (le failover bascule automatiquement sur le canal disponible).
    </ParamField>
  </Expandable>
</ParamField>

## Réponse

<ResponseExample>
  ```json theme={null}
  {
    "campaign_id": "abc123def456",
    "candidates_added": 23,
    "candidates_skipped": 2,
    "calls_queued": 23,
    "errors": [
      {
        "index": 5,
        "error": "Numéro de téléphone invalide"
      }
    ]
  }
  ```
</ResponseExample>

<Note>
  L'exemple ci-dessus correspond au mode `call`. En modes `sms` et `email`, la réponse a la forme : `candidates_sourced`, `candidates_skipped`, `sms_sent`, `emails_sent`, `errors`.
</Note>

<ResponseField name="campaign_id" type="string" required>
  Identifiant de la campagne
</ResponseField>

<ResponseField name="candidates_added" type="integer" required>
  Nombre de candidats ajoutés avec succès
</ResponseField>

<ResponseField name="candidates_skipped" type="integer" required>
  Nombre de candidats ignorés (déjà présents dans la campagne)
</ResponseField>

<ResponseField name="calls_queued" type="integer" required>
  Nombre d'appels mis en file d'attente
</ResponseField>

<ResponseField name="errors" type="array">
  Liste des erreurs rencontrées

  <Expandable title="Propriétés">
    <ResponseField name="index" type="integer">
      Index du candidat dans le tableau (commence à 0)
    </ResponseField>

    <ResponseField name="error" type="string">
      Description de l'erreur
    </ResponseField>
  </Expandable>
</ResponseField>

## Exemple de requête

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://app.voicehire.io/api/v1/campaigns/abc123def456/candidates \
    -H "X-API-Key: vh_live_XXXXXXXXXXXXX" \
    -H "Content-Type: application/json" \
    -d '{
      "candidates": [
        {
          "first_name": "Jean",
          "last_name": "Dupont",
          "phone": "0612345678",
          "email": "jean.dupont@example.com"
        },
        {
          "first_name": "Marie",
          "last_name": "Martin",
          "phone": "0687654321",
          "email": "marie.martin@example.com"
        }
      ]
    }'
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://app.voicehire.io/api/v1/campaigns/abc123def456/candidates",
      headers={
          "X-API-Key": "vh_live_XXXXXXXXXXXXX",
          "Content-Type": "application/json"
      },
      json={
          "candidates": [
              {
                  "first_name": "Jean",
                  "last_name": "Dupont",
                  "phone": "0612345678",
                  "email": "jean.dupont@example.com"
              },
              {
                  "first_name": "Marie",
                  "last_name": "Martin",
                  "phone": "0687654321",
                  "email": "marie.martin@example.com"
              }
          ]
      }
  )
  ```

  ```javascript Node.js theme={null}
  const axios = require('axios');

  const response = await axios.post(
    'https://app.voicehire.io/api/v1/campaigns/abc123def456/candidates',
    {
      candidates: [
        {
          first_name: 'Jean',
          last_name: 'Dupont',
          phone: '0612345678',
          email: 'jean.dupont@example.com'
        },
        {
          first_name: 'Marie',
          last_name: 'Martin',
          phone: '0687654321',
          email: 'marie.martin@example.com'
        }
      ]
    },
    {
      headers: {
        'X-API-Key': 'vh_live_XXXXXXXXXXXXX',
        'Content-Type': 'application/json'
      }
    }
  );
  ```
</CodeGroup>

## Notes importantes

* Les candidats déjà présents dans la campagne (même téléphone **ou** même email) sont ignorés (skipped)
* Maximum 100 candidats par requête
* En modes `sms` et `email`, un **failover** s'applique par candidat : si le canal principal n'a pas de coordonnée valide, l'autre canal est utilisé s'il est disponible. Le coût facturé correspond au canal **réellement utilisé**.
* Le coût en crédits dépend du canal effectivement utilisé :

| Canal utilisé   | Action                                    | Coût par candidat traité avec succès            |
| --------------- | ----------------------------------------- | ----------------------------------------------- |
| `call` (défaut) | Appel téléphonique automatique immédiat   | **1 crédit** (`transaction_type = campaign`)    |
| `sms`           | SMS d'invitation avec lien personnalisé   | **0,25 crédit** (`transaction_type = sourcing`) |
| `email`         | Email d'invitation avec lien personnalisé | **Gratuit**                                     |

Aucun crédit n'est débité pour les candidats skippés (doublon), ni en cas d'échec d'envoi.

## Codes d'erreur

| Code | Description                                            |
| ---- | ------------------------------------------------------ |
| 400  | Données invalides (format incorrect, champs manquants) |
| 401  | Clé API manquante ou invalide                          |
| 402  | Crédits insuffisants                                   |
| 404  | Campagne non trouvée                                   |


## OpenAPI

````yaml POST /api/v1/campaigns/{campaign_id}/candidates
openapi: 3.0.3
info:
  title: VoiceHire API
  description: >
    API REST pour automatiser les campagnes de recrutement avec VoiceHire.


    ## Authentification


    L'API utilise une authentification par clé API. Vous devez inclure votre clé
    API dans l'en-tête `X-API-Key` de chaque requête.


    ```

    X-API-Key: vh_live_XXXXXXXXXXXXX

    ```


    ## Format des réponses


    Toutes les réponses sont au format JSON. Les erreurs suivent un format
    standardisé :


    ```json

    {
      "error": {
        "code": "ERROR_CODE",
        "message": "Description de l'erreur",
        "details": {}
      }
    }

    ```


    ## Limites


    - Les numéros de téléphone doivent être des numéros français (06 ou 07)

    - Maximum 100 candidats par requête d'ajout

    - Les campagnes génèrent automatiquement 6 questions d'entretien
  version: 1.0.0
  contact:
    name: Support VoiceHire
    email: support@voicehire.io
    url: https://www.voicehire.io
servers:
  - url: https://app.voicehire.io
    description: Production
  - url: http://localhost:8080
    description: Développement local
security:
  - ApiKeyAuth: []
tags:
  - name: Campagnes
    description: Gestion des campagnes de recrutement
  - name: Candidats
    description: Gestion des candidats
  - name: Compte
    description: Informations du compte
paths:
  /api/v1/campaigns/{campaign_id}/candidates:
    post:
      tags:
        - Candidats
      summary: Ajouter des candidats
      description: >
        Ajoute des candidats à une campagne et les contacte selon `contact_mode`
        :

        appel automatique (`call`, défaut), SMS d'invitation (`sms`) ou email
        d'invitation (`email`).

        Maximum 100 candidats par requête.
      operationId: addCandidates
      parameters:
        - name: campaign_id
          in: path
          required: true
          description: Identifiant de la campagne
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CandidateAddRequest'
            example:
              candidates:
                - first_name: Jean
                  last_name: Dupont
                  phone_number: '+33612345678'
                  email: jean.dupont@example.com
                - first_name: Marie
                  last_name: Martin
                  phone_number: '+33687654321'
      responses:
        '201':
          description: Candidats ajoutés avec succès
          content:
            application/json:
              schema:
                type: object
                properties:
                  added:
                    type: integer
                    description: Nombre de candidats ajoutés
                  duplicates:
                    type: integer
                    description: Nombre de doublons ignorés
                  invalid:
                    type: integer
                    description: Nombre de candidats invalides
                  calls_queued:
                    type: integer
                    description: Nombre d'appels en file d'attente
              example:
                added: 2
                duplicates: 0
                invalid: 0
                calls_queued: 2
        '400':
          description: Données invalides
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Campagne non trouvée
components:
  schemas:
    CandidateAddRequest:
      type: object
      required:
        - candidates
      properties:
        contact_mode:
          type: string
          enum:
            - call
            - sms
            - email
          default: call
          description: >
            Méthode de contact :

            - `call` (défaut) : appel téléphonique automatique immédiat. `email`
            requis par candidat.

            - `sms`  : SMS d'invitation à passer l'entretien (0,25 crédit).
            **Failover email** : si le `phone` est absent/invalide mais qu'un
            `email` valide est fourni, l'invitation part par email (gratuit).

            - `email` : email d'invitation à passer l'entretien (gratuit).
            **Failover SMS** : si l'`email` est absent/invalide mais qu'un
            `phone` valide est fourni, l'invitation part par SMS (0,25 crédit).


            Pour `sms` et `email`, chaque candidat doit fournir au moins une
            coordonnée valide (`phone` OU `email`), sinon il est rejeté.
        candidates:
          type: array
          minItems: 1
          maxItems: 100
          items:
            type: object
            required:
              - first_name
              - last_name
              - phone
            properties:
              first_name:
                type: string
                description: Prénom du candidat
                example: Jean
              last_name:
                type: string
                description: Nom du candidat
                example: Dupont
              phone:
                type: string
                description: Numéro de téléphone (format E.164, principalement FR)
                example: 33612345678
              email:
                type: string
                format: email
                description: >
                  Email du candidat. Requis si `contact_mode = call`. Pour
                  `sms`/`email`, au moins une coordonnée valide (`phone` ou
                  `email`) est requise — le failover bascule automatiquement sur
                  le canal disponible.
                example: jean.dupont@example.com
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Code d'erreur machine-readable
              example: MISSING_API_KEY
            message:
              type: string
              description: Message d'erreur human-readable
              example: API key is required
            details:
              type: object
              description: Détails supplémentaires sur l'erreur
              additionalProperties: true
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: Clé API au format vh_live_XXXXX

````