openapi: 3.0.3
info:
  title: Omogen — API pour partenaires ATS
  version: "1.1.0"
  description: |
    API d'intégration pour les partenaires ATS (Applicant Tracking Systems).
    Permet de synchroniser les offres d'emploi et de créer des processus d'entretien,
    avec réception des rapports d'évaluation via webhooks.

    **⚠️ API en cours de construction.** Contact : [gillian@omogen.ai](mailto:gillian@omogen.ai).
servers:
  - url: https://api.omogen.ai/api/v1/integration-partner
    description: Production

tags:
  - name: Configuration
    description: Récupération des paramètres de configuration
  - name: Jobs
    description: Synchronisation des offres d'emploi
  - name: Applications
    description: Création de processus d'entretien
  - name: Webhooks
    description: Payloads envoyés par Omogen au partenaire

security:
  - BearerToken: []
    OmPartner: []

paths:
  /interview-params:
    get:
      tags: [Configuration]
      summary: Get Interview Params
      description: |
        Récupère les paramètres de configuration disponibles pour les entretiens.
        Permet aux utilisateurs de choisir d'activer Omogen et de sélectionner un mode de déploiement.

        ⚠️ Les paramètres exacts peuvent être personnalisés pour améliorer l'expérience utilisateur dans l'ATS.
      operationId: getInterviewParams
      parameters:
        - name: lang
          in: query
          required: false
          schema:
            type: string
            enum: [fr, en]
            default: fr
          description: Code langue des libellés retournés
      responses:
        "200":
          description: Paramètres récupérés avec succès
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InterviewParamsResponse"
              example:
                success: true
                data:
                  use_cases:
                    - value: prequalification
                      label: Préqualification
                    - value: reactivation
                      label: Réactivation
                  deployment_templates:
                    - value: single-call
                      label: Appel unique
                      description: Un appel immédiat au candidat.
                    - value: sms-then-call
                      label: SMS puis appel
                      description: Envoi d'un SMS puis appel quelques minutes plus tard.
                  report_limit:
                    default: 100
                    min: 1
                    max: 1000
                    label: Limite de rapports
                    description: Seuil d'arrêt automatique sur les rapports d'entretien.
                  match_limit:
                    default: 100
                    min: 1
                    max: 1000
                    label: Limite de matchs
                    description: Seuil d'arrêt automatique sur les candidats qui matchent.
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /jobs:
    post:
      tags: [Jobs]
      summary: Sync a Job Offer
      description: |
        Synchronise une offre d'emploi dans Omogen. Le endpoint retourne immédiatement
        (201 Created) avec l'`id` de la job et un `edit_url`. L'extraction des critères
        de sélection et la génération du plan d'entretien se font **de manière asynchrone**
        en arrière-plan.

        **ℹ️ Cet endpoint est utilisé uniquement dans le Flow 1 (Push).** Dans le Flow 2,
        les jobs sont créées automatiquement lors de la réception d'une candidature.

        **Idempotence :** si `external_job_id` est fourni et qu'une job avec cet identifiant
        existe déjà pour l'organisation, l'endpoint retourne **200 OK** au lieu de 201 et
        ne recrée pas la job.
      operationId: syncJob
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SyncJobRequest"
            example:
              job_title: Développeur Full Stack Python/React
              job_description: >-
                Nous recherchons un développeur full stack expérimenté pour rejoindre notre équipe.
                Vous travaillerez sur des projets innovants utilisant Python, React et des
                technologies cloud modernes. Minimum 3 ans d'expérience requis.
              external_job_id: JOB-12345
      responses:
        "201":
          description: Job synchronisée, traitement asynchrone en cours
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SyncJobResponse"
              example:
                success: true
                message: Job synchronized, async processing in progress
                data:
                  job_opportunity_id: 550e8400-e29b-41d4-a716-446655440000
                  edit_url: https://app.omogen.ai/jobs/550e8400-e29b-41d4-a716-446655440000
        "200":
          description: Job déjà existante (idempotence sur `external_job_id`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SyncJobResponse"
              example:
                success: true
                message: Job already exists
                data:
                  job_opportunity_id: 550e8400-e29b-41d4-a716-446655440000
                  edit_url: https://app.omogen.ai/jobs/550e8400-e29b-41d4-a716-446655440000
        "400":
          description: |
            Paramètres invalides. Codes possibles :
            - `INVALID_PAYLOAD` : champs manquants ou mal formés (`job_title`, `job_description`)
            - `VALIDATION_ERROR` : erreur de validation métier
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                success: false
                error: "job_description: This field is required."
                code: INVALID_PAYLOAD
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  /applications:
    post:
      tags: [Applications]
      summary: Sync an Application (Create Interview Process)
      description: |
        Crée ou met à jour un candidat et crée un processus d'entretien (process) pour celui-ci.
        Le candidat sera contacté selon le mode de déploiement spécifié.

        ### Comportement selon le flow

        - **Flow 1 (Push)** : l'`external_job_id` doit correspondre à une job déjà synchronisée via `POST /jobs`.
        - **Flow 2 (Pull)** : Omogen récupère automatiquement les infos de l'offre via l'endpoint du partenaire.
        - **Mode triage** : si aucun `external_job_id` n'est fourni et que le mode triage est activé, Omogen
          utilise le CV pour déterminer automatiquement la job cible.

        Pour un appel à cet endpoint qui n'inclut que le CV (sans données candidat),
        utiliser plutôt `POST /applications/cv-only`.
      operationId: syncApplication
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SyncApplicationRequest"
            example:
              external_application_id: APP-456
              external_job_id: JOB-12345
              external_candidate_id: CAND-789
              first_name: Marie
              last_name: Dubois
              email: marie.dubois@example.com
              phone: "+33612345678"
              use_case: prequalification
              deployment_template: single-call
              cv_url: https://example.com/cv/marie-dubois.pdf
      responses:
        "201":
          description: Application synchronisée et processus créé avec succès
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SyncApplicationResponse"
              example:
                success: true
                message: Application synchronized and process created successfully
                data:
                  process_id: c50e8400-e29b-41d4-a716-446655440007
                  status: in_progress
        "200":
          description: Application déjà existante (idempotence)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SyncApplicationResponse"
              example:
                success: true
                message: Application already exists
                data:
                  process_id: c50e8400-e29b-41d4-a716-446655440007
                  status: in_progress
        "400":
          description: |
            Paramètres invalides ou erreur de validation métier. Codes possibles :
            - `INVALID_PAYLOAD` : payload mal formé (email, téléphone, champ manquant)
            - `VALIDATION_ERROR` : erreur de validation générique
            - `JOB_FETCH_NOT_FOUND` : l'`external_job_id` n'existe pas côté partenaire (Pull flow)
            - `PULL_FLOW_CONFIG_ERROR` : le `job_fetch_url` n'est pas correctement configuré
            - `CV_DOWNLOAD_FAILED` : échec du téléchargement du CV depuis `cv_url`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                invalidPayload:
                  summary: Payload invalide
                  value:
                    success: false
                    error: "email: Enter a valid email address."
                    code: INVALID_PAYLOAD
                jobNotFound:
                  summary: Job introuvable côté partenaire (Pull)
                  value:
                    success: false
                    error: Partner returned 404 for job JOB-12345
                    code: JOB_FETCH_NOT_FOUND
                cvDownloadFailed:
                  summary: Téléchargement du CV échoué
                  value:
                    success: false
                    error: Failed to download CV from the provided URL
                    code: CV_DOWNLOAD_FAILED
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"
        "502":
          description: |
            Erreur upstream lors de la récupération de l'offre depuis l'endpoint partenaire
            (Flow 2 — Pull). Codes possibles :
            - `JOB_FETCH_NETWORK_ERROR` : erreur réseau en contactant l'ATS partenaire
            - `JOB_FETCH_HTTP_ERROR` : l'ATS partenaire a retourné un statut 4xx/5xx
            - `JOB_FETCH_INVALID_RESPONSE` : réponse de l'ATS partenaire non parsable (JSON invalide)
            - `JOB_FETCH_MISSING_TITLE` : la réponse de l'ATS partenaire ne contient pas `job_title`
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                success: false
                error: "Partner API returned HTTP 500 for job JOB-12345"
                code: JOB_FETCH_HTTP_ERROR

  /applications/cv-only:
    post:
      tags: [Applications]
      summary: Sync an Application from CV only
      description: |
        Crée un processus d'entretien à partir d'un CV seul. Omogen extrait automatiquement
        les informations candidat (nom, prénom, email, téléphone) depuis le CV via un service
        d'extraction. Si l'extraction échoue pour certains champs, les valeurs de secours
        fournies dans la requête sont utilisées.

        **Format de la requête :** `multipart/form-data` ou `application/json`.
        Le CV peut être fourni soit en base64 via `cv_file`, soit via une URL publique via
        `cv_url`. **L'un des deux est obligatoire.**

        **Extensions supportées :** `.pdf`, `.doc`, `.docx`, `.txt`, `.rtf`.
      operationId: syncCvOnlyApplication
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/SyncCvOnlyApplicationRequest"
          application/json:
            schema:
              $ref: "#/components/schemas/SyncCvOnlyApplicationRequest"
            example:
              external_application_id: APP-789
              external_candidate_id: CAND-789
              cv_url: https://example.com/cv/marie-dubois.pdf
              cv_filename: marie-dubois.pdf
              external_job_id: JOB-12345
              use_case: prequalification
              first_name: Marie
              last_name: Dubois
              email: marie.dubois@example.com
              phone: "+33612345678"
      responses:
        "201":
          description: Application créée avec succès depuis le CV
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SyncApplicationResponse"
              example:
                success: true
                message: Application synchronized and process created successfully
                data:
                  process_id: c50e8400-e29b-41d4-a716-446655440007
                  status: in_progress
        "200":
          description: Application déjà existante (idempotence)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SyncApplicationResponse"
        "400":
          description: |
            Paramètres invalides ou échec d'extraction du CV. Codes possibles :
            - `INVALID_PAYLOAD` : payload mal formé (extension de fichier non supportée, `cv_file` et `cv_url` absents, email/téléphone invalide)
            - `VALIDATION_ERROR` : erreur de validation générique
            - `CV_DOWNLOAD_FAILED` : échec du téléchargement du CV depuis `cv_url` ou décodage base64
            - `CV_INVALID_INPUT` : le contenu du CV n'est pas un document valide
            - `CV_EXTRACTION_FAILED` : échec du parsing du CV (format non reconnu, contenu illisible)
            - `CV_PROCESSING_ERROR` : erreur interne pendant l'extraction
            - `CV_EXTRACTION_UNEXPECTED_ERROR` : erreur inattendue pendant l'extraction
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CvExtractionErrorResponse"
              examples:
                extensionInvalide:
                  summary: Extension non supportée
                  value:
                    success: false
                    error: "cv_filename: Unsupported file extension. Allowed: .pdf, .doc, .docx, .txt, .rtf"
                    code: INVALID_PAYLOAD
                cvFetchFailed:
                  summary: Téléchargement échoué
                  value:
                    success: false
                    error: Failed to download CV from URL
                    code: CV_DOWNLOAD_FAILED
                    details:
                      url: https://example.com/cv/broken.pdf
                      status_code: 404
                extractionFailed:
                  summary: Parsing du CV échoué
                  value:
                    success: false
                    error: Unable to extract candidate information from CV
                    code: CV_EXTRACTION_FAILED
                    details:
                      reason: document_unreadable
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/ServerError"

  # ─── Webhooks ──────────────────────────────────────────────────────────────
  # Les webhooks sont envoyés PAR Omogen VERS le partenaire. On les documente
  # ici comme des "requêtes sortantes" à implémenter côté partenaire — les
  # schémas complets sont dans la section components.
  /webhooks/report-generated:
    post:
      tags: [Webhooks]
      summary: Webhook - Report Generated
      description: |
        **Webhook envoyé par Omogen** lorsqu'un processus atteint un état terminal
        (completed, failed, expired, canceled, incomplete). C'est l'événement
        principal à implémenter côté partenaire.

        **À implémenter côté partenaire :** un endpoint HTTP qui accepte ce payload
        et répond avec un statut 2xx. L'URL de destination, la méthode (POST ou PUT)
        et les headers d'authentification sont configurés dans Omogen.
      operationId: webhookReportGenerated
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReportGeneratedWebhook"
      responses:
        "200":
          description: Webhook reçu (le partenaire doit répondre 2xx)

  /webhooks/call-booked:
    post:
      tags: [Webhooks]
      summary: Webhook - Call Booked
      description: |
        **Webhook envoyé par Omogen** lorsqu'un candidat a réservé un rendez-vous
        d'appel (auto-planification via SMS ou email). Activé uniquement si la
        fonctionnalité est configurée pour l'organisation.
      operationId: webhookCallBooked
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallBookedWebhook"
      responses:
        "200":
          description: Webhook reçu (le partenaire doit répondre 2xx)

  /webhooks/process-rejected:
    post:
      tags: [Webhooks]
      summary: Webhook - Process Rejected
      description: |
        **Webhook envoyé par Omogen** lorsqu'un candidat est rejeté avant même la
        création d'un processus (échec de l'eligibility check, job inéligible, etc.).
        Utile pour informer l'ATS que la candidature ne sera pas traitée.

        Contrairement à `report-generated`, aucun processus n'est créé — le champ
        `process_id` peut être `null`.
      operationId: webhookProcessRejected
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProcessRejectedWebhook"
      responses:
        "200":
          description: Webhook reçu (le partenaire doit répondre 2xx)

components:
  securitySchemes:
    BearerToken:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: "Clé API Omogen envoyée dans le header `Authorization: Bearer <API_KEY>`. Fournie par le support Omogen."
    OmPartner:
      type: apiKey
      in: header
      name: OM-PARTNER
      description: "Clé partenaire unique envoyée dans le header `OM-PARTNER: <PARTNER_KEY>`. Identifie l'ATS source."

  responses:
    Unauthorized:
      description: |
        Clé API invalide ou manquante. Code possible :
        - `AUTHENTICATION_FAILED` : authentification échouée
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error: Invalid or missing API key
            code: AUTHENTICATION_FAILED
    ServerError:
      description: |
        Erreur serveur inattendue. Code possible :
        - `INTERNAL_ERROR` : erreur interne non catégorisée
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error: Internal server error
            code: INTERNAL_ERROR

  schemas:
    # ─── Erreurs ─────────────────────────────────────────────────────────────
    ErrorResponse:
      type: object
      required: [success, error, code]
      properties:
        success:
          type: boolean
          example: false
        error:
          type: string
          description: Message d'erreur lisible
        code:
          type: string
          description: Code d'erreur machine-readable (stable)

    CvExtractionErrorResponse:
      allOf:
        - $ref: "#/components/schemas/ErrorResponse"
        - type: object
          properties:
            details:
              type: object
              nullable: true
              description: Diagnostic structuré (URL, status, raison technique)
              additionalProperties: true

    # ─── Interview params ───────────────────────────────────────────────────
    InterviewParamsResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            use_cases:
              type: array
              items:
                type: object
                properties:
                  value:
                    type: string
                    enum: [prequalification, reactivation]
                  label:
                    type: string
            deployment_templates:
              type: array
              items:
                type: object
                properties:
                  value:
                    type: string
                  label:
                    type: string
                  description:
                    type: string
            report_limit:
              $ref: "#/components/schemas/LimitConfig"
            match_limit:
              $ref: "#/components/schemas/LimitConfig"

    LimitConfig:
      type: object
      properties:
        default:
          type: integer
        min:
          type: integer
        max:
          type: integer
        label:
          type: string
        description:
          type: string

    # ─── Sync Job ───────────────────────────────────────────────────────────
    SyncJobRequest:
      type: object
      required: [job_title, job_description]
      properties:
        job_title:
          type: string
          description: "Titre du poste (ex : « Développeur Full Stack »)"
        job_description:
          type: string
          description: Description complète du poste
        external_job_id:
          type: string
          description: |
            ID de l'offre dans le système partenaire. Si fourni, l'endpoint est
            idempotent sur cet id : un second appel avec le même `external_job_id`
            retourne la même `job_opportunity_id` sans recréer la job.

    SyncJobResponse:
      type: object
      properties:
        success:
          type: boolean
        message:
          type: string
        data:
          type: object
          properties:
            job_opportunity_id:
              type: string
              format: uuid
              description: ID interne Omogen de la job
            edit_url:
              type: string
              format: uri
              description: URL de l'interface Omogen pour éditer la job

    # ─── Sync Application ──────────────────────────────────────────────────
    SyncApplicationRequest:
      type: object
      required:
        - external_application_id
        - external_candidate_id
        - first_name
        - last_name
        - email
      properties:
        external_application_id:
          type: string
          description: ID de la candidature dans le système partenaire
        external_job_id:
          type: string
          description: |
            ID de l'offre dans le système partenaire. Optionnel uniquement si le
            mode triage est activé pour l'organisation.
        external_candidate_id:
          type: string
          description: ID du candidat dans le système partenaire
        first_name:
          type: string
        last_name:
          type: string
        email:
          type: string
          format: email
        phone:
          type: string
          description: Numéro au format international (`+33612345678`) ou local (`0612345678`)
        use_case:
          type: string
          enum: [prequalification, reactivation]
          default: prequalification
        deployment_template:
          type: string
          description: "Mode de déploiement (ex : `single-call`, `sms-then-call`)"
        cv_url:
          type: string
          format: uri
          description: URL publique du CV du candidat
        job_title:
          type: string
          description: "Titre du poste (Flow 1 — requis si la job n'est pas préalablement synchronisée)"
        job_description:
          type: string
          description: "Description du poste (Flow 1)"

    SyncCvOnlyApplicationRequest:
      type: object
      required:
        - external_application_id
        - external_candidate_id
        - cv_filename
      properties:
        external_application_id:
          type: string
        external_candidate_id:
          type: string
        cv_file:
          type: string
          format: byte
          description: "Contenu du CV encodé en base64. Exclusif avec `cv_url` — au moins l'un des deux est requis."
        cv_url:
          type: string
          format: uri
          description: "URL publique vers le CV. Exclusif avec `cv_file` — au moins l'un des deux est requis."
        cv_filename:
          type: string
          description: "Nom du fichier avec extension (.pdf, .doc, .docx, .txt, .rtf)"
        first_name:
          type: string
          description: Prénom de secours si l'extraction depuis le CV échoue
        last_name:
          type: string
          description: Nom de secours si l'extraction depuis le CV échoue
        email:
          type: string
          format: email
          description: Email de secours si l'extraction depuis le CV échoue
        phone:
          type: string
          description: Téléphone de secours si l'extraction depuis le CV échoue
        external_job_id:
          type: string
          description: ID de l'offre dans le système partenaire. Optionnel si mode triage.
        use_case:
          type: string
          enum: [prequalification, reactivation]
          default: prequalification
        deployment_template:
          type: string
        job_title:
          type: string
        job_description:
          type: string

    SyncApplicationResponse:
      type: object
      properties:
        success:
          type: boolean
        message:
          type: string
        data:
          type: object
          properties:
            process_id:
              type: string
              format: uuid
            status:
              type: string
              enum: [in_progress, active, completed, incomplete, failed, expired, canceled]

    # ─── Sub-schemas utilisés par les webhooks ─────────────────────────────
    WebhookCandidate:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: ID interne Omogen du candidat
        external_id:
          type: string
          nullable: true
          description: ID du candidat côté partenaire (via application bridge)
        first_name:
          type: string
        last_name:
          type: string
        email:
          type: string
          format: email
          nullable: true
        phone:
          type: string
          description: Format E.164

    InterviewReport:
      type: object
      nullable: true
      properties:
        status:
          type: string
          enum: [match, no_match, na]
        report_status:
          type: string
          enum: [in_progress, completed, failed]
        overall_score:
          type: number
          format: float
          minimum: 0
          maximum: 1
        is_complete:
          type: boolean
        summary:
          type: string
        evaluations:
          type: array
          items:
            $ref: "#/components/schemas/Evaluation"
        data_points:
          type: array
          items:
            $ref: "#/components/schemas/DataPoint"

    CvReport:
      type: object
      nullable: true
      properties:
        status:
          type: string
          enum: [completed, failed]
        match_result:
          type: string
          enum: [match, no_match]
        overall_score:
          type: number
          format: float
          minimum: 0
          maximum: 1

    Evaluation:
      type: object
      properties:
        criteria_id:
          type: string
          nullable: true
        criteria_name:
          type: string
        value:
          type: string
          nullable: true
        is_mentioned:
          type: boolean
        is_satisfied:
          type: boolean
        is_knockout:
          type: boolean

    DataPoint:
      type: object
      properties:
        criteria_id:
          type: string
          nullable: true
        criteria_name:
          type: string
        value:
          type: string
          nullable: true
        is_mentioned:
          type: boolean

    FailureReason:
      type: object
      nullable: true
      properties:
        code:
          type: string
          enum:
            - cv_required
            - cv_screening_failed
            - cv_requirements_not_configured
            - technical_error
            - all_attempts_failed
            - early_hang_up
        message:
          type: string

    Appointment:
      type: object
      nullable: true
      properties:
        scheduled_at:
          type: string
          format: date-time
        duration_minutes:
          type: integer

    # ─── Webhook payloads ──────────────────────────────────────────────────
    ReportGeneratedWebhook:
      type: object
      description: Envoyé quand un processus atteint un état terminal
      required: [event_type, timestamp, process, external_application_id, external_candidate_id, reports, report_url]
      properties:
        event_type:
          type: string
          enum: [report_generated]
        timestamp:
          type: string
          format: date-time
          description: UTC ISO 8601
        process:
          type: object
          properties:
            id:
              type: string
              format: uuid
            status:
              type: string
              enum: [active, in_progress, completed, incomplete, canceled, expired, failed]
            failure_reason:
              $ref: "#/components/schemas/FailureReason"
            use_case:
              type: string
              enum: [prequalification, reactivation]
            created_at:
              type: string
              format: date-time
        external_application_id:
          type: string
        external_job_id:
          type: string
          nullable: true
        external_candidate_id:
          type: string
        job_title:
          type: string
          nullable: true
        reports:
          type: object
          properties:
            interview_report:
              $ref: "#/components/schemas/InterviewReport"
            cv_report:
              $ref: "#/components/schemas/CvReport"
        appointment:
          $ref: "#/components/schemas/Appointment"
        tags:
          type: array
          description: Signaux détectés automatiquement lors de l'analyse de la conversation
          items:
            type: string
            enum:
              - early_hang_up
              - prefers_recruiter
              - not_interested
              - not_available
              - to_review
        report_url:
          type: string
          format: uri
          description: URL vers le rapport complet dans l'interface Omogen

    CallBookedWebhook:
      type: object
      description: Envoyé lorsqu'un candidat a réservé un créneau d'appel
      required: [event, process_id, call_start_time, candidate, job_opportunity_id]
      properties:
        event:
          type: string
          enum: [call_booked]
        process_id:
          type: string
          format: uuid
        call_start_time:
          type: string
          format: date-time
          description: Heure planifiée de l'appel (ISO 8601)
        candidate:
          $ref: "#/components/schemas/WebhookCandidate"
        job_opportunity_id:
          type: string
          format: uuid

    ProcessRejectedWebhook:
      type: object
      description: |
        Envoyé lorsqu'un candidat est rejeté avant la création d'un processus
        (eligibility check échoué, triage rejeté). Aucun appel n'est passé.
      required: [event, external_application_id, external_candidate_id, rejection_reason, timestamp]
      properties:
        event:
          type: string
          enum: [process_rejected]
        process_id:
          type: string
          format: uuid
          nullable: true
          description: ID de processus pré-généré, peut être `null` si aucun processus n'a été créé
        external_application_id:
          type: string
        external_candidate_id:
          type: string
        rejection_reason:
          type: string
          description: Raison lisible du rejet
        timestamp:
          type: string
          format: date-time
          description: UTC
