{
  "openapi": "3.1.0",
  "info": {
    "title": "HireLayer API",
    "version": "2026-10-01",
    "summary": "Recruiting APIs: resume parsing, job criteria, matching, ranking and skills.",
    "description": "Five recruiting APIs behind one API key. Every call is synchronous and a successful call costs 1 credit. Human-readable documentation: https://onlineresumeparser.com/api-docs. Markdown for agents: https://onlineresumeparser.com/llms.txt.",
    "contact": {
      "name": "HireLayer",
      "url": "https://cal.com/resumeparser/demo-resume-parser"
    }
  },
  "servers": [
    {
      "url": "https://onlineresumeparser.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "HireLayer CV Extract",
      "description": "Resume file → structured candidate JSON",
      "externalDocs": {
        "url": "https://onlineresumeparser.com/api-docs/extract"
      }
    },
    {
      "name": "HireLayer Job Extract",
      "description": "Job description → weighted criteria",
      "externalDocs": {
        "url": "https://onlineresumeparser.com/api-docs/job-extract"
      }
    },
    {
      "name": "HireLayer Match",
      "description": "One candidate vs. one job, criterion by criterion",
      "externalDocs": {
        "url": "https://onlineresumeparser.com/api-docs/match"
      }
    },
    {
      "name": "HireLayer Rank",
      "description": "Up to 10 candidates ranked for one job",
      "externalDocs": {
        "url": "https://onlineresumeparser.com/api-docs/rank"
      }
    },
    {
      "name": "HireLayer Skills",
      "description": "Free-text skills → reference catalog",
      "externalDocs": {
        "url": "https://onlineresumeparser.com/api-docs/skills"
      }
    },
    {
      "name": "Platform",
      "description": "Service status."
    }
  ],
  "externalDocs": {
    "description": "HireLayer API documentation",
    "url": "https://onlineresumeparser.com/api-docs"
  },
  "paths": {
    "/api/v3/parser": {
      "post": {
        "operationId": "parseResume",
        "summary": "Parse a resume",
        "description": "Upload one resume file and receive the structured candidate profile in the same response.\n\n**Billing:** 1 credit per successful parse (HTTP 200).\n\n**Latency:** Synchronous. Parsing usually takes about 35 seconds; scans that need OCR take longer. The gateway waits up to 145 seconds, then returns `504`. Use a client timeout of at least 150 seconds.\n\n**Retries:** Retry `502`, `503` and `504` with exponential backoff and honour `Retry-After`. Never retry `4xx` unchanged. Failed requests are not charged.\n\n**Idempotency:** There is no idempotency key. A request your client abandons can still complete and be charged: do not use a client timeout shorter than the gateway timeout.\n\n**Partial results:** When an optional step is skipped (OCR of some pages, photo, geocoding, occupation codes), the response is still `200` with `upstream_status: \"partial\"` and a note in `warnings`.\n\n**Documents:** 13 formats (PDF, DOC, DOCX, ODT, PPT, PPTX, ODP, XLS, RTF, TXT, JPG/JPEG, PNG and BMP). All pages are read; scanned documents are OCR'd on their first 4 pages. Up to 100,000 extracted characters.\n\n**Languages:** Free-text values stay in the language of the resume (no translation). Enumerated fields use the fixed English values of the response schema. `rome_jobs` labels are in French.\n\n**Request ID:** Successful responses carry `request_id`; error responses carry the `x-parser-request-id` header. Quote them when contacting support.\n\nReference: https://onlineresumeparser.com/api-docs/extract#parse-resume",
        "tags": [
          "HireLayer CV Extract"
        ],
        "x-stability": "stable",
        "x-client-timeout-seconds": 150,
        "externalDocs": {
          "url": "https://onlineresumeparser.com/api-docs/extract#parse-resume"
        },
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/ParseResumeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Parsed resume.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParseResumeResponse"
                },
                "examples": {
                  "example-1": {
                    "summary": "Parsed resume.",
                    "value": {
                      "status": "success",
                      "request_id": "6f1c2a9e-4b7d-4c3e-9a51-2f8d7e6b1c04",
                      "warnings": [],
                      "errors": [],
                      "info_resume": {
                        "application_id": "app_123",
                        "date_parsing": "2026-10-01T09:30:12",
                        "language": "EN",
                        "url": "https://files.example.com/original/1790847012000-alex-morgan.pdf",
                        "face_url": "https://files.example.com/faces/1790847012000-alex-morgan.jpg",
                        "face_url_expires_at": null,
                        "text": "Alex Morgan\nSenior Software Engineer\nParis, France\nalex.morgan@example.com\n\nSenior software engineer with 8 years of experience building web platforms…"
                      },
                      "info_candidate": {
                        "full_name": "Alex Morgan",
                        "last_name": "Morgan",
                        "first_name": "Alex",
                        "email": "alex.morgan@example.com",
                        "phone_number": "+33612345678",
                        "birth_date": "1992-04-18",
                        "age": 34,
                        "availability_now": false,
                        "availability_date": "2026-11-01",
                        "driver_license": [
                          "Permis B"
                        ],
                        "job_title": "Senior Software Engineer",
                        "education_name": "Master of Science in Computer Science",
                        "education_level": "Level 7",
                        "experience_level": "5 to 10 years",
                        "linkedin_url": "https://www.linkedin.com/in/alex-morgan",
                        "github_url": "https://github.com/alexmorgan",
                        "other_urls": [
                          "https://alexmorgan.dev"
                        ],
                        "location": {
                          "country": "France",
                          "country_code": "FR",
                          "region": "Île-de-France",
                          "department": "Paris",
                          "city": "Paris",
                          "postal_code": "75011",
                          "full_address": "75011 Paris, France",
                          "latitude": 48.8589,
                          "longitude": 2.3801
                        },
                        "mobility": {
                          "can_work_in_other_cities": true,
                          "other_cities": [
                            {
                              "city": "Lyon",
                              "country": "France",
                              "postal_code": null,
                              "latitude": 45.764,
                              "longitude": 4.8357
                            }
                          ]
                        }
                      },
                      "work_experiences": [
                        {
                          "company_name": "Northstar Labs",
                          "job_title": "Senior Software Engineer",
                          "description": "Lead a team of five engineers building a TypeScript and React SaaS platform.",
                          "contract_type": "Permanent contract",
                          "start_date": "2022-03-01",
                          "end_date": null,
                          "currently_active": true,
                          "work_experience_country": "France",
                          "work_experience_country_code": "FR",
                          "work_experience_city": "Paris",
                          "work_experience_postal_code": null,
                          "experience_duration": 55
                        },
                        {
                          "company_name": "Atelier Digital",
                          "job_title": "Software Engineer",
                          "description": "Built Node.js APIs and data pipelines for recruitment clients.",
                          "contract_type": "Permanent contract",
                          "start_date": "2018-09-01",
                          "end_date": "2022-02-28",
                          "currently_active": false,
                          "work_experience_country": "France",
                          "work_experience_country_code": "FR",
                          "work_experience_city": "Paris",
                          "work_experience_postal_code": null,
                          "experience_duration": 42
                        }
                      ],
                      "educations": [
                        {
                          "degree_title": "Master of Science in Computer Science",
                          "school_name": "École Polytechnique",
                          "description": "Distributed systems and software architecture.",
                          "degree_type": "Level 7",
                          "start_date": "2014-09-01",
                          "end_date": "2016-06-30",
                          "currently_active": false,
                          "location": {
                            "country": "France",
                            "country_code": "FR",
                            "region": "Île-de-France",
                            "department": "Essonne",
                            "city": "Palaiseau",
                            "postal_code": "91120",
                            "full_address": "Palaiseau, France"
                          }
                        }
                      ],
                      "rome_jobs": [
                        {
                          "job_title": "Ingénieur / Ingénieure logiciel",
                          "job_code": "38971",
                          "rome_title": "Études et développement informatique",
                          "rome_code": "M1805",
                          "prediction_score": 0.93
                        }
                      ],
                      "languages": [
                        {
                          "language": "English",
                          "level": "Native or Bilingual (C2)"
                        },
                        {
                          "language": "French",
                          "level": "Professional Working Proficiency (B2)"
                        }
                      ],
                      "skills": [
                        {
                          "skill_title": "React",
                          "skill_type": "Hard skill",
                          "status": "normalized",
                          "domain": "Technologie",
                          "subcategory": "Languages & Frameworks"
                        },
                        {
                          "skill_title": "Typescript",
                          "skill_type": "Hard skill",
                          "status": "normalized",
                          "domain": "Technologie",
                          "subcategory": "Languages & Frameworks"
                        },
                        {
                          "skill_title": "Figma",
                          "skill_type": "Software skill",
                          "status": "normalized",
                          "domain": "Design & Contenu",
                          "subcategory": "Logiciel"
                        },
                        {
                          "skill_title": "Technical leadership",
                          "skill_type": "Soft skill",
                          "status": "raw",
                          "domain": null,
                          "subcategory": null
                        }
                      ],
                      "certifications": [
                        "AWS Certified Developer – Associate"
                      ],
                      "interests": [
                        "Open-source software",
                        "Climbing"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "- `Missing required file field`: No multipart part named `file`. Do not retry.\n- `application_id must be a string when provided`: `application_id` was sent as a file part. Do not retry.\n- `The uploaded file is empty or invalid. Please check the file and try again.` (`INVALID_FILE`): Empty file, unsupported format, content that does not match its type, or a `do_not_store_data` value other than `true`/`false`. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "missing-required-file-field": {
                    "summary": "Missing required file field",
                    "value": {
                      "error": "Missing required file field"
                    }
                  },
                  "application-id-must-be-a-string-when-provided": {
                    "summary": "application_id must be a string when provided",
                    "value": {
                      "error": "application_id must be a string when provided"
                    }
                  },
                  "invalid-file": {
                    "summary": "The uploaded file is empty or invalid. Please check the file and try again.",
                    "value": {
                      "error": "The uploaded file is empty or invalid. Please check the file and try again.",
                      "code": "INVALID_FILE"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "- `Missing API Key`: The `X-API-Key` header is absent. `Authorization: Bearer` is not accepted. Do not retry.\n- `Invalid API Key`: The key is malformed, unknown or revoked. Rotating a key revokes the previous one immediately. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "missing-api-key": {
                    "summary": "Missing API Key",
                    "value": {
                      "error": "Missing API Key"
                    }
                  },
                  "invalid-api-key": {
                    "summary": "Invalid API Key",
                    "value": {
                      "error": "Invalid API Key"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "- `Insufficient credits available`: The account has no credit left. Fix, then retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "insufficient-credits-available": {
                    "summary": "Insufficient credits available",
                    "value": {
                      "error": "Insufficient credits available"
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "- `The uploaded file is too large to process. Please upload a smaller file.`: The encoded upload exceeds 6 MiB (a file of about 4.5 MB). Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "the-uploaded-file-is-too-large-to-process-please-upload-a-smaller-file": {
                    "summary": "The uploaded file is too large to process. Please upload a smaller file.",
                    "value": {
                      "error": "The uploaded file is too large to process. Please upload a smaller file."
                    }
                  }
                }
              }
            }
          },
          "415": {
            "description": "- `Content-Type must be multipart/form-data`: The request is not `multipart/form-data`. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "content-type-must-be-multipart-form-data": {
                    "summary": "Content-Type must be multipart/form-data",
                    "value": {
                      "error": "Content-Type must be multipart/form-data"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "- `This document does not appear to be a CV or resume. Please upload a CV or resume and try again.` (`DOCUMENT_NOT_A_RESUME`): The document is clearly not a resume (cover letter, ID, invoice…). Do not retry.\n- `The text could not be extracted from this document. Please verify that the file is readable and contains selectable text.` (`DOCUMENT_TEXT_EMPTY`): No text was found, even with OCR. Do not retry.\n- `The document could not be read. Please upload a valid, readable file.` (`DOCUMENT_UNREADABLE`): The file is corrupted or cannot be opened. Do not retry.\n- `This document contains too much text to process. Please try a shorter or simpler version.` (`DOCUMENT_TOO_LARGE`): More than 100,000 characters of text were extracted. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "document-not-a-resume": {
                    "summary": "This document does not appear to be a CV or resume. Please upload a CV or resume and try again.",
                    "value": {
                      "error": "This document does not appear to be a CV or resume. Please upload a CV or resume and try again.",
                      "code": "DOCUMENT_NOT_A_RESUME"
                    }
                  },
                  "document-text-empty": {
                    "summary": "The text could not be extracted from this document. Please verify that the file is readable and contains selectable text.",
                    "value": {
                      "error": "The text could not be extracted from this document. Please verify that the file is readable and contains selectable text.",
                      "code": "DOCUMENT_TEXT_EMPTY"
                    }
                  },
                  "document-unreadable": {
                    "summary": "The document could not be read. Please upload a valid, readable file.",
                    "value": {
                      "error": "The document could not be read. Please upload a valid, readable file.",
                      "code": "DOCUMENT_UNREADABLE"
                    }
                  },
                  "document-too-large": {
                    "summary": "This document contains too much text to process. Please try a shorter or simpler version.",
                    "value": {
                      "error": "This document contains too much text to process. Please try a shorter or simpler version.",
                      "code": "DOCUMENT_TOO_LARGE"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "- `Internal Server Error`: Unexpected failure. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "internal-server-error": {
                    "summary": "Internal Server Error",
                    "value": {
                      "error": "Internal Server Error"
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "- `An error occurred while processing the document or extracting its text. Please try again later.` (`PARSER_UNAVAILABLE`): The parser failed or returned an invalid result. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "parser-unavailable": {
                    "summary": "An error occurred while processing the document or extracting its text. Please try again later.",
                    "value": {
                      "error": "An error occurred while processing the document or extracting its text. Please try again later.",
                      "code": "PARSER_UNAVAILABLE"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "- `An error occurred while processing the document or extracting its text. Please try again later.` (`PARSER_UNAVAILABLE`): A processing step (text extraction, OCR, model) is temporarily unavailable. Retry with backoff.\n- `Credit service temporarily unavailable`: Credits could not be checked or deducted. No credit is consumed, including when the operation itself had completed. Retry with backoff.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "parser-unavailable": {
                    "summary": "An error occurred while processing the document or extracting its text. Please try again later.",
                    "value": {
                      "error": "An error occurred while processing the document or extracting its text. Please try again later.",
                      "code": "PARSER_UNAVAILABLE"
                    }
                  },
                  "credit-service-temporarily-unavailable": {
                    "summary": "Credit service temporarily unavailable",
                    "value": {
                      "error": "Credit service temporarily unavailable"
                    }
                  }
                }
              }
            }
          },
          "504": {
            "description": "- `An error occurred while processing the document or extracting its text. Please try again later.` (`PARSER_UNAVAILABLE`): Parsing did not finish within 145 seconds. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "parser-unavailable": {
                    "summary": "An error occurred while processing the document or extracting its text. Please try again later.",
                    "value": {
                      "error": "An error occurred while processing the document or extracting its text. Please try again later.",
                      "code": "PARSER_UNAVAILABLE"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/jobs/extract-criteria": {
      "post": {
        "operationId": "extractJobCriteria",
        "summary": "Extract job criteria",
        "description": "Turn a job description into weighted criteria that can be checked against a resume.\n\n**Billing:** 1 credit per successful call.\n\n**Latency:** Synchronous. The gateway waits up to 60 seconds, then returns `502 Upstream API unavailable`. Use a client timeout of at least 65 seconds.\n\n**Retries:** Transient model errors are retried by the service before it answers. Retry `502` and `503` with exponential backoff; never retry `4xx` unchanged. Failed requests are not charged.\n\n**Validation:** Validation stops at the first error, so one call reports one problem. Text fields are trimmed before their length is checked.\n\n**Output:** Only requirements a resume can prove are returned; the endpoint does not score candidates. `label` and `rationale` are written in French. The number of criteria is not fixed and can be zero.\n\n**Stability:** Two calls with the same text can return slightly different criteria. Extract once per job, let a recruiter review the list, and store it with the job.\n\nReference: https://onlineresumeparser.com/api-docs/job-extract#extract-criteria",
        "tags": [
          "HireLayer Job Extract"
        ],
        "x-stability": "stable",
        "x-client-timeout-seconds": 65,
        "externalDocs": {
          "url": "https://onlineresumeparser.com/api-docs/job-extract#extract-criteria"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExtractJobCriteriaRequest"
              },
              "examples": {
                "default": {
                  "value": {
                    "job_text": "Senior Frontend Engineer, Paris (hybrid). You will build our recruiting platform with React and TypeScript. Requirements: 5+ years of frontend development, strong React and TypeScript skills, fluent English. Nice to have: experience with Next.js."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Extracted criteria.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtractJobCriteriaResponse"
                },
                "examples": {
                  "example-1": {
                    "summary": "Extracted criteria.",
                    "value": {
                      "matching_criteria": [
                        {
                          "id": "crit_1",
                          "label": "Maîtrise de React",
                          "weight": 3,
                          "is_mandatory": true,
                          "rationale": "React est explicitement exigé pour le poste."
                        },
                        {
                          "id": "crit_2",
                          "label": "Maîtrise de TypeScript",
                          "weight": 3,
                          "is_mandatory": true,
                          "rationale": "TypeScript est explicitement exigé pour le poste."
                        },
                        {
                          "id": "crit_3",
                          "label": "Au moins 5 ans d’expérience en développement frontend",
                          "weight": 3,
                          "is_mandatory": true,
                          "rationale": "L’offre demande plus de cinq ans d’expérience frontend."
                        },
                        {
                          "id": "crit_4",
                          "label": "Anglais courant",
                          "weight": 2,
                          "is_mandatory": true,
                          "rationale": "Un anglais courant est demandé."
                        },
                        {
                          "id": "crit_5",
                          "label": "Expérience avec Next.js",
                          "weight": 1,
                          "is_mandatory": false,
                          "rationale": "Next.js est présenté comme un atout."
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "- `Request body must be a JSON object`: The body is missing, is a JSON array, or `Content-Type` is not `application/json`. Do not retry.\n- `The request contains unsupported fields`: The body contains a field that is not documented for this endpoint. Do not retry.\n- `The 'job_text' field is required`: `job_text` is missing or not a string. Do not retry.\n- `job_text cannot be empty`: `job_text` is empty after trimming. Do not retry.\n- `job_text must be 50000 characters or less`: `job_text` is longer than 50,000 characters after trimming. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceError"
                },
                "examples": {
                  "request-body-must-be-a-json-object": {
                    "summary": "Request body must be a JSON object",
                    "value": {
                      "success": false,
                      "error": "Request body must be a JSON object"
                    }
                  },
                  "the-request-contains-unsupported-fields": {
                    "summary": "The request contains unsupported fields",
                    "value": {
                      "success": false,
                      "error": "The request contains unsupported fields"
                    }
                  },
                  "the-job-text-field-is-required": {
                    "summary": "The 'job_text' field is required",
                    "value": {
                      "success": false,
                      "error": "The 'job_text' field is required"
                    }
                  },
                  "job-text-cannot-be-empty": {
                    "summary": "job_text cannot be empty",
                    "value": {
                      "success": false,
                      "error": "job_text cannot be empty"
                    }
                  },
                  "job-text-must-be-50000-characters-or-less": {
                    "summary": "job_text must be 50000 characters or less",
                    "value": {
                      "success": false,
                      "error": "job_text must be 50000 characters or less"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "- `Missing API Key`: The `X-API-Key` header is absent. `Authorization: Bearer` is not accepted. Do not retry.\n- `Invalid API Key`: The key is malformed, unknown or revoked. Rotating a key revokes the previous one immediately. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "missing-api-key": {
                    "summary": "Missing API Key",
                    "value": {
                      "error": "Missing API Key"
                    }
                  },
                  "invalid-api-key": {
                    "summary": "Invalid API Key",
                    "value": {
                      "error": "Invalid API Key"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "- `Insufficient credits available`: The account has no credit left. `availableCredits` is the current balance. Fix, then retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "insufficient-credits-available": {
                    "summary": "Insufficient credits available",
                    "value": {
                      "error": "Insufficient credits available",
                      "availableCredits": 0
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "- `Internal server error`: Unexpected gateway failure. Retry with backoff.\n- `Internal server error. Please try again later.`: The body is not valid JSON, is a JSON primitive or exceeds 10 MB, or the service failed unexpectedly. Check the payload: if it is valid, retry. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/GatewayError"
                    },
                    {
                      "$ref": "#/components/schemas/ServiceError"
                    }
                  ]
                },
                "examples": {
                  "internal-server-error": {
                    "summary": "Internal server error",
                    "value": {
                      "error": "Internal server error"
                    }
                  },
                  "internal-server-error-please-try-again-later": {
                    "summary": "Internal server error. Please try again later.",
                    "value": {
                      "success": false,
                      "error": "Internal server error. Please try again later."
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "- `Upstream API unavailable`: The service did not answer within 60 seconds, or could not be reached. Retry with backoff.\n- `The AI processing step failed. Please try again later.`: Criteria extraction failed or returned an invalid result after the service's internal retries. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/GatewayError"
                    },
                    {
                      "$ref": "#/components/schemas/ServiceError"
                    }
                  ]
                },
                "examples": {
                  "upstream-api-unavailable": {
                    "summary": "Upstream API unavailable",
                    "value": {
                      "error": "Upstream API unavailable"
                    }
                  },
                  "the-ai-processing-step-failed-please-try-again-later": {
                    "summary": "The AI processing step failed. Please try again later.",
                    "value": {
                      "success": false,
                      "error": "The AI processing step failed. Please try again later."
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "- `Credit service temporarily unavailable`: Credits could not be checked or deducted. No credit is consumed, including when the operation itself had completed. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "credit-service-temporarily-unavailable": {
                    "summary": "Credit service temporarily unavailable",
                    "value": {
                      "error": "Credit service temporarily unavailable"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/matching/job-candidate": {
      "post": {
        "operationId": "matchCandidate",
        "summary": "Match a candidate to a job",
        "description": "Evaluate one resume against each job criterion and get an explained 0–1 score.\n\n**Billing:** 1 credit per successful call.\n\n**Latency:** Synchronous. The gateway waits up to 60 seconds, then returns `502 Upstream API unavailable`. Use a client timeout of at least 65 seconds.\n\n**Retries:** Transient model errors are retried by the service before it answers. Retry `502` and `503` with exponential backoff; never retry `4xx` unchanged. Failed requests are not charged.\n\n**Validation:** Validation stops at the first error, so one call reports one problem. Text fields are trimmed before their length is checked.\n\n**Score:** `score = Σ(weight × value) / Σ weight` with `ideal` = 1, `potential` = 0.6, `not_mentioned` = 0.5 and `not_valid` = 0. Every criterion counts in the denominator; `is_mandatory` has no effect on the score.\n\n**Empty criteria:** With `\"matching_criteria\": []` the model is not called and the response is exactly `{\"score\": 0, \"summary\": \"Aucun critère à évaluer.\", \"evaluated_criteria\": []}`.\n\n**Hard requirements:** To reject candidates who miss a mandatory criterion, check `is_mandatory` and `match_status` in your code: the score alone does not do it.\n\nReference: https://onlineresumeparser.com/api-docs/match#match-job-candidate",
        "tags": [
          "HireLayer Match"
        ],
        "x-stability": "stable",
        "x-client-timeout-seconds": 65,
        "externalDocs": {
          "url": "https://onlineresumeparser.com/api-docs/match#match-job-candidate"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MatchCandidateRequest"
              },
              "examples": {
                "default": {
                  "value": {
                    "job_text": "Senior Frontend Engineer, Paris (hybrid). You will build our recruiting platform with React and TypeScript. Requirements: 5+ years of frontend development, strong React and TypeScript skills, fluent English. Nice to have: experience with Next.js.",
                    "candidate_text": "Alex Morgan, Senior Software Engineer in Paris. 8 years of experience. Since 2022, leads a team building a React and TypeScript SaaS platform at Northstar Labs. Previously built Node.js APIs. Professional English.",
                    "matching_criteria": [
                      {
                        "id": "crit_1",
                        "label": "Maîtrise de React",
                        "weight": 3,
                        "is_mandatory": true,
                        "rationale": "React est explicitement exigé pour le poste."
                      },
                      {
                        "id": "crit_2",
                        "label": "Maîtrise de TypeScript",
                        "weight": 3,
                        "is_mandatory": true,
                        "rationale": "TypeScript est explicitement exigé pour le poste."
                      },
                      {
                        "id": "crit_3",
                        "label": "Au moins 5 ans d’expérience en développement frontend",
                        "weight": 3,
                        "is_mandatory": true,
                        "rationale": "L’offre demande plus de cinq ans d’expérience frontend."
                      },
                      {
                        "id": "crit_4",
                        "label": "Anglais courant",
                        "weight": 2,
                        "is_mandatory": true,
                        "rationale": "Un anglais courant est demandé."
                      },
                      {
                        "id": "crit_5",
                        "label": "Expérience avec Next.js",
                        "weight": 1,
                        "is_mandatory": false,
                        "rationale": "Next.js est présenté comme un atout."
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Evaluation of every criterion.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MatchCandidateResponse"
                },
                "examples": {
                  "example-1": {
                    "summary": "Evaluation of every criterion.",
                    "value": {
                      "score": 0.8916666666666666,
                      "summary": "Profil très aligné : React, TypeScript et l’expérience demandée sont démontrés. Le niveau d’anglais reste à confirmer.",
                      "evaluated_criteria": [
                        {
                          "id": "crit_1",
                          "label": "Maîtrise de React",
                          "weight": 3,
                          "is_mandatory": true,
                          "rationale": "React est explicitement exigé pour le poste.",
                          "match_status": "ideal",
                          "match_explanation": "Le CV décrit une équipe React dirigée depuis 2022 sur une plateforme en production."
                        },
                        {
                          "id": "crit_2",
                          "label": "Maîtrise de TypeScript",
                          "weight": 3,
                          "is_mandatory": true,
                          "rationale": "TypeScript est explicitement exigé pour le poste.",
                          "match_status": "ideal",
                          "match_explanation": "La plateforme actuelle est développée en TypeScript."
                        },
                        {
                          "id": "crit_3",
                          "label": "Au moins 5 ans d’expérience en développement frontend",
                          "weight": 3,
                          "is_mandatory": true,
                          "rationale": "L’offre demande plus de cinq ans d’expérience frontend.",
                          "match_status": "ideal",
                          "match_explanation": "Le candidat cumule huit ans d’expérience en développement."
                        },
                        {
                          "id": "crit_4",
                          "label": "Anglais courant",
                          "weight": 2,
                          "is_mandatory": true,
                          "rationale": "Un anglais courant est demandé.",
                          "match_status": "potential",
                          "match_explanation": "Le CV mentionne un anglais professionnel, sans préciser un niveau courant."
                        },
                        {
                          "id": "crit_5",
                          "label": "Expérience avec Next.js",
                          "weight": 1,
                          "is_mandatory": false,
                          "rationale": "Next.js est présenté comme un atout.",
                          "match_status": "not_mentioned",
                          "match_explanation": "Le CV ne mentionne pas Next.js."
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "- `Request body must be a JSON object`: The body is missing, is a JSON array, or `Content-Type` is not `application/json`. Do not retry.\n- `The request contains unsupported fields`: The body contains a field that is not documented for this endpoint. Do not retry.\n- `The 'job_text' field is required`: `job_text` is missing or not a string. Do not retry.\n- `job_text cannot be empty`: `job_text` is empty after trimming. Do not retry.\n- `job_text must be 50000 characters or less`: `job_text` is longer than 50,000 characters after trimming. Do not retry.\n- `The 'candidate_text' field is required`: `candidate_text` is missing or not a string. Do not retry.\n- `candidate_text cannot be empty`: `candidate_text` is empty after trimming. Do not retry.\n- `candidate_text must be 50000 characters or less`: `candidate_text` is longer than 50,000 characters after trimming. Do not retry.\n- `The 'matching_criteria' field must be an array`: `matching_criteria` is missing or not an array. Do not retry.\n- `matching_criteria contains an invalid criterion`: A criterion has a missing, empty or extra field, or a `weight` outside 1–3. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceError"
                },
                "examples": {
                  "request-body-must-be-a-json-object": {
                    "summary": "Request body must be a JSON object",
                    "value": {
                      "success": false,
                      "error": "Request body must be a JSON object"
                    }
                  },
                  "the-request-contains-unsupported-fields": {
                    "summary": "The request contains unsupported fields",
                    "value": {
                      "success": false,
                      "error": "The request contains unsupported fields"
                    }
                  },
                  "the-job-text-field-is-required": {
                    "summary": "The 'job_text' field is required",
                    "value": {
                      "success": false,
                      "error": "The 'job_text' field is required"
                    }
                  },
                  "job-text-cannot-be-empty": {
                    "summary": "job_text cannot be empty",
                    "value": {
                      "success": false,
                      "error": "job_text cannot be empty"
                    }
                  },
                  "job-text-must-be-50000-characters-or-less": {
                    "summary": "job_text must be 50000 characters or less",
                    "value": {
                      "success": false,
                      "error": "job_text must be 50000 characters or less"
                    }
                  },
                  "the-candidate-text-field-is-required": {
                    "summary": "The 'candidate_text' field is required",
                    "value": {
                      "success": false,
                      "error": "The 'candidate_text' field is required"
                    }
                  },
                  "candidate-text-cannot-be-empty": {
                    "summary": "candidate_text cannot be empty",
                    "value": {
                      "success": false,
                      "error": "candidate_text cannot be empty"
                    }
                  },
                  "candidate-text-must-be-50000-characters-or-less": {
                    "summary": "candidate_text must be 50000 characters or less",
                    "value": {
                      "success": false,
                      "error": "candidate_text must be 50000 characters or less"
                    }
                  },
                  "the-matching-criteria-field-must-be-an-array": {
                    "summary": "The 'matching_criteria' field must be an array",
                    "value": {
                      "success": false,
                      "error": "The 'matching_criteria' field must be an array"
                    }
                  },
                  "matching-criteria-contains-an-invalid-criterion": {
                    "summary": "matching_criteria contains an invalid criterion",
                    "value": {
                      "success": false,
                      "error": "matching_criteria contains an invalid criterion"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "- `Missing API Key`: The `X-API-Key` header is absent. `Authorization: Bearer` is not accepted. Do not retry.\n- `Invalid API Key`: The key is malformed, unknown or revoked. Rotating a key revokes the previous one immediately. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "missing-api-key": {
                    "summary": "Missing API Key",
                    "value": {
                      "error": "Missing API Key"
                    }
                  },
                  "invalid-api-key": {
                    "summary": "Invalid API Key",
                    "value": {
                      "error": "Invalid API Key"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "- `Insufficient credits available`: The account has no credit left. `availableCredits` is the current balance. Fix, then retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "insufficient-credits-available": {
                    "summary": "Insufficient credits available",
                    "value": {
                      "error": "Insufficient credits available",
                      "availableCredits": 0
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "- `Internal server error`: Unexpected gateway failure. Retry with backoff.\n- `Internal server error. Please try again later.`: The body is not valid JSON, is a JSON primitive or exceeds 10 MB, or the service failed unexpectedly. Check the payload: if it is valid, retry. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/GatewayError"
                    },
                    {
                      "$ref": "#/components/schemas/ServiceError"
                    }
                  ]
                },
                "examples": {
                  "internal-server-error": {
                    "summary": "Internal server error",
                    "value": {
                      "error": "Internal server error"
                    }
                  },
                  "internal-server-error-please-try-again-later": {
                    "summary": "Internal server error. Please try again later.",
                    "value": {
                      "success": false,
                      "error": "Internal server error. Please try again later."
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "- `Upstream API unavailable`: The service did not answer within 60 seconds, or could not be reached. Retry with backoff.\n- `The AI processing step failed. Please try again later.`: Matching failed or returned an invalid result after the service's internal retries. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/GatewayError"
                    },
                    {
                      "$ref": "#/components/schemas/ServiceError"
                    }
                  ]
                },
                "examples": {
                  "upstream-api-unavailable": {
                    "summary": "Upstream API unavailable",
                    "value": {
                      "error": "Upstream API unavailable"
                    }
                  },
                  "the-ai-processing-step-failed-please-try-again-later": {
                    "summary": "The AI processing step failed. Please try again later.",
                    "value": {
                      "success": false,
                      "error": "The AI processing step failed. Please try again later."
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "- `Credit service temporarily unavailable`: Credits could not be checked or deducted. No credit is consumed, including when the operation itself had completed. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "credit-service-temporarily-unavailable": {
                    "summary": "Credit service temporarily unavailable",
                    "value": {
                      "error": "Credit service temporarily unavailable"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/matching/job-candidates/rank": {
      "post": {
        "operationId": "rankCandidates",
        "summary": "Rank candidates for a job",
        "description": "Order up to 10 candidates for one job description, with a score and a rationale for each.\n\n**Billing:** 1 credit per successful call, whatever the number of candidates.\n\n**Latency:** Synchronous. The gateway waits up to 60 seconds, then returns `502 Upstream API unavailable`. Use a client timeout of at least 65 seconds.\n\n**Retries:** Transient model errors are retried by the service before it answers. Retry `502` and `503` with exponential backoff; never retry `4xx` unchanged. Failed requests are not charged.\n\n**Validation:** Validation stops at the first error, so one call reports one problem. Text fields are trimmed before their length is checked.\n\n**Ordering:** Every candidate appears exactly once and ranks are `1`…`n`. `rank` is authoritative: `score` is not guaranteed to decrease strictly with rank.\n\n**More than 10 candidates:** Ranks are relative to one request. To screen a larger pool, pre-filter it, or score each candidate with [Match](https://onlineresumeparser.com/api-docs/match.md) against the same criteria, which gives comparable scores.\n\nReference: https://onlineresumeparser.com/api-docs/rank#rank-job-candidates",
        "tags": [
          "HireLayer Rank"
        ],
        "x-stability": "stable",
        "x-client-timeout-seconds": 65,
        "externalDocs": {
          "url": "https://onlineresumeparser.com/api-docs/rank#rank-job-candidates"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RankCandidatesRequest"
              },
              "examples": {
                "default": {
                  "value": {
                    "job_text": "Senior Frontend Engineer, Paris (hybrid). You will build our recruiting platform with React and TypeScript. Requirements: 5+ years of frontend development, strong React and TypeScript skills, fluent English. Nice to have: experience with Next.js.",
                    "candidates": [
                      {
                        "id": "candidate_1",
                        "candidate_text": "Alex Morgan, Senior Software Engineer in Paris. 8 years of experience. Since 2022, leads a team building a React and TypeScript SaaS platform at Northstar Labs. Previously built Node.js APIs. Professional English."
                      },
                      {
                        "id": "candidate_2",
                        "candidate_text": "Frontend developer in Lyon with 3 years of React experience on e-commerce sites. JavaScript, some TypeScript. Conversational English."
                      },
                      {
                        "id": "candidate_3",
                        "candidate_text": "Full-stack JavaScript developer with 6 years of experience, mostly Vue.js and PHP. Fluent English."
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ranked candidates.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RankCandidatesResponse"
                },
                "examples": {
                  "example-1": {
                    "summary": "Ranked candidates.",
                    "value": {
                      "rankings": [
                        {
                          "rank": 1,
                          "candidate_id": "candidate_1",
                          "score": 0.91,
                          "rationale": "React et TypeScript sont démontrés en production, avec l’expérience demandée."
                        },
                        {
                          "rank": 2,
                          "candidate_id": "candidate_2",
                          "score": 0.64,
                          "rationale": "Bonne pratique de React, mais expérience plus courte et anglais seulement conversationnel."
                        },
                        {
                          "rank": 3,
                          "candidate_id": "candidate_3",
                          "score": 0.38,
                          "rationale": "Profil JavaScript solide, mais centré sur Vue.js sans expérience React mentionnée."
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "- `Request body must be a JSON object`: The body is missing, is a JSON array, or `Content-Type` is not `application/json`. Do not retry.\n- `The request contains unsupported fields`: The body contains a field that is not documented for this endpoint. Do not retry.\n- `The 'job_text' field is required`: `job_text` is missing or not a string. Do not retry.\n- `job_text cannot be empty`: `job_text` is empty after trimming. Do not retry.\n- `job_text must be 50000 characters or less`: `job_text` is longer than 50,000 characters after trimming. Do not retry.\n- `The 'candidates' field must be an array`: `candidates` is missing or not an array. Do not retry.\n- `candidates cannot be empty`: `candidates` is `[]`. Do not retry.\n- `candidates must contain 10 candidates or fewer`: More than 10 candidates. Do not retry.\n- `candidates contains an invalid candidate`: A candidate has a missing, empty or extra field. Do not retry.\n- `candidate_text must be 50000 characters or less`: A `candidate_text` is longer than 50,000 characters after trimming. Do not retry.\n- `candidates contains duplicate ids`: Two candidates share the same `id` after trimming. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceError"
                },
                "examples": {
                  "request-body-must-be-a-json-object": {
                    "summary": "Request body must be a JSON object",
                    "value": {
                      "success": false,
                      "error": "Request body must be a JSON object"
                    }
                  },
                  "the-request-contains-unsupported-fields": {
                    "summary": "The request contains unsupported fields",
                    "value": {
                      "success": false,
                      "error": "The request contains unsupported fields"
                    }
                  },
                  "the-job-text-field-is-required": {
                    "summary": "The 'job_text' field is required",
                    "value": {
                      "success": false,
                      "error": "The 'job_text' field is required"
                    }
                  },
                  "job-text-cannot-be-empty": {
                    "summary": "job_text cannot be empty",
                    "value": {
                      "success": false,
                      "error": "job_text cannot be empty"
                    }
                  },
                  "job-text-must-be-50000-characters-or-less": {
                    "summary": "job_text must be 50000 characters or less",
                    "value": {
                      "success": false,
                      "error": "job_text must be 50000 characters or less"
                    }
                  },
                  "the-candidates-field-must-be-an-array": {
                    "summary": "The 'candidates' field must be an array",
                    "value": {
                      "success": false,
                      "error": "The 'candidates' field must be an array"
                    }
                  },
                  "candidates-cannot-be-empty": {
                    "summary": "candidates cannot be empty",
                    "value": {
                      "success": false,
                      "error": "candidates cannot be empty"
                    }
                  },
                  "candidates-must-contain-10-candidates-or-fewer": {
                    "summary": "candidates must contain 10 candidates or fewer",
                    "value": {
                      "success": false,
                      "error": "candidates must contain 10 candidates or fewer"
                    }
                  },
                  "candidates-contains-an-invalid-candidate": {
                    "summary": "candidates contains an invalid candidate",
                    "value": {
                      "success": false,
                      "error": "candidates contains an invalid candidate"
                    }
                  },
                  "candidate-text-must-be-50000-characters-or-less": {
                    "summary": "candidate_text must be 50000 characters or less",
                    "value": {
                      "success": false,
                      "error": "candidate_text must be 50000 characters or less"
                    }
                  },
                  "candidates-contains-duplicate-ids": {
                    "summary": "candidates contains duplicate ids",
                    "value": {
                      "success": false,
                      "error": "candidates contains duplicate ids"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "- `Missing API Key`: The `X-API-Key` header is absent. `Authorization: Bearer` is not accepted. Do not retry.\n- `Invalid API Key`: The key is malformed, unknown or revoked. Rotating a key revokes the previous one immediately. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "missing-api-key": {
                    "summary": "Missing API Key",
                    "value": {
                      "error": "Missing API Key"
                    }
                  },
                  "invalid-api-key": {
                    "summary": "Invalid API Key",
                    "value": {
                      "error": "Invalid API Key"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "- `Insufficient credits available`: The account has no credit left. `availableCredits` is the current balance. Fix, then retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "insufficient-credits-available": {
                    "summary": "Insufficient credits available",
                    "value": {
                      "error": "Insufficient credits available",
                      "availableCredits": 0
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "- `Internal server error`: Unexpected gateway failure. Retry with backoff.\n- `Internal server error. Please try again later.`: The body is not valid JSON, is a JSON primitive or exceeds 10 MB, or the service failed unexpectedly. Check the payload: if it is valid, retry. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/GatewayError"
                    },
                    {
                      "$ref": "#/components/schemas/ServiceError"
                    }
                  ]
                },
                "examples": {
                  "internal-server-error": {
                    "summary": "Internal server error",
                    "value": {
                      "error": "Internal server error"
                    }
                  },
                  "internal-server-error-please-try-again-later": {
                    "summary": "Internal server error. Please try again later.",
                    "value": {
                      "success": false,
                      "error": "Internal server error. Please try again later."
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "- `Upstream API unavailable`: The service did not answer within 60 seconds, or could not be reached. Retry with backoff.\n- `The AI processing step failed. Please try again later.`: Ranking failed or returned an invalid result after the service's internal retries. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/GatewayError"
                    },
                    {
                      "$ref": "#/components/schemas/ServiceError"
                    }
                  ]
                },
                "examples": {
                  "upstream-api-unavailable": {
                    "summary": "Upstream API unavailable",
                    "value": {
                      "error": "Upstream API unavailable"
                    }
                  },
                  "the-ai-processing-step-failed-please-try-again-later": {
                    "summary": "The AI processing step failed. Please try again later.",
                    "value": {
                      "success": false,
                      "error": "The AI processing step failed. Please try again later."
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "- `Credit service temporarily unavailable`: Credits could not be checked or deducted. No credit is consumed, including when the operation itself had completed. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "credit-service-temporarily-unavailable": {
                    "summary": "Credit service temporarily unavailable",
                    "value": {
                      "error": "Credit service temporarily unavailable"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/skills/match": {
      "post": {
        "operationId": "matchSkills",
        "summary": "Match skills to the catalog",
        "description": "Map free-text skills, one or up to 100 at a time, to the closest catalog skills.\n\n**Billing:** 1 credit per successful call, single or batch.\n\n**Latency:** Synchronous. The gateway waits up to 60 seconds; use a client timeout of at least 65 seconds.\n\n**Exact matches:** When the normalized text (case, accents and punctuation ignored) equals a catalog label or synonym, that skill is ranked first with `similarity_score: 1`. Other results come from semantic similarity.\n\n**Thresholds:** Results are always returned, even weak ones. Choose your own `similarity_score` threshold; HireLayer CV Extract uses 0.75 to mark a skill as `normalized`.\n\n**Unknown fields:** Unlike the other endpoints, unknown body fields are ignored.\n\nReference: https://onlineresumeparser.com/api-docs/skills#skills-match",
        "tags": [
          "HireLayer Skills"
        ],
        "x-stability": "stable",
        "x-client-timeout-seconds": 65,
        "externalDocs": {
          "url": "https://onlineresumeparser.com/api-docs/skills#skills-match"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MatchSkillsRequest"
              },
              "examples": {
                "default": {
                  "value": {
                    "skill": "react js",
                    "top_k": 3
                  }
                },
                "batch": {
                  "summary": "Batch",
                  "value": {
                    "skills": [
                      "python",
                      "gestion de projets",
                      " "
                    ],
                    "top_k": 1
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Single mode (`skill`). / Batch mode (`skills`).",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/SkillMatchResult"
                    },
                    {
                      "$ref": "#/components/schemas/SkillMatchBatchResult"
                    }
                  ]
                },
                "examples": {
                  "example-1": {
                    "summary": "Single mode (`skill`).",
                    "value": {
                      "query_skill": "react js",
                      "total_results": 3,
                      "results": [
                        {
                          "rank": 1,
                          "skill": "React",
                          "similarity_score": 0.8712,
                          "domain": "Technologie",
                          "subcategory": "Languages & Frameworks"
                        },
                        {
                          "rank": 2,
                          "skill": "React Native",
                          "similarity_score": 0.7934,
                          "domain": "Technologie",
                          "subcategory": "Languages & Frameworks"
                        },
                        {
                          "rank": 3,
                          "skill": "Javascript",
                          "similarity_score": 0.7121,
                          "domain": "Technologie",
                          "subcategory": "Languages & Frameworks"
                        }
                      ]
                    }
                  },
                  "batch": {
                    "summary": "Batch",
                    "value": {
                      "total_queries": 3,
                      "successful_matches": 2,
                      "results": [
                        {
                          "query_skill": "python",
                          "success": true,
                          "results": [
                            {
                              "rank": 1,
                              "skill": "Python",
                              "similarity_score": 1,
                              "domain": "Technologie",
                              "subcategory": "Languages & Frameworks"
                            }
                          ]
                        },
                        {
                          "query_skill": "gestion de projets",
                          "success": true,
                          "results": [
                            {
                              "rank": 1,
                              "skill": "Gestion de projet - PMO",
                              "similarity_score": 1,
                              "domain": "Business",
                              "subcategory": ""
                            }
                          ]
                        },
                        {
                          "query_skill": " ",
                          "success": false,
                          "error": "Empty skill"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "- `Provide either 'skill' or 'skills', not both`: Both keys are present, even if one is `null`. Do not retry.\n- `The 'skill' or 'skills' field is required`: Neither key is present. Do not retry.\n- `The 'skill' field is required`: `skill` is not a string. Do not retry.\n- `Skill cannot be empty`: `skill` is empty after trimming. Do not retry.\n- `The 'skills' (list) field is required`: `skills` is not an array. Do not retry.\n- `The 'skills' field must be a non-empty list`: `skills` is `[]`. Do not retry.\n- `Maximum 100 skills per batch request`: `skills` has more than 100 items. Do not retry.\n- `top_k must be an integer between 1 and 50`: `top_k` is not a JSON integer from 1 to 50 (`\"5\"` and `null` are rejected). Do not retry.\n- `Request body must be a JSON object`: The body is missing, is a JSON array, or `Content-Type` is not `application/json`. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceError"
                },
                "examples": {
                  "provide-either-skill-or-skills-not-both": {
                    "summary": "Provide either 'skill' or 'skills', not both",
                    "value": {
                      "success": false,
                      "error": "Provide either 'skill' or 'skills', not both"
                    }
                  },
                  "the-skill-or-skills-field-is-required": {
                    "summary": "The 'skill' or 'skills' field is required",
                    "value": {
                      "success": false,
                      "error": "The 'skill' or 'skills' field is required"
                    }
                  },
                  "the-skill-field-is-required": {
                    "summary": "The 'skill' field is required",
                    "value": {
                      "success": false,
                      "error": "The 'skill' field is required"
                    }
                  },
                  "skill-cannot-be-empty": {
                    "summary": "Skill cannot be empty",
                    "value": {
                      "success": false,
                      "error": "Skill cannot be empty"
                    }
                  },
                  "the-skills-list-field-is-required": {
                    "summary": "The 'skills' (list) field is required",
                    "value": {
                      "success": false,
                      "error": "The 'skills' (list) field is required"
                    }
                  },
                  "the-skills-field-must-be-a-non-empty-list": {
                    "summary": "The 'skills' field must be a non-empty list",
                    "value": {
                      "success": false,
                      "error": "The 'skills' field must be a non-empty list"
                    }
                  },
                  "maximum-100-skills-per-batch-request": {
                    "summary": "Maximum 100 skills per batch request",
                    "value": {
                      "success": false,
                      "error": "Maximum 100 skills per batch request"
                    }
                  },
                  "top-k-must-be-an-integer-between-1-and-50": {
                    "summary": "top_k must be an integer between 1 and 50",
                    "value": {
                      "success": false,
                      "error": "top_k must be an integer between 1 and 50"
                    }
                  },
                  "request-body-must-be-a-json-object": {
                    "summary": "Request body must be a JSON object",
                    "value": {
                      "success": false,
                      "error": "Request body must be a JSON object"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "- `Missing API Key`: The `X-API-Key` header is absent. `Authorization: Bearer` is not accepted. Do not retry.\n- `Invalid API Key`: The key is malformed, unknown or revoked. Rotating a key revokes the previous one immediately. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "missing-api-key": {
                    "summary": "Missing API Key",
                    "value": {
                      "error": "Missing API Key"
                    }
                  },
                  "invalid-api-key": {
                    "summary": "Invalid API Key",
                    "value": {
                      "error": "Invalid API Key"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "- `Insufficient credits available`: The account has no credit left. `availableCredits` is the current balance. Fix, then retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "insufficient-credits-available": {
                    "summary": "Insufficient credits available",
                    "value": {
                      "error": "Insufficient credits available",
                      "availableCredits": 0
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "- `Internal server error`: Unexpected gateway failure. Retry with backoff.\n- `Internal server error. Please try again later.`: The body is not valid JSON, is a JSON primitive or exceeds 10 MB, or the service failed unexpectedly. Check the payload: if it is valid, retry. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/GatewayError"
                    },
                    {
                      "$ref": "#/components/schemas/ServiceError"
                    }
                  ]
                },
                "examples": {
                  "internal-server-error": {
                    "summary": "Internal server error",
                    "value": {
                      "error": "Internal server error"
                    }
                  },
                  "internal-server-error-please-try-again-later": {
                    "summary": "Internal server error. Please try again later.",
                    "value": {
                      "success": false,
                      "error": "Internal server error. Please try again later."
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "- `Upstream API unavailable`: The service did not answer within 60 seconds, or could not be reached. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "upstream-api-unavailable": {
                    "summary": "Upstream API unavailable",
                    "value": {
                      "error": "Upstream API unavailable"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "- `Credit service temporarily unavailable`: Credits could not be checked or deducted. No credit is consumed, including when the operation itself had completed. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "credit-service-temporarily-unavailable": {
                    "summary": "Credit service temporarily unavailable",
                    "value": {
                      "error": "Credit service temporarily unavailable"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/skills": {
      "get": {
        "operationId": "listSkills",
        "summary": "List the skill catalog",
        "description": "Download the whole reference catalog used for matching.\n\n**Billing:** 1 credit per successful call.\n\n**Caching:** The catalog changes rarely and each call costs a credit: cache it on your side, for example once a day.\n\n**Labels:** Labels and domains are mostly French (`Technologie`, `Ressources Humaines`, `Design & Contenu`…). Query parameters are ignored.\n\nReference: https://onlineresumeparser.com/api-docs/skills#skills-catalog",
        "tags": [
          "HireLayer Skills"
        ],
        "x-stability": "stable",
        "x-client-timeout-seconds": 65,
        "externalDocs": {
          "url": "https://onlineresumeparser.com/api-docs/skills#skills-catalog"
        },
        "responses": {
          "200": {
            "description": "The catalog.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListSkillsResponse"
                },
                "examples": {
                  "example-1": {
                    "summary": "The catalog.",
                    "value": {
                      "total_skills": 664,
                      "skills": [
                        {
                          "skill": "GMAO",
                          "domain": "Business",
                          "subcategory": "",
                          "rank": 1
                        },
                        {
                          "skill": "Gantt",
                          "domain": "Business",
                          "subcategory": "",
                          "rank": 2
                        },
                        {
                          "skill": "Figma",
                          "domain": "Design & Contenu",
                          "subcategory": "Logiciel",
                          "rank": 180
                        },
                        {
                          "skill": "React",
                          "domain": "Technologie",
                          "subcategory": "Languages & Frameworks",
                          "rank": 499
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "- `Missing API Key`: The `X-API-Key` header is absent. `Authorization: Bearer` is not accepted. Do not retry.\n- `Invalid API Key`: The key is malformed, unknown or revoked. Rotating a key revokes the previous one immediately. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "missing-api-key": {
                    "summary": "Missing API Key",
                    "value": {
                      "error": "Missing API Key"
                    }
                  },
                  "invalid-api-key": {
                    "summary": "Invalid API Key",
                    "value": {
                      "error": "Invalid API Key"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "- `Insufficient credits available`: The account has no credit left. `availableCredits` is the current balance. Fix, then retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "insufficient-credits-available": {
                    "summary": "Insufficient credits available",
                    "value": {
                      "error": "Insufficient credits available",
                      "availableCredits": 0
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "- `Internal server error`: Unexpected gateway failure. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "internal-server-error": {
                    "summary": "Internal server error",
                    "value": {
                      "error": "Internal server error"
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "- `Upstream API unavailable`: The service did not answer within 60 seconds, or could not be reached. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "upstream-api-unavailable": {
                    "summary": "Upstream API unavailable",
                    "value": {
                      "error": "Upstream API unavailable"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "- `Credit service temporarily unavailable`: Credits could not be checked or deducted. No credit is consumed, including when the operation itself had completed. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "credit-service-temporarily-unavailable": {
                    "summary": "Credit service temporarily unavailable",
                    "value": {
                      "error": "Credit service temporarily unavailable"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Check API health",
        "description": "Check that the Job Extract, Match, Skills and Rank service is up. No API key needed.\n\n**Billing:** Free.\n\n**Scope:** Covers the service behind `/api/v1`. It does not check HireLayer CV Extract (`/api/v3/parser`), and it does not validate API keys.\n\nReference: https://onlineresumeparser.com/api-docs/health#health",
        "tags": [
          "Platform"
        ],
        "security": [],
        "x-stability": "stable",
        "x-client-timeout-seconds": 10,
        "externalDocs": {
          "url": "https://onlineresumeparser.com/api-docs/health#health"
        },
        "responses": {
          "200": {
            "description": "The service is up.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetHealthResponse"
                },
                "examples": {
                  "example-1": {
                    "summary": "The service is up.",
                    "value": {
                      "status": "healthy",
                      "service": "HireLayer API",
                      "skills_loaded": 664,
                      "timestamp": "2026-10-01T09:30:12.417Z"
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "- `Upstream API unavailable`: The service is down or did not answer within 60 seconds. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "upstream-api-unavailable": {
                    "summary": "Upstream API unavailable",
                    "value": {
                      "error": "Upstream API unavailable"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v2/parser": {
      "post": {
        "operationId": "parseResumeV2",
        "summary": "Submit a resume (legacy V2)",
        "description": "Legacy asynchronous contract: the request is accepted with `202` and the result is posted to `webhook_url`.\n\n**Billing:** 1 credit when the request is accepted (HTTP 202).\n\n**Asynchronous:** The gateway waits up to 45 seconds for the request to be accepted. The webhook then receives `{\"status\": \"success\", …}` with the parsed resume, or `{\"status\": \"failure\", \"error_code\": …, \"error_message\": …}`.\n\n**Billing:** The credit is consumed when the request is accepted, even if processing fails later.\n\nReference: https://onlineresumeparser.com/api-docs/extract-v2#parse-resume-v2",
        "tags": [
          "HireLayer CV Extract"
        ],
        "deprecated": true,
        "x-stability": "legacy",
        "x-client-timeout-seconds": 50,
        "externalDocs": {
          "url": "https://onlineresumeparser.com/api-docs/extract-v2#parse-resume-v2"
        },
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/ParseResumeV2Request"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted. The result is sent to `webhook_url` later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParseResumeV2Response"
                },
                "examples": {
                  "example-1": {
                    "summary": "Accepted. The result is sent to `webhook_url` later.",
                    "value": {
                      "message": "CV processing initiated.",
                      "body": null,
                      "error": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "- `An error occurred while processing the document or extracting its text. Please try again later.`: Any error from the legacy parser is returned with its status code (`400` for a missing file or `webhook_url`, unsupported type…) and this generic message. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "an-error-occurred-while-processing-the-document-or-extracting-its-text-please-try-again-later": {
                    "summary": "An error occurred while processing the document or extracting its text. Please try again later.",
                    "value": {
                      "error": "An error occurred while processing the document or extracting its text. Please try again later."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "- `Missing API Key`: The `X-API-Key` header is absent. `Authorization: Bearer` is not accepted. Do not retry.\n- `Invalid API Key`: The key is malformed, unknown or revoked. Rotating a key revokes the previous one immediately. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "missing-api-key": {
                    "summary": "Missing API Key",
                    "value": {
                      "error": "Missing API Key"
                    }
                  },
                  "invalid-api-key": {
                    "summary": "Invalid API Key",
                    "value": {
                      "error": "Invalid API Key"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "- `Insufficient credits available`: The account has no credit left. Fix, then retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "insufficient-credits-available": {
                    "summary": "Insufficient credits available",
                    "value": {
                      "error": "Insufficient credits available"
                    }
                  }
                }
              }
            }
          },
          "415": {
            "description": "- `Content-Type must be multipart/form-data`: The request is not `multipart/form-data`. Do not retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "content-type-must-be-multipart-form-data": {
                    "summary": "Content-Type must be multipart/form-data",
                    "value": {
                      "error": "Content-Type must be multipart/form-data"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "- `Credit service temporarily unavailable`: Credits could not be checked or deducted. No credit is consumed, including when the operation itself had completed. Retry with backoff.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GatewayError"
                },
                "examples": {
                  "credit-service-temporarily-unavailable": {
                    "summary": "Credit service temporarily unavailable",
                    "value": {
                      "error": "Credit service temporarily unavailable"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "API key from the dashboard. `Authorization: Bearer` is not supported."
      }
    },
    "schemas": {
      "GatewayError": {
        "type": "object",
        "description": "Error returned by the HireLayer gateway (authentication, credits, CV Extract).",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message."
          },
          "code": {
            "type": "string",
            "description": "Machine-readable code (CV Extract errors only).",
            "enum": [
              "INVALID_FILE",
              "DOCUMENT_NOT_A_RESUME",
              "DOCUMENT_TEXT_EMPTY",
              "DOCUMENT_UNREADABLE",
              "DOCUMENT_TOO_LARGE",
              "PARSER_UNAVAILABLE"
            ]
          },
          "availableCredits": {
            "type": "integer",
            "description": "Current balance, on `403` from `/api/v1` endpoints."
          }
        },
        "required": [
          "error"
        ]
      },
      "ServiceError": {
        "type": "object",
        "description": "Error returned by the Job Extract, Match, Rank and Skills services.",
        "properties": {
          "success": {
            "const": false
          },
          "error": {
            "type": "string",
            "description": "Human-readable message."
          }
        },
        "required": [
          "success",
          "error"
        ]
      },
      "ParseResumeRequest": {
        "type": "object",
        "properties": {
          "file": {
            "type": "string",
            "format": "binary",
            "description": "The resume: PDF, DOC, DOCX, ODT, PPT, PPTX, ODP, XLS, RTF, TXT, JPG/JPEG, PNG or BMP. The type is detected from the content. Keep it under 4.5 MB."
          },
          "application_id": {
            "description": "Your own reference, echoed in `info_resume.application_id`.",
            "type": "string"
          },
          "webhook_url": {
            "description": "HTTP(S) URL that also receives the result. The response stays synchronous. See [Webhook](#webhook).",
            "type": "string",
            "format": "uri"
          },
          "do_not_store_data": {
            "description": "`true`: the resume file is **not stored**. `info_resume.url` is then `null`, and the cropped photo is only reachable through a temporary link valid 10 minutes. Case-insensitive. Defaults to `false` (file stored).",
            "type": "string",
            "enum": [
              "true",
              "false"
            ]
          }
        },
        "required": [
          "file"
        ]
      },
      "ParseResumeResponse": {
        "type": "object",
        "properties": {
          "status": {
            "description": "Always `success` on HTTP 200.",
            "type": "string",
            "const": "success"
          },
          "upstream_status": {
            "description": "Present when an optional step was skipped (see `warnings`). The data is still usable.",
            "type": "string",
            "const": "partial"
          },
          "request_id": {
            "description": "Unique request ID. Quote it when contacting support.",
            "type": "string"
          },
          "warnings": {
            "description": "Human-readable notes about skipped steps (OCR, photo, geocoding, occupation codes). Informational: do not parse.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "errors": {
            "description": "Always empty on HTTP 200. Failures use non-2xx statuses.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "info_resume": {
            "title": "ResumeInfo",
            "description": "The document.",
            "type": "object",
            "properties": {
              "application_id": {
                "description": "Your `application_id`, or `null` when not sent.",
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "date_parsing": {
                "description": "Parsing time in UTC, `YYYY-MM-DDTHH:mm:ss`, without a zone suffix.",
                "type": "string"
              },
              "language": {
                "description": "Main language of the resume, uppercase ISO 639-1 code such as `EN` or `FR`.",
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "url": {
                "description": "URL of the stored original file. Anyone with the link can open it. `null` with `do_not_store_data=true`, because the file is not stored.",
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "face_url": {
                "description": "Candidate photo cropped from the resume. `null` when no face is found and for `.txt` files.",
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "face_url_expires_at": {
                "description": "With `do_not_store_data=true`: expiry of the temporary `face_url`, 10 minutes after parsing, UTC `YYYY-MM-DDTHH:mm:ss`. Otherwise `null` and `face_url` does not expire.",
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "text": {
                "description": "Full extracted text, including OCR. May end with `[EXTRACTED_HYPERLINKS]` and `[PDF_ANNOTATION_TEXTS]` sections.",
                "type": "string"
              }
            },
            "required": [
              "application_id",
              "date_parsing",
              "language",
              "url",
              "face_url",
              "face_url_expires_at",
              "text"
            ]
          },
          "info_candidate": {
            "title": "Candidate",
            "description": "The candidate profile.",
            "type": "object",
            "properties": {
              "full_name": {
                "description": "First and last name.",
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "last_name": {
                "description": "Last name.",
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "first_name": {
                "description": "First name.",
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "email": {
                "description": "Lower-cased email address.",
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "phone_number": {
                "description": "E.164 phone number, e.g. `+33612345678`. The country is inferred from the resume, France by default.",
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "birth_date": {
                "description": "Date of birth, `YYYY-MM-DD`. A year alone becomes `YYYY-01-01`.",
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "age": {
                "description": "Age in years, as stated or inferred.",
                "anyOf": [
                  {
                    "type": "number"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "availability_now": {
                "description": "Whether the candidate is available now.",
                "anyOf": [
                  {
                    "type": "boolean"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "availability_date": {
                "description": "Date from which the candidate is available, `YYYY-MM-DD`.",
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "driver_license": {
                "description": "Driving licences as written, e.g. `Permis B`.",
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "job_title": {
                "description": "Target or current job title. Falls back to the most recent position.",
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "education_name": {
                "description": "Highest qualification relevant to the job title.",
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "education_level": {
                "description": "Highest European Qualifications Framework (EQF) level.",
                "anyOf": [
                  {
                    "type": "string",
                    "enum": [
                      "Level 1",
                      "Level 2",
                      "Level 3",
                      "Level 4",
                      "Level 5",
                      "Level 6",
                      "Level 7",
                      "Level 8",
                      "Other"
                    ]
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "experience_level": {
                "description": "Total professional experience bracket.",
                "anyOf": [
                  {
                    "type": "string",
                    "enum": [
                      "0 to 1 year",
                      "1 to 3 years",
                      "3 to 5 years",
                      "5 to 10 years",
                      "More than 10 years"
                    ]
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "linkedin_url": {
                "description": "LinkedIn profile URL.",
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "github_url": {
                "description": "GitHub profile URL.",
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "other_urls": {
                "description": "Other URLs in the resume (portfolio, website…).",
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "location": {
                "title": "Location",
                "description": "Where the candidate lives.",
                "type": "object",
                "properties": {
                  "country": {
                    "description": "Country name.",
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "country_code": {
                    "description": "ISO 3166-1 alpha-2 country code, e.g. `FR`.",
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "region": {
                    "description": "Region.",
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "department": {
                    "description": "Department or county.",
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "city": {
                    "description": "City.",
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "postal_code": {
                    "description": "Postal code.",
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "full_address": {
                    "description": "Address as a single line.",
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "latitude": {
                    "description": "Latitude from geocoding.",
                    "anyOf": [
                      {
                        "type": "number"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "longitude": {
                    "description": "Longitude from geocoding.",
                    "anyOf": [
                      {
                        "type": "number"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "required": [
                  "country",
                  "country_code",
                  "region",
                  "department",
                  "city",
                  "postal_code",
                  "full_address",
                  "latitude",
                  "longitude"
                ]
              },
              "mobility": {
                "title": "Mobility",
                "description": "Where else the candidate can work.",
                "type": "object",
                "properties": {
                  "can_work_in_other_cities": {
                    "description": "Whether the resume says the candidate can work elsewhere. `null` when not mentioned.",
                    "anyOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "other_cities": {
                    "description": "Cities explicitly named as possible work locations.",
                    "type": "array",
                    "items": {
                      "title": "MobilityCity",
                      "type": "object",
                      "properties": {
                        "city": {
                          "description": "City named in the resume.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "country": {
                          "description": "Country, when stated or reliably inferred.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "postal_code": {
                          "description": "Postal code, when present in the resume.",
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "latitude": {
                          "description": "Latitude from geocoding.",
                          "anyOf": [
                            {
                              "type": "number"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "longitude": {
                          "description": "Longitude from geocoding.",
                          "anyOf": [
                            {
                              "type": "number"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        }
                      },
                      "required": [
                        "city",
                        "country",
                        "postal_code",
                        "latitude",
                        "longitude"
                      ]
                    }
                  }
                },
                "required": [
                  "can_work_in_other_cities",
                  "other_cities"
                ]
              }
            },
            "required": [
              "full_name",
              "last_name",
              "first_name",
              "email",
              "phone_number",
              "birth_date",
              "age",
              "availability_now",
              "availability_date",
              "driver_license",
              "job_title",
              "education_name",
              "education_level",
              "experience_level",
              "linkedin_url",
              "github_url",
              "other_urls",
              "location",
              "mobility"
            ]
          },
          "work_experiences": {
            "type": "array",
            "items": {
              "title": "WorkExperience",
              "type": "object",
              "properties": {
                "company_name": {
                  "description": "Employer name.",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "job_title": {
                  "description": "Job title for this position.",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "description": {
                  "description": "Tasks, responsibilities and achievements.",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "contract_type": {
                  "description": "Canonical contract type. Unrecognized values become `Other`.",
                  "anyOf": [
                    {
                      "type": "string",
                      "enum": [
                        "Permanent contract",
                        "Fixed-term contract",
                        "Temporary assignment",
                        "Internship",
                        "Apprenticeship",
                        "Freelance",
                        "Volunteering",
                        "Other"
                      ]
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "start_date": {
                  "description": "Start date, `YYYY-MM-DD`. A year alone becomes `YYYY-01-01`; a month alone, its first day.",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "end_date": {
                  "description": "End date, `YYYY-MM-DD`. `null` for an ongoing role. A year alone becomes `YYYY-12-31`; a month alone, its last day.",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "currently_active": {
                  "description": "Whether this is a current position.",
                  "anyOf": [
                    {
                      "type": "boolean"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "work_experience_country": {
                  "description": "Country of the position.",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "work_experience_country_code": {
                  "description": "ISO 3166-1 alpha-2 country code.",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "work_experience_city": {
                  "description": "City of the position.",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "work_experience_postal_code": {
                  "description": "Postal code of the position.",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "experience_duration": {
                  "description": "Duration of this position in months, as extracted.",
                  "anyOf": [
                    {
                      "type": "number"
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              },
              "required": [
                "company_name",
                "job_title",
                "description",
                "contract_type",
                "start_date",
                "end_date",
                "currently_active",
                "work_experience_country",
                "work_experience_country_code",
                "work_experience_city",
                "work_experience_postal_code",
                "experience_duration"
              ]
            }
          },
          "educations": {
            "type": "array",
            "items": {
              "title": "Education",
              "type": "object",
              "properties": {
                "degree_title": {
                  "description": "Name of the degree or programme.",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "school_name": {
                  "description": "School or institution.",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "description": {
                  "description": "Details of the programme.",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "degree_type": {
                  "description": "EQF level of the degree, expected to be one of `Level 1`…`Level 8` or `Other`. Not normalized: treat other strings as `Other`.",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "start_date": {
                  "description": "Start date, `YYYY-MM-DD`.",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "end_date": {
                  "description": "End date, `YYYY-MM-DD`.",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "currently_active": {
                  "description": "Whether the programme is in progress.",
                  "anyOf": [
                    {
                      "type": "boolean"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "location": {
                  "title": "EducationLocation",
                  "description": "Location of the school.",
                  "type": "object",
                  "properties": {
                    "country": {
                      "description": "Country name.",
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "country_code": {
                      "description": "ISO 3166-1 alpha-2 country code, e.g. `FR`.",
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "region": {
                      "description": "Region.",
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "department": {
                      "description": "Department or county.",
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "city": {
                      "description": "City.",
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "postal_code": {
                      "description": "Postal code.",
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "full_address": {
                      "description": "Address as a single line.",
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "country",
                    "country_code",
                    "region",
                    "department",
                    "city",
                    "postal_code",
                    "full_address"
                  ]
                }
              },
              "required": [
                "degree_title",
                "school_name",
                "description",
                "degree_type",
                "start_date",
                "end_date",
                "currently_active",
                "location"
              ]
            }
          },
          "rome_jobs": {
            "description": "Occupations predicted from `info_candidate.job_title` with the French ROME taxonomy.",
            "type": "array",
            "items": {
              "title": "RomeJob",
              "type": "object",
              "properties": {
                "job_title": {
                  "description": "Occupation label, in French.",
                  "type": "string"
                },
                "job_code": {
                  "description": "Occupation (appellation) code.",
                  "type": "string"
                },
                "rome_title": {
                  "description": "ROME job family label, in French.",
                  "type": "string"
                },
                "rome_code": {
                  "description": "ROME code, e.g. `M1805`.",
                  "type": "string"
                },
                "prediction_score": {
                  "description": "Confidence between 0.7 and 1.",
                  "type": "number"
                }
              },
              "required": [
                "job_title",
                "job_code",
                "rome_title",
                "rome_code",
                "prediction_score"
              ]
            }
          },
          "languages": {
            "type": "array",
            "items": {
              "title": "Language",
              "type": "object",
              "properties": {
                "language": {
                  "description": "Language name as written in the resume.",
                  "type": "string"
                },
                "level": {
                  "description": "CEFR-based proficiency, or `null` when not stated.",
                  "anyOf": [
                    {
                      "type": "string",
                      "enum": [
                        "Native or Bilingual (C2)",
                        "Full Professional Proficiency (C1)",
                        "Professional Working Proficiency (B2)",
                        "Limited Working Proficiency (B1)",
                        "Advanced Basic Proficiency (A2)",
                        "Introductory Proficiency (A1)"
                      ]
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              },
              "required": [
                "language",
                "level"
              ]
            }
          },
          "skills": {
            "type": "array",
            "items": {
              "title": "Skill",
              "type": "object",
              "properties": {
                "skill_title": {
                  "description": "Catalog label when `status` is `normalized`, otherwise the text from the resume.",
                  "type": "string"
                },
                "skill_type": {
                  "type": "string",
                  "enum": [
                    "Hard skill",
                    "Soft skill",
                    "Software skill"
                  ]
                },
                "status": {
                  "description": "`normalized`: matched to the [HireLayer Skills catalog](/api-docs/skills). `raw`: no close catalog match.",
                  "type": "string",
                  "enum": [
                    "normalized",
                    "raw"
                  ]
                },
                "domain": {
                  "description": "Catalog domain. `null` when `status` is `raw`.",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "subcategory": {
                  "description": "Catalog subcategory. `null` when `status` is `raw`.",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              },
              "required": [
                "skill_title",
                "skill_type",
                "status",
                "domain",
                "subcategory"
              ]
            }
          },
          "certifications": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "interests": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "status",
          "request_id",
          "warnings",
          "errors",
          "info_resume",
          "info_candidate",
          "work_experiences",
          "educations",
          "rome_jobs",
          "languages",
          "skills",
          "certifications",
          "interests"
        ],
        "description": "Parsed resume."
      },
      "ExtractJobCriteriaRequest": {
        "type": "object",
        "properties": {
          "job_text": {
            "description": "Full job description, in any language. Trimmed before validation.",
            "type": "string",
            "minLength": 1,
            "maxLength": 50000
          }
        },
        "required": [
          "job_text"
        ],
        "additionalProperties": false
      },
      "ExtractJobCriteriaResponse": {
        "type": "object",
        "properties": {
          "matching_criteria": {
            "description": "Criteria sorted by `weight`, highest first, with IDs `crit_1`…`crit_n` in that order. Can be empty.",
            "type": "array",
            "items": {
              "title": "MatchingCriterion",
              "type": "object",
              "properties": {
                "id": {
                  "description": "Criterion ID. Job Extract generates `crit_1`…`crit_n`; Match accepts any non-empty string.",
                  "type": "string",
                  "minLength": 1
                },
                "label": {
                  "description": "What is evaluated, in a short phrase.",
                  "type": "string",
                  "minLength": 1
                },
                "weight": {
                  "description": "Importance: `3` essential, `2` important, `1` nice to have.",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 3
                },
                "is_mandatory": {
                  "description": "Whether the job states it as a hard requirement. Informational: it does not change the Match score.",
                  "type": "boolean"
                },
                "rationale": {
                  "description": "Why the criterion matters for the job.",
                  "type": "string",
                  "minLength": 1
                }
              },
              "required": [
                "id",
                "label",
                "weight",
                "is_mandatory",
                "rationale"
              ]
            }
          }
        },
        "required": [
          "matching_criteria"
        ],
        "description": "Extracted criteria."
      },
      "MatchCandidateRequest": {
        "type": "object",
        "properties": {
          "job_text": {
            "description": "Job description. Trimmed before validation.",
            "type": "string",
            "minLength": 1,
            "maxLength": 50000
          },
          "candidate_text": {
            "description": "Candidate resume as plain text, e.g. `info_resume.text` from HireLayer CV Extract. Trimmed before validation.",
            "type": "string",
            "minLength": 1,
            "maxLength": 50000
          },
          "matching_criteria": {
            "description": "Criteria to evaluate, usually from HireLayer Job Extract. Can be empty. No other criterion field is accepted.",
            "type": "array",
            "items": {
              "title": "MatchingCriterion",
              "type": "object",
              "properties": {
                "id": {
                  "description": "Criterion ID. Job Extract generates `crit_1`…`crit_n`; Match accepts any non-empty string.",
                  "type": "string",
                  "minLength": 1
                },
                "label": {
                  "description": "What is evaluated, in a short phrase.",
                  "type": "string",
                  "minLength": 1
                },
                "weight": {
                  "description": "Importance: `3` essential, `2` important, `1` nice to have.",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 3
                },
                "is_mandatory": {
                  "description": "Whether the job states it as a hard requirement. Informational: it does not change the Match score.",
                  "type": "boolean"
                },
                "rationale": {
                  "description": "Why the criterion matters for the job.",
                  "type": "string",
                  "minLength": 1
                }
              },
              "required": [
                "id",
                "label",
                "weight",
                "is_mandatory",
                "rationale"
              ],
              "additionalProperties": false
            }
          }
        },
        "required": [
          "job_text",
          "candidate_text",
          "matching_criteria"
        ],
        "additionalProperties": false
      },
      "MatchCandidateResponse": {
        "type": "object",
        "properties": {
          "score": {
            "description": "Weighted average of the criterion statuses. Not rounded.",
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "summary": {
            "description": "Overall assessment, in French.",
            "type": "string"
          },
          "evaluated_criteria": {
            "description": "Every input criterion, in input order, with its evaluation.",
            "type": "array",
            "items": {
              "title": "EvaluatedCriterion",
              "type": "object",
              "properties": {
                "id": {
                  "description": "Criterion ID. Job Extract generates `crit_1`…`crit_n`; Match accepts any non-empty string.",
                  "type": "string",
                  "minLength": 1
                },
                "label": {
                  "description": "What is evaluated, in a short phrase.",
                  "type": "string",
                  "minLength": 1
                },
                "weight": {
                  "description": "Importance: `3` essential, `2` important, `1` nice to have.",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 3
                },
                "is_mandatory": {
                  "description": "Whether the job states it as a hard requirement. Informational: it does not change the Match score.",
                  "type": "boolean"
                },
                "rationale": {
                  "description": "Why the criterion matters for the job.",
                  "type": "string",
                  "minLength": 1
                },
                "match_status": {
                  "description": "`ideal`: clearly met · `potential`: partly or indirectly met · `not_mentioned`: the resume says nothing · `not_valid`: contradicted.",
                  "type": "string",
                  "enum": [
                    "ideal",
                    "potential",
                    "not_valid",
                    "not_mentioned"
                  ]
                },
                "match_explanation": {
                  "description": "Evidence from the resume, in French.",
                  "type": "string"
                }
              },
              "required": [
                "id",
                "label",
                "weight",
                "is_mandatory",
                "rationale",
                "match_status",
                "match_explanation"
              ]
            }
          }
        },
        "required": [
          "score",
          "summary",
          "evaluated_criteria"
        ],
        "description": "Evaluation of every criterion."
      },
      "RankCandidatesRequest": {
        "type": "object",
        "properties": {
          "job_text": {
            "description": "Job description. Trimmed before validation.",
            "type": "string",
            "minLength": 1,
            "maxLength": 50000
          },
          "candidates": {
            "description": "Candidates to rank against the same job.",
            "minItems": 1,
            "maxItems": 10,
            "type": "array",
            "items": {
              "title": "RankingCandidate",
              "type": "object",
              "properties": {
                "id": {
                  "description": "Your candidate ID. Unique within the request after trimming.",
                  "type": "string",
                  "minLength": 1
                },
                "candidate_text": {
                  "description": "Resume as plain text. Trimmed before validation.",
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 50000
                }
              },
              "required": [
                "id",
                "candidate_text"
              ],
              "additionalProperties": false
            }
          }
        },
        "required": [
          "job_text",
          "candidates"
        ],
        "additionalProperties": false
      },
      "RankCandidatesResponse": {
        "type": "object",
        "properties": {
          "rankings": {
            "description": "One entry per candidate, sorted by `rank`.",
            "type": "array",
            "items": {
              "title": "Ranking",
              "type": "object",
              "properties": {
                "rank": {
                  "description": "Position from 1 (best) to the number of candidates.",
                  "type": "integer",
                  "minimum": 1
                },
                "candidate_id": {
                  "description": "The `id` you sent, trimmed.",
                  "type": "string"
                },
                "score": {
                  "description": "Relevance for the job. Use `rank` for ordering.",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1
                },
                "rationale": {
                  "description": "Why the candidate holds this position, in French.",
                  "type": "string"
                }
              },
              "required": [
                "rank",
                "candidate_id",
                "score",
                "rationale"
              ]
            }
          }
        },
        "required": [
          "rankings"
        ],
        "description": "Ranked candidates."
      },
      "MatchSkillsRequest": {
        "oneOf": [
          {
            "required": [
              "skill"
            ]
          },
          {
            "required": [
              "skills"
            ]
          }
        ],
        "type": "object",
        "properties": {
          "skill": {
            "description": "One free-text skill. Send `skill` or `skills`, not both.",
            "type": "string",
            "minLength": 1
          },
          "skills": {
            "description": "Batch of free-text skills. Non-string items are converted with `String()`.",
            "minItems": 1,
            "maxItems": 100,
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "top_k": {
            "description": "Matches per skill. Defaults to `5` with `skill`, `3` with `skills`.",
            "type": "integer",
            "minimum": 1,
            "maximum": 50
          }
        }
      },
      "SkillMatchResult": {
        "title": "SkillMatchResult",
        "type": "object",
        "properties": {
          "query_skill": {
            "description": "Your skill, trimmed.",
            "type": "string"
          },
          "total_results": {
            "description": "Number of `results`, at most `top_k`.",
            "type": "integer"
          },
          "results": {
            "description": "Closest catalog skills, best first.",
            "type": "array",
            "items": {
              "title": "SkillMatch",
              "type": "object",
              "properties": {
                "rank": {
                  "description": "Position, 1 is the closest.",
                  "type": "integer",
                  "minimum": 1
                },
                "skill": {
                  "description": "Catalog label.",
                  "type": "string"
                },
                "similarity_score": {
                  "description": "Cosine similarity rounded to 4 decimals. `1` for an exact label or synonym match.",
                  "type": "number",
                  "maximum": 1
                },
                "domain": {
                  "description": "Catalog domain, e.g. `Technologie`.",
                  "type": "string"
                },
                "subcategory": {
                  "description": "Catalog subcategory. Empty string when the skill has none.",
                  "type": "string"
                }
              },
              "required": [
                "rank",
                "skill",
                "similarity_score",
                "domain",
                "subcategory"
              ]
            }
          }
        },
        "required": [
          "query_skill",
          "total_results",
          "results"
        ],
        "description": "Single mode (`skill`)."
      },
      "SkillMatchBatchResult": {
        "title": "SkillMatchBatchResult",
        "type": "object",
        "properties": {
          "total_queries": {
            "description": "Number of skills sent.",
            "type": "integer"
          },
          "successful_matches": {
            "description": "Number of items with `success: true`.",
            "type": "integer"
          },
          "results": {
            "description": "One item per input skill, in input order. Duplicates are kept.",
            "type": "array",
            "items": {
              "title": "SkillBatchItem",
              "type": "object",
              "properties": {
                "query_skill": {
                  "description": "The input skill.",
                  "type": "string"
                },
                "success": {
                  "type": "boolean"
                },
                "results": {
                  "description": "Present when `success` is `true`.",
                  "type": "array",
                  "items": {
                    "title": "SkillMatch",
                    "type": "object",
                    "properties": {
                      "rank": {
                        "description": "Position, 1 is the closest.",
                        "type": "integer",
                        "minimum": 1
                      },
                      "skill": {
                        "description": "Catalog label.",
                        "type": "string"
                      },
                      "similarity_score": {
                        "description": "Cosine similarity rounded to 4 decimals. `1` for an exact label or synonym match.",
                        "type": "number",
                        "maximum": 1
                      },
                      "domain": {
                        "description": "Catalog domain, e.g. `Technologie`.",
                        "type": "string"
                      },
                      "subcategory": {
                        "description": "Catalog subcategory. Empty string when the skill has none.",
                        "type": "string"
                      }
                    },
                    "required": [
                      "rank",
                      "skill",
                      "similarity_score",
                      "domain",
                      "subcategory"
                    ]
                  }
                },
                "error": {
                  "description": "Present when `success` is `false`: `Empty skill`, or `The AI processing step failed. Please try again later.` when matching failed (retry this skill).",
                  "type": "string"
                }
              },
              "required": [
                "query_skill",
                "success"
              ]
            }
          }
        },
        "required": [
          "total_queries",
          "successful_matches",
          "results"
        ],
        "description": "Batch mode (`skills`)."
      },
      "ListSkillsResponse": {
        "type": "object",
        "properties": {
          "total_skills": {
            "description": "Number of skills in the catalog.",
            "type": "integer"
          },
          "skills": {
            "description": "The whole catalog. There is no pagination.",
            "type": "array",
            "items": {
              "title": "CatalogSkill",
              "type": "object",
              "properties": {
                "skill": {
                  "description": "Catalog label.",
                  "type": "string"
                },
                "domain": {
                  "description": "Catalog domain, e.g. `Technologie`.",
                  "type": "string"
                },
                "subcategory": {
                  "description": "Catalog subcategory. Empty string when the skill has none.",
                  "type": "string"
                },
                "rank": {
                  "description": "1-based position in the catalog. Not a relevance score.",
                  "type": "integer",
                  "minimum": 1
                }
              },
              "required": [
                "skill",
                "domain",
                "subcategory",
                "rank"
              ]
            }
          }
        },
        "required": [
          "total_skills",
          "skills"
        ],
        "description": "The catalog."
      },
      "GetHealthResponse": {
        "type": "object",
        "properties": {
          "status": {
            "description": "`healthy` whenever the service answers.",
            "type": "string",
            "enum": [
              "healthy",
              "unhealthy"
            ]
          },
          "service": {
            "type": "string",
            "const": "HireLayer API"
          },
          "skills_loaded": {
            "description": "Number of skills in the loaded catalog.",
            "type": "integer"
          },
          "timestamp": {
            "description": "Server time, ISO 8601 UTC.",
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
          }
        },
        "required": [
          "status",
          "service",
          "skills_loaded",
          "timestamp"
        ],
        "description": "The service is up."
      },
      "ParseResumeV2Request": {
        "type": "object",
        "properties": {
          "file": {
            "type": "string",
            "format": "binary",
            "description": "The resume: PDF, DOCX, ODT, PPTX, ODP, TXT, JPG or PNG."
          },
          "webhook_url": {
            "description": "URL that receives the result when processing ends.",
            "type": "string",
            "format": "uri"
          },
          "application_id": {
            "description": "Your own reference, echoed in the webhook payload.",
            "type": "string"
          }
        },
        "required": [
          "file",
          "webhook_url"
        ]
      },
      "ParseResumeV2Response": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string",
            "const": "CV processing initiated."
          },
          "body": {
            "type": "null"
          },
          "error": {
            "type": "boolean",
            "const": false
          }
        },
        "required": [
          "message",
          "body",
          "error"
        ],
        "description": "Accepted. The result is sent to `webhook_url` later."
      }
    }
  }
}