openapi: 3.0.0
info:
  title: 'Documentation API Manuscry'
  description: "L'API Manuscry permet d'ajouter des contacts à vos campagnes de courriers personnalisés de manière programmatique."
  version: '1.0'
  contact:
    name: 'Support Manuscry'
    email: contact@manuscry.com
    url: 'https://manuscry.com'
  license:
    name: Proprietary
    url: 'https://manuscry.com/legal/terms'
servers:
  -
    url: 'https://manuscry.com/api'
    description: 'Manuscry API Server'
security:
  -
    BearerAuth: {  }
  -
    ApiKeyAuth: {  }
tags:
  -
    name: MCP
    description: 'Opérations liées aux MCP'
  -
    name: Campagnes
    description: 'Opérations liées aux Campagnes'
  -
    name: 'Envoi de courriers'
    description: 'Opérations liées aux Envoi de courriers'
  -
    name: Utilisateur
    description: 'Opérations liées aux Utilisateur'
  -
    name: Contacts
    description: 'Opérations liées aux Contacts'
  -
    name: 'Liste de blocage'
    description: 'Opérations liées aux Liste de blocage'
  -
    name: Courriers
    description: 'Opérations liées aux Courriers'
  -
    name: 'Impression autonome'
    description: 'Opérations liées aux Impression autonome'
  -
    name: 'API Partenaire'
    description: 'Opérations liées aux API Partenaire'
  -
    name: Calendrier
    description: 'Opérations liées aux Calendrier'
  -
    name: Webhooks
    description: 'Opérations liées aux Webhooks'
  -
    name: "Recherche d'adresses"
    description: "Opérations liées aux Recherche d'adresses"
paths:
  '/campaigns/{campaign_id}/settings':
    get:
      operationId: get-campaign-settings
      summary: "Récupérer la configuration d'une campagne"
      description: "Récupère les champs personnalisés et la configuration d'une campagne spécifique. Les champs retournés dépendent du type de campagne et de la configuration de la lettre."
      security:
        -
          BearerAuth: {  }
        -
          ApiKeyAuth: {  }
      tags:
        - Campagnes
      parameters:
        -
          name: campaign_id
          in: path
          required: true
          description: "ID hashé de la campagne (visible dans l'URL de votre campagne)"
          schema:
            type: string
          example: abc123def456
      responses:
        200:
          description: Succès
          content:
            application/json:
              schema:
                type: object
                properties:
                  linkedin_manual: { type: object, properties: { success: { type: boolean }, data: { type: object, properties: { campaign: { type: object, properties: { id: { type: string }, name: { type: string }, type: { type: string }, status: { type: string }, site: { type: string }, can_add_contacts: { type: boolean }, contacts_count: { type: integer } } }, fields: { type: array, items: { type: object } }, sheet_type: { type: string } } } } }
                  manual_with_variables: { type: object, properties: { success: { type: boolean }, data: { type: object, properties: { campaign: { type: object, properties: { id: { type: string }, name: { type: string }, type: { type: string }, status: { type: string }, site: { type: string }, can_add_contacts: { type: boolean }, contacts_count: { type: integer } } }, fields: { type: array, items: { type: object } }, sheet_type: { type: string } } } } }
                  birthday: { type: object, properties: { success: { type: boolean }, data: { type: object, properties: { campaign: { type: object, properties: { id: { type: string }, name: { type: string }, type: { type: string }, status: { type: string }, site: { type: string }, can_add_contacts: { type: boolean }, contacts_count: { type: integer } } }, fields: { type: array, items: { type: object } }, sheet_type: { type: string } } } } }
                  linkedin_auto: { type: object, properties: { success: { type: boolean }, data: { type: object, properties: { campaign: { type: object, properties: { id: { type: string }, name: { type: string }, type: { type: string }, status: { type: string }, site: { type: string }, can_add_contacts: { type: boolean }, contacts_count: { type: integer } } }, fields: { type: array }, sheet_type: { type: string } } } } }
              example:
                linkedin_manual:
                  success: true
                  data: { campaign: { id: abc123def456, name: 'Ma campagne LinkedIn', type: linkedin, status: active, site: manuscry, can_add_contacts: true, contacts_count: 25 }, fields: [{ key: identifier, label: 'Identifiant unique', type: text, required: false, validation: 'nullable|string|max:100', placeholder: 'Généré automatiquement si vide', is_custom: false, is_in_letter: false, validation_type: system }, { key: linkedin_url, label: 'URL LinkedIn', type: url, required: true, validation: 'required|url|regex:/linkedin\.com\/in\/.+/', placeholder: 'https://www.linkedin.com/in/nom-prenom/', is_custom: false, is_in_letter: false, validation_type: required }, { key: ai_context, label: 'Contexte personnel (optionnel)', type: textarea, required: false, validation: 'nullable|string|max:500', placeholder: 'Informations spécifiques non présentes sur le profil...', is_custom: false, is_in_letter: true, validation_type: optional }], sheet_type: linkedin_urls }
                manual_with_variables:
                  success: true
                  data: { campaign: { id: def456ghi789, name: 'Prospection dirigeants', type: manual, status: active, site: manuscry, can_add_contacts: true, contacts_count: 150 }, fields: [{ key: identifier, label: 'Identifiant unique', type: text, required: false, validation: 'nullable|string|max:100', placeholder: 'Généré automatiquement si vide', is_custom: false, is_in_letter: false, validation_type: system }, { key: address_line1, label: 'Adresse ligne 1', type: text, required: true, validation: 'required|string|max:255', placeholder: 'Nom ou raison sociale', is_custom: false, is_in_letter: false, validation_type: required }, { key: address_line2, label: 'Adresse ligne 2', type: text, required: true, validation: 'required|string|max:255', placeholder: 'Numéro et nom de rue', is_custom: false, is_in_letter: false, validation_type: required }, { key: address_zip, label: 'Code postal', type: text, required: true, validation: 'required|string|max:20', placeholder: '75001', is_custom: false, is_in_letter: false, validation_type: required }, { key: address_city, label: Ville, type: text, required: true, validation: 'required|string|max:255', placeholder: PARIS, is_custom: false, is_in_letter: false, validation_type: required }, { key: address_country, label: Pays, type: text, required: true, validation: 'required|string|max:2', placeholder: FR, is_custom: false, is_in_letter: false, validation_type: required }, { key: secteur_activite, label: "Secteur d'activité", type: text, required: true, validation: 'required|string|max:255', placeholder: 'Exemple: Finance, Tech, Santé...', is_custom: true, is_in_letter: true, validation_type: required }], sheet_type: manual_contacts }
                birthday:
                  success: true
                  data: { campaign: { id: jkl012mno345, name: 'Anniversaires clients', type: birthday, status: active, site: manuscry, can_add_contacts: true, contacts_count: 89 }, fields: [{ key: identifier, label: 'Identifiant unique', type: text, required: false, validation: 'nullable|string|max:100', placeholder: 'Généré automatiquement si vide', is_custom: false, is_in_letter: false, validation_type: system }, { key: birthday_date, label: "Date d'anniversaire", type: date, required: true, validation: 'required|date|date_format:Y-m-d', placeholder: '1985-03-15', is_custom: false, is_in_letter: false, validation_type: required }, { key: address_line1, label: 'Adresse ligne 1', type: text, required: true, validation: 'required|string|max:255', placeholder: 'Nom complet', is_custom: false, is_in_letter: false, validation_type: required }, { key: ai_context, label: 'Contexte personnel (optionnel)', type: textarea, required: false, validation: 'nullable|string|max:500', placeholder: 'Informations sur le client...', is_custom: false, is_in_letter: true, validation_type: optional }], sheet_type: birthday_contacts }
                linkedin_auto:
                  success: true
                  data: { campaign: { id: pqr678stu901, name: 'Ciblage LinkedIn automatique', type: linkedin, status: active, site: manuscry, can_add_contacts: false, contacts_count: 0 }, fields: {  }, sheet_type: null }
        400:
          description: 'Requête invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        401:
          description: 'Non autorisé - Clé API manquante ou invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        403:
          description: 'Accès refusé - Campaign/Agent en pause ou annulé'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        404:
          description: 'Ressource non trouvée'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        422:
          description: 'Erreur de validation'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        429:
          description: 'Trop de requêtes - Rate limit dépassé (100 req/min)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        500:
          description: 'Erreur serveur interne'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  '/campaigns/{campaign_id}/contacts':
    post:
      operationId: add-contact-self-print
      summary: 'Générer un courrier en impression autonome'
      description: "Génère un courrier prêt à imprimer (PNG haute définition) en ajoutant un contact à une campagne en mode impression autonome. Pas d'envoi postal - vous récupérez ensuite les fichiers via l'API pour les imprimer vous-même. À utiliser pour brancher Manuscry à votre CRM ou intégration."
      security:
        -
          BearerAuth: {  }
        -
          ApiKeyAuth: {  }
      tags:
        - 'Envoi de courriers'
      parameters:
        -
          name: campaign_id
          in: path
          required: true
          description: 'ID hashé de la campagne (self-print)'
          schema:
            type: string
          example: jkl012mno345
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                identifier:
                  type: string
                address_line1:
                  type: string
                address_line2:
                  type: string
                address_zip:
                  type: string
                address_city:
                  type: string
                address_country:
                  type: string
                ai_context:
                  type: string
            example:
              identifier: contact_unique_004
              address_line1: 'Sophie Bernard'
              address_line2: '789 boulevard Victor Hugo'
              address_zip: '33000'
              address_city: BORDEAUX
              address_country: FR
              ai_context: 'Prospect qualifié lors du salon professionnel, secteur viticole'
      responses:
        201:
          description: Succès
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  message: { type: string }
                  data: { type: object, properties: { contact: { type: object, properties: { identifier: { type: string }, fields: { type: object, properties: { identifier: { type: string }, address_line1: { type: string }, address_line2: { type: string }, address_zip: { type: string }, address_city: { type: string }, address_country: { type: string }, ai_context: { type: string } } }, created_at: { type: string }, updated_at: { type: string } } }, campaign: { type: object, properties: { id: { type: string }, contacts_count: { type: integer } } } } }
              example:
                success: true
                message: 'Contact added successfully'
                data:
                  contact: { identifier: contact_unique_004, fields: { identifier: contact_unique_004, address_line1: 'Sophie Bernard', address_line2: '789 boulevard Victor Hugo', address_zip: '33000', address_city: BORDEAUX, address_country: FR, ai_context: 'Prospect qualifié lors du salon professionnel, secteur viticole' }, created_at: '2025-11-02T10:30:00.000000Z', updated_at: '2025-11-02T10:30:00.000000Z' }
                  campaign: { id: jkl012mno345, contacts_count: 8 }
        400:
          description: 'Requête invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        401:
          description: 'Non autorisé - Clé API manquante ou invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        403:
          description: 'Accès refusé - Campaign/Agent en pause ou annulé'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        404:
          description: 'Ressource non trouvée'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        422:
          description: 'Erreur de validation'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        429:
          description: 'Trop de requêtes - Rate limit dépassé (100 req/min)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        500:
          description: 'Erreur serveur interne'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /me:
    get:
      operationId: get-me
      summary: 'Informations utilisateur'
      description: "Récupère les informations de l'utilisateur connecté et la liste de ses campagnes (hors brouillons)."
      security:
        -
          BearerAuth: {  }
        -
          ApiKeyAuth: {  }
      tags:
        - Utilisateur
      responses:
        200:
          description: Succès
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  user: { type: object, properties: { id: { type: string }, name: { type: string }, email: { type: string }, created_at: { type: string } } }
                  campaigns: { type: array, items: { type: object } }
                  meta: { type: object, properties: { total_campaigns: { type: integer }, active_campaigns: { type: integer } } }
              example:
                success: true
                user:
                  id: aBc123Xy
                  name: 'Marie Dupont'
                  email: marie.dupont@example.com
                  created_at: '2024-01-15T10:30:00.000000Z'
                campaigns:
                  - { id: dEf456Zw, name: 'Campagne Printemps 2024', status: active, internal_note: 'Dirigeants tech', is_active: true, is_self_print: false }
                  - { id: gHi789Uv, name: 'Anniversaires Clients', status: active, internal_note: null, is_active: true, is_self_print: true }
                meta:
                  total_campaigns: 2
                  active_campaigns: 2
        400:
          description: 'Requête invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        401:
          description: 'Non autorisé - Clé API manquante ou invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        403:
          description: 'Accès refusé - Campaign/Agent en pause ou annulé'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        404:
          description: 'Ressource non trouvée'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        422:
          description: 'Erreur de validation'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        429:
          description: 'Trop de requêtes - Rate limit dépassé (100 req/min)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        500:
          description: 'Erreur serveur interne'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /contacts:
    get:
      operationId: get-contacts
      summary: 'Liste des contacts'
      description: 'Récupère la liste des contacts avec pagination et filtres.'
      security:
        -
          BearerAuth: {  }
        -
          ApiKeyAuth: {  }
      tags:
        - Contacts
      parameters:
        -
          name: page
          in: query
          required: false
          description: 'Numéro de page'
          schema:
            type: integer
          example: '1'
        -
          name: per_page
          in: query
          required: false
          description: 'Éléments par page (1-100)'
          schema:
            type: integer
          example: '12'
        -
          name: search
          in: query
          required: false
          description: 'Recherche textuelle'
          schema:
            type: string
          example: TechCorp
        -
          name: status_filter
          in: query
          required: false
          description: 'Filtre par statut'
          schema:
            type: string
          example: contact
        -
          name: source_filter
          in: query
          required: false
          description: 'Filtre par source'
          schema:
            type: string
          example: linkedin
        -
          name: campaign_filter
          in: query
          required: false
          description: 'ID hashé de la campagne (recommandé - alias agent_filter accepté)'
          schema:
            type: string
          example: aBc123Xy
        -
          name: sort_field
          in: query
          required: false
          description: 'Champ de tri'
          schema:
            type: string
          example: created_at
        -
          name: sort_direction
          in: query
          required: false
          description: 'Direction (asc/desc)'
          schema:
            type: string
          example: desc
        -
          name: semantic_search
          in: query
          required: false
          description: 'Recherche par IA'
          schema:
            type: boolean
          example: 'false'
      responses:
        200:
          description: Succès
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { type: array, items: { type: object } }
                  meta: { type: object, properties: { current_page: { type: integer }, from: { type: integer }, last_page: { type: integer }, per_page: { type: integer }, to: { type: integer }, total: { type: integer } } }
                  filters_applied: { type: object, properties: { search: { type: string }, status_filter: { type: string }, source_filter: { type: string }, campaign_filter: { type: string }, sort_field: { type: string }, sort_direction: { type: string }, semantic_search: { type: boolean } } }
              example:
                success: true
                data:
                  - { id: aBc123Xy, firstname: Jean, lastname: Dupont, companyname: 'TechCorp SAS', status: contact, source: linkedin_auto, relevance: 8, linkedin_url: 'https://linkedin.com/in/jean-dupont', picture: 'https://example.com/avatar.jpg', contacts: [{ type: postal, line1: 'Jean Dupont', line2: '123 rue de Rivoli', zip: '75001', city: Paris }], created_at: '2024-01-15T10:30:00.000000Z', updated_at: '2024-01-20T14:45:00.000000Z', campaign: { id: dEf456Zw, name: 'Campagne Printemps 2024' }, agent_targeting: null }
                meta:
                  current_page: 1
                  from: 1
                  last_page: 5
                  per_page: 12
                  to: 12
                  total: 58
                filters_applied:
                  search: TechCorp
                  status_filter: contact
                  source_filter: null
                  campaign_filter: null
                  sort_field: created_at
                  sort_direction: desc
                  semantic_search: false
        400:
          description: 'Requête invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        401:
          description: 'Non autorisé - Clé API manquante ou invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        403:
          description: 'Accès refusé - Campaign/Agent en pause ou annulé'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        404:
          description: 'Ressource non trouvée'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        422:
          description: 'Erreur de validation'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        429:
          description: 'Trop de requêtes - Rate limit dépassé (100 req/min)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        500:
          description: 'Erreur serveur interne'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  '/campaigns/{campaignId}/stats':
    get:
      operationId: get-campaign-stats
      summary: "Statistiques d'une campagne"
      description: "Récupère les statistiques de performance d'une campagne."
      security:
        -
          BearerAuth: {  }
        -
          ApiKeyAuth: {  }
      tags:
        - Campagnes
      parameters:
        -
          name: campaignId
          in: path
          required: true
          description: 'ID hashé de la campagne'
          schema:
            type: string
          example: aBc123Xy
        -
          name: period
          in: query
          required: false
          description: "Période d'analyse (7d, 14d, 30d, 60d, 90d, 120d, 180d, 365d)"
          schema:
            type: string
          example: 14d
      responses:
        200:
          description: Succès
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  campaign: { type: object, properties: { id: { type: string }, name: { type: string } } }
                  period: { type: string }
                  stats: { type: object, properties: { letters_sent: { type: object, properties: { current: { type: integer }, previous: { type: integer }, change: { type: object, properties: { value: { type: number }, trend: { type: string }, display: { type: string } } }, label: { type: string } } }, qr_scanned: { type: object, properties: { current: { type: integer }, previous: { type: integer }, change: { type: object, properties: { value: { type: number }, trend: { type: string }, display: { type: string } } }, label: { type: string } } }, qr_scan_rate: { type: object, properties: { current: { type: number }, label: { type: string } } }, letters_returned: { type: object, properties: { current: { type: integer }, label: { type: string } } }, return_rate: { type: object, properties: { current: { type: number }, label: { type: string } } } } }
              example:
                success: true
                campaign:
                  id: aBc123Xy
                  name: 'Campagne Printemps 2024'
                period: 30d
                stats:
                  letters_sent: { current: 45, previous: 38, change: { value: 18.4, trend: up, display: +18.4% }, label: 'Courriers envoyés' }
                  qr_scanned: { current: 32, previous: 25, change: { value: 28.0, trend: up, display: +28.0% }, label: 'QR codes flashés' }
                  qr_scan_rate: { current: 71.1, label: 'Taux de flash (%)' }
                  letters_returned: { current: 3, label: 'Courriers retournés' }
                  return_rate: { current: 6.7, label: 'Taux de retour (%)' }
        400:
          description: 'Requête invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        401:
          description: 'Non autorisé - Clé API manquante ou invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        403:
          description: 'Accès refusé - Campaign/Agent en pause ou annulé'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        404:
          description: 'Ressource non trouvée'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        422:
          description: 'Erreur de validation'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        429:
          description: 'Trop de requêtes - Rate limit dépassé (100 req/min)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        500:
          description: 'Erreur serveur interne'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  '/contacts/{leadId}/block':
    delete:
      operationId: block-lead
      summary: 'Bloquer un contact'
      description: 'Bloque un contact et tous ses doublons, annule les contenus en attente et ajoute à la blocklist.'
      security:
        -
          BearerAuth: {  }
        -
          ApiKeyAuth: {  }
      tags:
        - Contacts
      parameters:
        -
          name: leadId
          in: path
          required: true
          description: 'ID hashé du contact'
          schema:
            type: string
          example: aBc123Xy
      responses:
        200:
          description: Succès
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  message: { type: string }
                  data: { type: object, properties: { lead_id: { type: string }, blocked_email: { type: string }, duplicates_blocked: { type: integer }, emails_cancelled: { type: integer }, letters_cancelled: { type: integer } } }
              example:
                success: true
                message: 'Lead blocked successfully.'
                data:
                  lead_id: aBc123Xy
                  blocked_email: contact@example.com
                  duplicates_blocked: 2
                  emails_cancelled: 0
                  letters_cancelled: 1
        400:
          description: 'Requête invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        401:
          description: 'Non autorisé - Clé API manquante ou invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        403:
          description: 'Accès refusé - Campaign/Agent en pause ou annulé'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        404:
          description: 'Ressource non trouvée'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        422:
          description: 'Erreur de validation'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        429:
          description: 'Trop de requêtes - Rate limit dépassé (100 req/min)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        500:
          description: 'Erreur serveur interne'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /blocklist:
    get:
      operationId: get-blocklist
      summary: 'Liste de blocage'
      description: 'Récupère la liste de blocage.'
      security:
        -
          BearerAuth: {  }
        -
          ApiKeyAuth: {  }
      tags:
        - 'Liste de blocage'
      parameters:
        -
          name: type
          in: query
          required: false
          description: 'Type de blocage (email, domain, company, siren, address, all)'
          schema:
            type: string
          example: all
        -
          name: page
          in: query
          required: false
          description: 'Numéro de page'
          schema:
            type: integer
          example: '1'
        -
          name: per_page
          in: query
          required: false
          description: 'Éléments par page'
          schema:
            type: integer
          example: '25'
      responses:
        200:
          description: Succès
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { type: array, items: { type: object } }
                  meta: { type: object, properties: { current_page: { type: integer }, from: { type: integer }, last_page: { type: integer }, per_page: { type: integer }, to: { type: integer }, total: { type: integer } } }
              example:
                success: true
                data:
                  - { id: aBc123Xy, type: email, value: spam@example.com, created_at: '2024-01-15T10:30:00.000000Z', updated_at: '2024-01-15T10:30:00.000000Z' }
                  - { id: dEf456Zw, type: company, value: 'concurrent sarl', created_at: '2024-01-14T15:20:00.000000Z', updated_at: '2024-01-14T15:20:00.000000Z' }
                meta:
                  current_page: 1
                  from: 1
                  last_page: 1
                  per_page: 25
                  to: 2
                  total: 2
        400:
          description: 'Requête invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        401:
          description: 'Non autorisé - Clé API manquante ou invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        403:
          description: 'Accès refusé - Campaign/Agent en pause ou annulé'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        404:
          description: 'Ressource non trouvée'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        422:
          description: 'Erreur de validation'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        429:
          description: 'Trop de requêtes - Rate limit dépassé (100 req/min)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        500:
          description: 'Erreur serveur interne'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    post:
      operationId: add-blocklist
      summary: 'Ajouter à la blocklist'
      description: 'Ajoute un élément à bloquer.'
      security:
        -
          BearerAuth: {  }
        -
          ApiKeyAuth: {  }
      tags:
        - 'Liste de blocage'
      parameters:
        -
          name: type
          in: query
          required: true
          description: 'Type (email, domain, company, siren, address)'
          schema:
            type: string
          example: email
        -
          name: value
          in: query
          required: true
          description: 'Valeur à bloquer'
          schema:
            type: string
          example: unwanted@spam.com
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                type:
                  type: string
                value:
                  type: string
            example:
              type: email
              value: unwanted@spam.com
      responses:
        201:
          description: Succès
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  message: { type: string }
                  data: { type: object, properties: { id: { type: string }, type: { type: string }, value: { type: string }, created_at: { type: string } } }
              example:
                success: true
                message: 'Item added to blocklist successfully'
                data:
                  id: aBc123Xy
                  type: email
                  value: unwanted@spam.com
                  created_at: '2024-01-22T10:30:00.000000Z'
        400:
          description: 'Requête invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        401:
          description: 'Non autorisé - Clé API manquante ou invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        403:
          description: 'Accès refusé - Campaign/Agent en pause ou annulé'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        404:
          description: 'Ressource non trouvée'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        422:
          description: 'Erreur de validation'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        429:
          description: 'Trop de requêtes - Rate limit dépassé (100 req/min)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        500:
          description: 'Erreur serveur interne'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    delete:
      operationId: delete-blocklist
      summary: 'Supprimer de la blocklist'
      description: 'Supprime un élément de la blocklist par ID ou par valeur.'
      security:
        -
          BearerAuth: {  }
        -
          ApiKeyAuth: {  }
      tags:
        - 'Liste de blocage'
      parameters:
        -
          name: id
          in: query
          required: false
          description: "ID hashé de l'élément à supprimer"
          schema:
            type: string
          example: aBc123Xy
        -
          name: value
          in: query
          required: false
          description: 'Valeur à débloquer (alternative à id)'
          schema:
            type: string
          example: unwanted@spam.com
      responses:
        200:
          description: Succès
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  message: { type: string }
              example:
                success: true
                message: 'Item removed from blocklist successfully'
        400:
          description: 'Requête invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        401:
          description: 'Non autorisé - Clé API manquante ou invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        403:
          description: 'Accès refusé - Campaign/Agent en pause ou annulé'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        404:
          description: 'Ressource non trouvée'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        422:
          description: 'Erreur de validation'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        429:
          description: 'Trop de requêtes - Rate limit dépassé (100 req/min)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        500:
          description: 'Erreur serveur interne'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /letters:
    get:
      operationId: get-self-print-letters
      summary: 'Liste des courriers impression autonome'
      description: "Récupère les courriers d'une campagne en impression autonome avec les URLs des fichiers haute définition à imprimer."
      security:
        -
          BearerAuth: {  }
        -
          ApiKeyAuth: {  }
      tags:
        - 'Impression autonome'
      parameters:
        -
          name: campaign_filter
          in: query
          required: true
          description: 'ID hashé de la campagne self-print (recommandé - alias agent_filter accepté)'
          schema:
            type: string
          example: abc123def456
        -
          name: category
          in: query
          required: false
          description: 'Catégorie (delivered recommandé pour récupérer les courriers prêts)'
          schema:
            type: string
          example: delivered
        -
          name: page
          in: query
          required: false
          description: 'Numéro de page'
          schema:
            type: integer
          example: '1'
        -
          name: per_page
          in: query
          required: false
          description: 'Éléments par page (1-100)'
          schema:
            type: integer
          example: '25'
      responses:
        200:
          description: Succès
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { type: array, items: { type: object } }
                  meta: { type: object, properties: { current_page: { type: integer }, from: { type: integer }, last_page: { type: integer }, per_page: { type: integer }, to: { type: integer }, total: { type: integer } } }
              example:
                success: true
                data:
                  - { id: aBc123Xy, lead_id: dEf456Zw, lead_name: 'Jean Dupont', company_name: 'TechCorp SAS', format: a6, status: delivered, tracking_code: BATCH-20250115-abc123, is_printed: false, printed_at: null, campaign: { id: gHi789Uv, name: 'Campagne E-commerce', is_self_print: true, print_files: { front_url: 'https://cdn.manuscry.com/letters/front_abc123.png', back_url: 'https://cdn.manuscry.com/letters/back_abc123.png' } }, created_at: '2025-01-15T10:30:00.000000Z', updated_at: '2025-01-15T10:30:00.000000Z' }
                meta:
                  current_page: 1
                  from: 1
                  last_page: 1
                  per_page: 25
                  to: 25
                  total: 15
        400:
          description: 'Requête invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        401:
          description: 'Non autorisé - Clé API manquante ou invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        403:
          description: 'Accès refusé - Campaign/Agent en pause ou annulé'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        404:
          description: 'Ressource non trouvée'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        422:
          description: 'Erreur de validation'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        429:
          description: 'Trop de requêtes - Rate limit dépassé (100 req/min)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        500:
          description: 'Erreur serveur interne'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /letters/to-validate:
    get:
      operationId: get-letters-to-validate
      summary: 'Courriers en attente de validation'
      description: 'Récupère la liste des courriers en attente de validation.'
      security:
        -
          BearerAuth: {  }
        -
          ApiKeyAuth: {  }
      tags:
        - Courriers
      parameters:
        -
          name: page
          in: query
          required: false
          description: 'Numéro de page'
          schema:
            type: integer
          example: '1'
        -
          name: per_page
          in: query
          required: false
          description: 'Éléments par page (1-100)'
          schema:
            type: integer
          example: '12'
        -
          name: campaign_filter
          in: query
          required: false
          description: 'ID hashé de la campagne (recommandé - alias agent_filter accepté)'
          schema:
            type: string
      responses:
        200:
          description: Succès
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { type: array, items: { type: object } }
                  meta: { type: object, properties: { current_page: { type: integer }, from: { type: integer }, last_page: { type: integer }, per_page: { type: integer }, to: { type: integer }, total: { type: integer } } }
              example:
                success: true
                data:
                  - { id: aBc123Xy, lead_id: dEf456Zw, lead_name: 'Jean Dupont', company_name: 'TechCorp SAS', format: a6, status: validating, tracking_code: BATCH-20240115-abc123, content_preview: 'Bonjour Jean, je vous contacte...', has_qr_code: true, campaign: { id: gHi789Uv, name: 'Campagne Printemps 2024' }, address: { line1: 'TechCorp SAS', line2: 'Jean Dupont', line3: '123 rue de Rivoli', zip: '75001', city: Paris, country: FRANCE }, created_at: '2024-01-15T10:30:00.000000Z', autovalidated_at: null, actions: { validate: 'https://manuscry.com/api/letters/validate/aBc123Xy', reject: 'https://manuscry.com/api/letters/reject/aBc123Xy' } }
                meta:
                  current_page: 1
                  from: 1
                  last_page: 2
                  per_page: 12
                  to: 12
                  total: 23
        400:
          description: 'Requête invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        401:
          description: 'Non autorisé - Clé API manquante ou invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        403:
          description: 'Accès refusé - Campaign/Agent en pause ou annulé'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        404:
          description: 'Ressource non trouvée'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        422:
          description: 'Erreur de validation'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        429:
          description: 'Trop de requêtes - Rate limit dépassé (100 req/min)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        500:
          description: 'Erreur serveur interne'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  '/letters/validate/{letterId}':
    post:
      operationId: validate-letter
      summary: 'Valider un courrier'
      description: 'Valide un courrier pour envoi automatique.'
      security:
        -
          BearerAuth: {  }
        -
          ApiKeyAuth: {  }
      tags:
        - Courriers
      parameters:
        -
          name: letterId
          in: path
          required: true
          description: 'ID hashé du courrier'
          schema:
            type: string
          example: aBc123Xy
        -
          name: blocks
          in: query
          required: false
          description: 'Blocs de contenu personnalisés'
          schema:
            type: array
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                blocks:
                  type: array
                  items: { type: object }
            example:
              blocks:
                -
                  id: block_123
                  content: 'Contenu personnalisé'
      responses:
        201:
          description: Succès
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  message: { type: string }
                  data: { type: object, properties: { letter_id: { type: string }, scheduled_validate_at: { type: string }, validation_delay_minutes: { type: integer } } }
              example:
                success: true
                message: 'Letter validated successfully - will be sent automatically in 60min'
                data:
                  letter_id: aBc123Xy
                  scheduled_validate_at: '2024-01-22T11:30:00.000000Z'
                  validation_delay_minutes: 60
        400:
          description: 'Requête invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        401:
          description: 'Non autorisé - Clé API manquante ou invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        403:
          description: 'Accès refusé - Campaign/Agent en pause ou annulé'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        404:
          description: 'Ressource non trouvée'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        422:
          description: 'Erreur de validation'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        429:
          description: 'Trop de requêtes - Rate limit dépassé (100 req/min)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        500:
          description: 'Erreur serveur interne'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  '/letters/reject/{letterId}':
    post:
      operationId: reject-letter
      summary: 'Refuser un courrier'
      description: 'Refuse un courrier.'
      security:
        -
          BearerAuth: {  }
        -
          ApiKeyAuth: {  }
      tags:
        - Courriers
      parameters:
        -
          name: letterId
          in: path
          required: true
          description: 'ID hashé du courrier'
          schema:
            type: string
          example: aBc123Xy
      responses:
        201:
          description: Succès
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  message: { type: string }
                  data: { type: object, properties: { letter_id: { type: string }, scheduled_delete_at: { type: string }, validation_delay_minutes: { type: integer } } }
              example:
                success: true
                message: 'Letter rejected successfully - will be deleted automatically in 60min'
                data:
                  letter_id: aBc123Xy
                  scheduled_delete_at: '2024-01-22T11:30:00.000000Z'
                  validation_delay_minutes: 60
        400:
          description: 'Requête invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        401:
          description: 'Non autorisé - Clé API manquante ou invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        403:
          description: 'Accès refusé - Campaign/Agent en pause ou annulé'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        404:
          description: 'Ressource non trouvée'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        422:
          description: 'Erreur de validation'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        429:
          description: 'Trop de requêtes - Rate limit dépassé (100 req/min)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        500:
          description: 'Erreur serveur interne'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  '/letters/mark-printed/{letterId}':
    post:
      operationId: mark-letter-printed
      summary: 'Marquer un courrier comme imprimé'
      description: 'Marque un courrier comme imprimé dans votre système. Permet de suivre les courriers que vous avez déjà imprimés.'
      security:
        -
          BearerAuth: {  }
        -
          ApiKeyAuth: {  }
      tags:
        - 'Impression autonome'
      parameters:
        -
          name: letterId
          in: path
          required: true
          description: 'ID hashé du courrier à marquer comme imprimé'
          schema:
            type: string
          example: aBc123Xy
      responses:
        201:
          description: Succès
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  message: { type: string }
                  data: { type: object, properties: { letter_id: { type: string }, is_printed: { type: boolean }, printed_at: { type: string } } }
              example:
                success: true
                message: 'Letter marked as printed successfully'
                data:
                  letter_id: aBc123Xy
                  is_printed: true
                  printed_at: '2025-01-22T14:30:00.000000Z'
        400:
          description: 'Requête invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        401:
          description: 'Non autorisé - Clé API manquante ou invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        403:
          description: 'Accès refusé - Campaign/Agent en pause ou annulé'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        404:
          description: 'Ressource non trouvée'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        422:
          description: 'Erreur de validation'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        429:
          description: 'Trop de requêtes - Rate limit dépassé (100 req/min)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        500:
          description: 'Erreur serveur interne'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  '/partners/campaigns/{campaign_id}/preview':
    post:
      operationId: partner-preview
      summary: "Preview d'un courrier"
      description: "Génère une image de preview d'un courrier avec des valeurs personnalisées. Cette route est réservée aux partenaires intégrant un éditeur de courriers dans leur propre application. L'accès est soumis à activation manuelle par l'équipe Manuscry."
      security:
        -
          BearerAuth: {  }
        -
          ApiKeyAuth: {  }
      tags:
        - 'API Partenaire'
      parameters:
        -
          name: campaign_id
          in: path
          required: true
          description: "ID hashé de la campagne (visible dans l'URL de votre campagne)"
          schema:
            type: string
          example: abc123def456
        -
          name: fields
          in: query
          required: true
          description: "Objet contenant les valeurs des champs personnalisés à injecter dans le template du courrier. Les clés correspondent aux variables {variable} configurées dans l'éditeur de la campagne."
          schema:
            type: object
          example: '{"firstname": "Jean", "lastname": "Dupont", "company": "TechCorp SAS"}'
        -
          name: surface
          in: query
          required: false
          description: 'Surface à rendre : "front" (recto, par défaut) ou "back" (verso - formats carte a6, dl, a5)'
          schema:
            type: string
          example: front
        -
          name: width
          in: query
          required: false
          description: "Largeur de l'image de sortie en pixels (200-1500, défaut: 450). Le rendu est effectué en haute définition puis redimensionné à la largeur demandée."
          schema:
            type: integer
          example: '450'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                fields:
                  type: object
                  properties: { firstname: { type: string }, lastname: { type: string }, company: { type: string }, city: { type: string } }
                surface:
                  type: string
                width:
                  type: integer
            example:
              fields:
                firstname: Jean
                lastname: Dupont
                company: 'TechCorp SAS'
                city: Paris
              surface: front
              width: 450
      responses:
        201:
          description: Succès
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
        400:
          description: 'Requête invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        401:
          description: 'Non autorisé - Clé API manquante ou invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        403:
          description: 'Accès refusé - Campaign/Agent en pause ou annulé'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        404:
          description: 'Ressource non trouvée'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        422:
          description: 'Erreur de validation'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        429:
          description: 'Trop de requêtes - Rate limit dépassé (100 req/min)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        500:
          description: 'Erreur serveur interne'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  '/campaigns/{campaignId}/calendar-events':
    get:
      operationId: get-calendar-events
      summary: 'Historique des rendez-vous calendrier'
      description: "Récupère l'historique des rendez-vous pris via la page de booking calendrier d'une campagne."
      security:
        -
          BearerAuth: {  }
        -
          ApiKeyAuth: {  }
      tags:
        - Calendrier
      parameters:
        -
          name: campaignId
          in: path
          required: true
          description: 'ID hashé de la campagne'
          schema:
            type: string
          example: aBc123Xy
        -
          name: page
          in: query
          required: false
          description: 'Numéro de page'
          schema:
            type: integer
          example: '1'
        -
          name: per_page
          in: query
          required: false
          description: 'Éléments par page (1-50)'
          schema:
            type: integer
          example: '10'
      responses:
        200:
          description: Succès
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  campaign: { type: object, properties: { id: { type: string }, name: { type: string } } }
                  data: { type: array, items: { type: object } }
                  meta: { type: object, properties: { current_page: { type: integer }, from: { type: integer }, last_page: { type: integer }, per_page: { type: integer }, to: { type: integer }, total: { type: integer } } }
              example:
                success: true
                campaign:
                  id: aBc123Xy
                  name: 'Campagne Printemps 2024'
                data:
                  - { id: xYz789Qw, contact_name: 'Marie Durand', contact_email: marie.durand@example.com, contact_firstname: Marie, contact_lastname: Durand, contact_phone: '+33612345678', scheduled_at: '2024-01-25T10:00:00.000000Z', timezone: Europe/Paris, google_event_id: abc123xyz, google_event_link: 'https://calendar.google.com/event?eid=abc123xyz', status: success, created_at: '2024-01-22T14:30:00.000000Z' }
                meta:
                  current_page: 1
                  from: 1
                  last_page: 2
                  per_page: 10
                  to: 10
                  total: 15
        400:
          description: 'Requête invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        401:
          description: 'Non autorisé - Clé API manquante ou invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        403:
          description: 'Accès refusé - Campaign/Agent en pause ou annulé'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        404:
          description: 'Ressource non trouvée'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        422:
          description: 'Erreur de validation'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        429:
          description: 'Trop de requêtes - Rate limit dépassé (100 req/min)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        500:
          description: 'Erreur serveur interne'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /find-postal-address:
    post:
      operationId: find-postal-address
      summary: 'Rechercher une adresse postale'
      description: "Lance une recherche d'adresse postale à partir d'une URL LinkedIn ou d'informations de contact."
      security:
        -
          BearerAuth: {  }
        -
          ApiKeyAuth: {  }
      tags:
        - "Recherche d'adresses"
      parameters:
        -
          name: linkedin_url
          in: query
          required: false
          description: 'URL complète du profil LinkedIn (requis si contact_info absent)'
          schema:
            type: string
          example: 'https://www.linkedin.com/in/johndoe/'
        -
          name: contact_info
          in: query
          required: false
          description: 'Informations de contact (requis si linkedin_url absent)'
          schema:
            type: object
        -
          name: contact_info.first_name
          in: query
          required: true
          description: 'Prénom de la personne'
          schema:
            type: string
          example: John
        -
          name: contact_info.last_name
          in: query
          required: true
          description: 'Nom de la personne'
          schema:
            type: string
          example: Doe
        -
          name: contact_info.company_name
          in: query
          required: true
          description: "Nom de l'entreprise"
          schema:
            type: string
          example: 'Acme Corp'
        -
          name: contact_info.additional_info
          in: query
          required: false
          description: 'Informations complémentaires (poste, secteur, localisation)'
          schema:
            type: string
          example: 'CEO, secteur tech, Paris'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                linkedin_url:
                  type: string
            example:
              linkedin_url: 'https://www.linkedin.com/in/johndoe/'
      responses:
        201:
          description: Succès
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  message: { type: string }
                  data: { type: object, properties: { search_id: { type: string }, status: { type: string }, estimated_completion: { type: string }, lead_data: { type: object, properties: { first_name: { type: string }, last_name: { type: string }, company_name: { type: string }, linkedin_url: { type: string }, created_at: { type: string } } }, credits_remaining: { type: integer } } }
              example:
                success: true
                message: 'Address search initiated successfully'
                data:
                  search_id: abc123def456
                  status: processing
                  estimated_completion: 'Within 5-10 minutes'
                  lead_data: { first_name: John, last_name: Doe, company_name: 'Acme Corp', linkedin_url: 'https://www.linkedin.com/in/johndoe/', created_at: '2025-09-25T10:30:00Z' }
                  credits_remaining: 45
        400:
          description: 'Requête invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        401:
          description: 'Non autorisé - Clé API manquante ou invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        403:
          description: 'Accès refusé - Campaign/Agent en pause ou annulé'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        404:
          description: 'Ressource non trouvée'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        422:
          description: 'Erreur de validation'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        429:
          description: 'Trop de requêtes - Rate limit dépassé (100 req/min)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        500:
          description: 'Erreur serveur interne'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  '/find-postal-address/{search_id}':
    get:
      operationId: get-postal-address-results
      summary: "Récupérer les résultats d'une recherche"
      description: "Récupère les résultats d'une recherche d'adresse en utilisant l'ID retourné lors de la création."
      security:
        -
          BearerAuth: {  }
        -
          ApiKeyAuth: {  }
      tags:
        - "Recherche d'adresses"
      parameters:
        -
          name: search_id
          in: path
          required: true
          description: 'ID de la recherche retourné lors de la création'
          schema:
            type: string
          example: abc123def456
      responses:
        200:
          description: Succès
          content:
            application/json:
              schema:
                type: object
                properties:
                  success: { type: boolean }
                  data: { type: object, properties: { search_id: { type: string }, status: { type: string }, search_data: { type: object, properties: { first_name: { type: string }, last_name: { type: string }, company_name: { type: string }, linkedin_url: { type: string }, created_at: { type: string }, processed_at: { type: string } } }, results: { type: object, properties: { success: { type: boolean }, total_addresses_found: { type: integer }, search_summary: { type: string }, addresses: { type: array, items: { type: object } } } } } }
              example:
                success: true
                data:
                  search_id: abc123def456
                  status: completed
                  search_data: { first_name: John, last_name: Doe, company_name: 'Acme Corporation', linkedin_url: 'https://www.linkedin.com/in/johndoe/', created_at: '2025-09-25T10:30:00Z', processed_at: '2025-09-25T10:35:00Z' }
                  results: { success: true, total_addresses_found: 2, search_summary: '2 adresses uniques trouvées via LinkedIn API et crawling web', addresses: [{ confidence_score: 9, sources_summary: 'LinkedIn API officielle', source_count: 1, address: { address_line1: 'Acme Corporation', address_line2: 'John Doe', address_line3: '12 rue de la Paix', address_zip: '75001', address_city: Paris, address_country: France, address_full: 'Acme Corporation, John Doe, 12 rue de la Paix, 75001 Paris, France' } }, { confidence_score: 7, sources_summary: 'Crawling site web entreprise', source_count: 1, address: { address_line1: 'Acme Corp - Siège Social', address_line2: 'John Doe', address_line3: '456 Avenue des Champs-Élysées', address_zip: '75008', address_city: Paris, address_country: France, address_full: 'Acme Corp - Siège Social, John Doe, 456 Avenue des Champs-Élysées, 75008 Paris, France' } }] }
        400:
          description: 'Requête invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        401:
          description: 'Non autorisé - Clé API manquante ou invalide'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        403:
          description: 'Accès refusé - Campaign/Agent en pause ou annulé'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        404:
          description: 'Ressource non trouvée'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        422:
          description: 'Erreur de validation'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        429:
          description: 'Trop de requêtes - Rate limit dépassé (100 req/min)'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        500:
          description: 'Erreur serveur interne'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: 'API Key'
      description: 'Utilisez votre clé API dans le header Authorization: Bearer YOUR_API_KEY'
    ApiKeyAuth:
      type: apiKey
      in: query
      name: api_key
      description: 'Utilisez votre clé API en paramètre URL: ?api_key=YOUR_API_KEY'
  schemas:
    Error:
      type: object
      properties:
        success:
          type: boolean
          example: false
        error:
          type: string
          example: 'Error message'
        message:
          type: string
          example: 'Detailed error description'
    ValidationError:
      type: object
      properties:
        success:
          type: boolean
          example: false
        error:
          type: string
          example: 'Validation failed'
        message:
          type: string
          example: 'The provided data contains errors'
        validation_errors:
          type: object
          additionalProperties:
            type: string
          example:
            email: 'Le champ email est obligatoire'
        missing_fields:
          type: array
          items:
            type: string
          example:
            - email
            - name
        invalid_fields:
          type: array
          items:
            type: string
          example:
            - phone
    Pagination:
      type: object
      properties:
        current_page:
          type: integer
          example: 1
        from:
          type: integer
          example: 1
        last_page:
          type: integer
          example: 10
        per_page:
          type: integer
          example: 25
        to:
          type: integer
          example: 25
        total:
          type: integer
          example: 250
