{
  "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).\nPermet de synchroniser les offres d'emploi et de créer des processus d'entretien,\navec réception des rapports d'évaluation via webhooks.\n\n**⚠️ API en cours de construction.** Contact : [gillian@omogen.ai](mailto:gillian@omogen.ai).\n"
  },
  "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.\nPermet aux utilisateurs de choisir d'activer Omogen et de sélectionner un mode de déploiement.\n\n⚠️ Les paramètres exacts peuvent être personnalisés pour améliorer l'expérience utilisateur dans l'ATS.\n",
        "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\n(201 Created) avec l'`id` de la job et un `edit_url`. L'extraction des critères\nde sélection et la génération du plan d'entretien se font **de manière asynchrone**\nen arrière-plan.\n\n**ℹ️ Cet endpoint est utilisé uniquement dans le Flow 1 (Push).** Dans le Flow 2,\nles jobs sont créées automatiquement lors de la réception d'une candidature.\n\n**Idempotence :** si `external_job_id` est fourni et qu'une job avec cet identifiant\nexiste déjà pour l'organisation, l'endpoint retourne **200 OK** au lieu de 201 et\nne recrée pas la job.\n",
        "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": {
          "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"
                  }
                }
              }
            }
          },
          "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"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Paramètres invalides. Codes possibles :\n- `INVALID_PAYLOAD` : champs manquants ou mal formés (`job_title`, `job_description`)\n- `VALIDATION_ERROR` : erreur de validation métier\n",
            "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.\nLe candidat sera contacté selon le mode de déploiement spécifié.\n\n### Comportement selon le flow\n\n- **Flow 1 (Push)** : l'`external_job_id` doit correspondre à une job déjà synchronisée via `POST /jobs`.\n- **Flow 2 (Pull)** : Omogen récupère automatiquement les infos de l'offre via l'endpoint du partenaire.\n- **Mode triage** : si aucun `external_job_id` n'est fourni et que le mode triage est activé, Omogen\n  utilise le CV pour déterminer automatiquement la job cible.\n\nPour un appel à cet endpoint qui n'inclut que le CV (sans données candidat),\nutiliser plutôt `POST /applications/cv-only`.\n",
        "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": {
          "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"
                  }
                }
              }
            }
          },
          "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"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Paramètres invalides ou erreur de validation métier. Codes possibles :\n- `INVALID_PAYLOAD` : payload mal formé (email, téléphone, champ manquant)\n- `VALIDATION_ERROR` : erreur de validation générique\n- `JOB_FETCH_NOT_FOUND` : l'`external_job_id` n'existe pas côté partenaire (Pull flow)\n- `PULL_FLOW_CONFIG_ERROR` : le `job_fetch_url` n'est pas correctement configuré\n- `CV_DOWNLOAD_FAILED` : échec du téléchargement du CV depuis `cv_url`\n",
            "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\n(Flow 2 — Pull). Codes possibles :\n- `JOB_FETCH_NETWORK_ERROR` : erreur réseau en contactant l'ATS partenaire\n- `JOB_FETCH_HTTP_ERROR` : l'ATS partenaire a retourné un statut 4xx/5xx\n- `JOB_FETCH_INVALID_RESPONSE` : réponse de l'ATS partenaire non parsable (JSON invalide)\n- `JOB_FETCH_MISSING_TITLE` : la réponse de l'ATS partenaire ne contient pas `job_title`\n",
            "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\nles informations candidat (nom, prénom, email, téléphone) depuis le CV via un service\nd'extraction. Si l'extraction échoue pour certains champs, les valeurs de secours\nfournies dans la requête sont utilisées.\n\n**Format de la requête :** `multipart/form-data` ou `application/json`.\nLe CV peut être fourni soit en base64 via `cv_file`, soit via une URL publique via\n`cv_url`. **L'un des deux est obligatoire.**\n\n**Extensions supportées :** `.pdf`, `.doc`, `.docx`, `.txt`, `.rtf`.\n",
        "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": {
          "200": {
            "description": "Application déjà existante (idempotence)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SyncApplicationResponse"
                }
              }
            }
          },
          "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"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Paramètres invalides ou échec d'extraction du CV. Codes possibles :\n- `INVALID_PAYLOAD` : payload mal formé (extension de fichier non supportée, `cv_file` et `cv_url` absents, email/téléphone invalide)\n- `VALIDATION_ERROR` : erreur de validation générique\n- `CV_DOWNLOAD_FAILED` : échec du téléchargement du CV depuis `cv_url` ou décodage base64\n- `CV_INVALID_INPUT` : le contenu du CV n'est pas un document valide\n- `CV_EXTRACTION_FAILED` : échec du parsing du CV (format non reconnu, contenu illisible)\n- `CV_PROCESSING_ERROR` : erreur interne pendant l'extraction\n- `CV_EXTRACTION_UNEXPECTED_ERROR` : erreur inattendue pendant l'extraction\n",
            "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/report-generated": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Webhook - Report Generated",
        "description": "**Webhook envoyé par Omogen** lorsqu'un processus atteint un état terminal\n(completed, failed, expired, canceled, incomplete). C'est l'événement\nprincipal à implémenter côté partenaire.\n\n**À implémenter côté partenaire :** un endpoint HTTP qui accepte ce payload\net répond avec un statut 2xx. L'URL de destination, la méthode (POST ou PUT)\net les headers d'authentification sont configurés dans Omogen.\n",
        "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\nd'appel (auto-planification via SMS ou email). Activé uniquement si la\nfonctionnalité est configurée pour l'organisation.\n",
        "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\ncréation d'un processus (échec de l'eligibility check, job inéligible, etc.).\nUtile pour informer l'ATS que la candidature ne sera pas traitée.\n\nContrairement à `report-generated`, aucun processus n'est créé — le champ\n`process_id` peut être `null`.\n",
        "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 :\n- `AUTHENTICATION_FAILED` : authentification échouée\n",
        "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 :\n- `INTERNAL_ERROR` : erreur interne non catégorisée\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            },
            "example": {
              "success": false,
              "error": "Internal server error",
              "code": "INTERNAL_ERROR"
            }
          }
        }
      }
    },
    "schemas": {
      "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
              }
            }
          }
        ]
      },
      "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"
          }
        }
      },
      "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\nidempotent sur cet id : un second appel avec le même `external_job_id`\nretourne la même `job_opportunity_id` sans recréer la job.\n"
          }
        }
      },
      "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"
              }
            }
          }
        }
      },
      "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\nmode triage est activé pour l'organisation.\n"
          },
          "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"
                ]
              }
            }
          }
        }
      },
      "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"
          }
        }
      },
      "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\n(eligibility check échoué, triage rejeté). Aucun appel n'est passé.\n",
        "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"
          }
        }
      }
    }
  }
}
