Products
HireLayer Resume Parser
HireLayer resume parser API reference: send a PDF, Word or image resume in one multipart request and get the candidate back as JSON, photo included, in 70 languages.
The HireLayer Resume Parser is a resume parsing REST API for developers. One multipart POST /api/v4/resumes/parse request with an X-API-Key header returns the candidate as JSON in the same HTTP response, under resume. It reads 15 file formats, scans and phone photos included, in 70 languages, for 1 credit per resume; the same file sent again is answered from storage for free. The Free plan includes 50 credits a month with no card.
info_candidate: name, lower-cased email, E.164 phone number, location with coordinates, LinkedIn and GitHub URLs, job title, highest EQF education level and experience bracket.work_experiencesandeducations: every position and diploma with normalized dates, contract type and location.languages: each language with a CEFR-based level such asProfessional Working Proficiency (B2).skills: typed asHard skill,Soft skillorSoftware skill, and resolved to the HireLayer skills taxonomy when a match exists.info_resume: the candidate photo cropped from the resume (face_url), the full extracted text and the resume language.
The parse is stored in the resume database of the workspace under its application_id: score it with the Resume Scorer or find it with Talent Pool Search without sending the file again.
- Endpoint
POST /api/v4/resumes/parse- Input
multipart/form-data· 15 file formats · 4.5 MB max- Output
- JSON, usually in 25 to 35 seconds
- Price
- 1 credit; 0 when the file is already stored
/api/v4/resumes/parseReads a resume file and returns it structured, stored in the workspace under an application_id.
- Authentication
X-API-Keyheader- Billing
- 1 credit; 0 when the file is already stored
- Client timeout
- Client timeout ≥ 150 s
Headers
HireLayer-WorkspacestringYour customer's workspace id. Case-insensitive, created on first use. Absent, calls use
default.
Request body
Content type multipart/form-data
filefilerequiredPDF, DOC, DOCX, ODT, PPT, PPTX, ODP, XLS, RTF, TXT, HTML, Markdown, JPG/JPEG, PNG or BMP, 4.5 MB at most.
application_idstringLetters, digits and
. _ : ~ -, up to 200 characters.do_not_store_databooleanEnterprise plan only, else
403 enterprise_plan_required: process without storing anything. See Storage.default
falsewebhook_urlstring (URL)HTTPS URL on the standard port, without credentials, that receives the result, signed.
Response
200Parsed (or found already stored).
application_idstring | nullThe resume's id in the workspace, yours or derived from the file.
nullwithout storage.request_idstringQuote it when contacting support.
storedbooleanalready_storedbooleanThe same file was already stored; it was not parsed again.
resumeParsedResumeThe resume, with the fields and values of the legacy
/api/v3/parserJSON. Only the envelope differs:application_idis at the top level of the v4 response, andstatusanderrorsgive way to HTTP status codes.›Show 9 child fieldsHide child fields
info_resumeResumeInfoThe document.
›Show 6 child fieldsHide child fields
date_parsingstringParsing time in UTC,
YYYY-MM-DDTHH:mm:ss, without a zone suffix.languagestring | nullMain language of the resume, uppercase ISO 639-1 code such as
ENorFR.urlstring | nullURL of the stored original file. Anyone with the link can open it.
nullwithdo_not_store_data=true, because the file is not stored.face_urlstring | nullCandidate photo cropped from the resume.
nullwhen no face is found and for.txtfiles.face_url_expires_atstring | nullWith
do_not_store_data=true: expiry of the temporaryface_url, 10 minutes after parsing, UTCYYYY-MM-DDTHH:mm:ss. Otherwisenullandface_urldoes not expire.textstringFull extracted text, including OCR. May end with
[EXTRACTED_HYPERLINKS]and[PDF_ANNOTATION_TEXTS]sections.
info_candidateCandidateThe candidate profile.
›Show 19 child fieldsHide child fields
full_namestring | nullFirst and last name.
last_namestring | nullLast name.
first_namestring | nullFirst name.
emailstring | nullLower-cased email address.
phone_numberstring | nullE.164 phone number, e.g.
+33612345678. The country is inferred from the resume, France by default. A number written in the resume that is not valid for its country is still returned, in E.164 when its length fits, otherwise as written, with a warning.birth_datestring | nullDate of birth,
YYYY-MM-DD. A year alone becomesYYYY-01-01.agenumber | nullAge in years, as stated or inferred.
availability_nowboolean | nullWhether the candidate is available now.
availability_datestring | nullDate from which the candidate is available,
YYYY-MM-DD.driver_licensestring[]Driving licences as written, e.g.
Permis B.job_titlestring | nullTarget or current job title. Falls back to the most recent position.
education_namestring | nullHighest qualification relevant to the job title.
education_levelstring | nullHighest European Qualifications Framework (EQF) level.
"Level 1""Level 2""Level 3""Level 4""Level 5""Level 6""Level 7""Level 8""Other"experience_levelstring | nullTotal professional experience bracket.
"0 to 1 year""1 to 3 years""3 to 5 years""5 to 10 years""More than 10 years"linkedin_urlstring | nullLinkedIn profile URL.
github_urlstring | nullGitHub profile URL.
other_urlsstring[]Other URLs in the resume (portfolio, website…).
locationLocationWhere the candidate lives.
›Show 9 child fieldsHide child fields
countrystring | nullCountry name.
country_codestring | nullISO 3166-1 alpha-2 country code, e.g.
FR.regionstring | nullRegion.
departmentstring | nullDepartment or county.
citystring | nullCity.
postal_codestring | nullPostal code.
full_addressstring | nullAddress as a single line.
latitudenumber | nullLatitude from geocoding.
longitudenumber | nullLongitude from geocoding.
mobilityMobilityWhere else the candidate can work.
›Show 2 child fieldsHide child fields
can_work_in_other_citiesboolean | nullWhether the resume says the candidate can work elsewhere.
nullwhen not mentioned.other_citiesMobilityCity[]Cities explicitly named as possible work locations.
›Show 5 child fieldsHide child fields
citystring | nullCity named in the resume.
countrystring | nullCountry, when stated or reliably inferred.
postal_codestring | nullPostal code, when present in the resume.
latitudenumber | nullLatitude from geocoding.
longitudenumber | nullLongitude from geocoding.
work_experiencesWorkExperience[]›Show 12 child fieldsHide child fields
company_namestring | nullEmployer name.
job_titlestring | nullJob title for this position.
descriptionstring | nullTasks, responsibilities and achievements.
contract_typestring | nullCanonical contract type. Unrecognized values become
Other."Permanent contract""Fixed-term contract""Temporary assignment""Internship""Apprenticeship""Freelance""Volunteering""Other"start_datestring | nullStart date,
YYYY-MM-DD. A year alone becomesYYYY-01-01; a month alone, its first day.end_datestring | nullEnd date,
YYYY-MM-DD.nullfor an ongoing role. A year alone becomesYYYY-12-31; a month alone, its last day.currently_activeboolean | nullWhether this is a current position.
work_experience_countrystring | nullCountry of the position.
work_experience_country_codestring | nullISO 3166-1 alpha-2 country code.
work_experience_citystring | nullCity of the position.
work_experience_postal_codestring | nullPostal code of the position.
experience_durationnumber | nullDuration of this position in months, as extracted.
educationsEducation[]›Show 8 child fieldsHide child fields
degree_titlestring | nullName of the degree or programme.
school_namestring | nullSchool or institution.
descriptionstring | nullDetails of the programme.
degree_typestring | nullEQF level of the degree, expected to be one of
Level 1…Level 8orOther. Not normalized: treat other strings asOther.start_datestring | nullStart date,
YYYY-MM-DD.end_datestring | nullEnd date,
YYYY-MM-DD.currently_activeboolean | nullWhether the programme is in progress.
locationEducationLocationLocation of the school.
›Show 7 child fieldsHide child fields
countrystring | nullCountry name.
country_codestring | nullISO 3166-1 alpha-2 country code, e.g.
FR.regionstring | nullRegion.
departmentstring | nullDepartment or county.
citystring | nullCity.
postal_codestring | nullPostal code.
full_addressstring | nullAddress as a single line.
rome_jobsRomeJob[]Occupations predicted from
info_candidate.job_titlewith the French ROME taxonomy.›Show 5 child fieldsHide child fields
job_titlestringOccupation label, in French.
job_codestringOccupation (appellation) code.
rome_titlestringROME job family label, in French.
rome_codestringROME code, e.g.
M1805.prediction_scorenumberConfidence between 0.7 and 1.
languagesLanguage[]›Show 2 child fieldsHide child fields
languagestringLanguage name as written in the resume.
levelstring | nullCEFR-based proficiency, or
nullwhen not stated."Native or Bilingual (C2)""Full Professional Proficiency (C1)""Professional Working Proficiency (B2)""Limited Working Proficiency (B1)""Advanced Basic Proficiency (A2)""Introductory Proficiency (A1)"
skillsSkill[]›Show 5 child fieldsHide child fields
skill_titlestringTaxonomy skill name when
statusisnormalized: in French for a resume in French, in English for a resume in any other language. Otherwise the text from the resume. One resume skill can give several normalized skills (Word/Excel).skill_typestring"Hard skill""Soft skill""Software skill"statusstringnormalized: resolved to the HireLayer Skills taxonomy.raw: kept as written in the resume."normalized""raw"domainstring | nullPrimary domain of the taxonomy skill, in the language of
skill_title.nullwhenstatusisraw.subcategorystring | nullPrimary family of the taxonomy skill, in the language of
skill_title.nullwhenstatusisraw.
certificationsstring[]interestsstring[]
warningsstring[]Notes about skipped steps (OCR, photo, geocoding, occupation codes) or about a document that may not be a resume.
credits_usedintegerCredits this call cost.
≥ 0
202Accepted. Sent with webhook_url; the result is posted there.
statusstring"pending"request_idstringapplication_idstring | nullwebhook_urlstring (URL)
Errors
- 400invalid_requestfile is required: send the resume in the multipart part file.
param:file. No partfile.Fix, then retry
- 400invalid_requestSend the body as multipart/form-data, with the file in the part file.
The body is not
multipart/form-data.Fix, then retry
- 400
- 400invalid_requestapplication_id cannot be score: that name is reserved for POST /api/v4/resumes/score.
param:application_id.application_idis not a valid id, or is a name the paths reserve (parse,score,search).Fix, then retry
- 400invalid_requestwebhook_url must be an https URL on the standard port, without credentials.
param:webhook_url.webhook_urlis not a public HTTPS URL.Fix, then retry
- 400invalid_requestHireLayer-Workspace must contain letters, digits and . _ : ~ -, up to 200 characters.
param:HireLayer-Workspace.HireLayer-Workspaceis not a valid id.Fix, then retry
- 401
- 401missing_api_keyAuthorization: Bearer is not accepted: send the key in the X-API-Key header.
The key sent as
Authorization: Bearer: it goes inX-API-Key.Fix, then retry
- 401missing_api_keyThe key goes in the X-API-Key header, not in the URL.
The key sent in the URL: it goes in
X-API-Key.Fix, then retry
- 401invalid_api_keyInvalid API key: it is unknown, malformed or revoked.
The key is unknown, malformed or revoked.
Fix, then retry
- 403insufficient_creditsThis call needs 1 credit and none is left. Nothing was processed or billed.
No credit left for the parse.
Fix, then retry
- 403enterprise_plan_requireddo_not_store_data is available on the Enterprise plan only. Nothing was processed or billed.
param:do_not_store_data.do_not_store_data: truebelow the Enterprise plan.Do not retry
- 413
- 422resume_unreadablefile cannot be read as a resume: it is empty, damaged or in an unsupported format. Nothing was billed.
param:file. The file cannot be read as a resume.Do not retry
- 429rate_limitedToo many files are being parsed at once for this account: retry after the Retry-After delay.
Too many parses at once. Header
Retry-After: <seconds>.Retry with backoff
- 500internal_errorInternal error: retry later. If it persists, contact support with the request_id.
An unexpected error.
Retry with backoff
- 502model_unavailableThe analysis did not complete after several attempts. Nothing was stored or billed: retry with backoff.
The analysis did not complete.
Retry with backoff
- 502internal_errorThe service could not be reached: retry later.
The service could not be reached.
Retry with backoff
- 503internal_errorThe credit service is briefly unavailable: retry in a second.
The credit service is briefly unavailable. Header
Retry-After: <seconds>.Retry with backoff
- 504internal_errorThe service did not answer in time. The change may still be applied: read it before sending it again.
No answer in time.
Retry with backoff
Behavior
- Credits
- 1. Sending a file already stored under the same id returns the stored resume with
already_stored: trueand costs 0. - Updating a resume
- send the new file with the resume's
application_id(yours or a derived one such asfile_…). The new parse replaces the stored resume, and the resume's scores becomestale: they stay readable and are evaluated again the next time the resume is sent toPOST /resumes/score. - Time
- about 25 to 35 seconds, 145 seconds at most; set a client timeout of 150 seconds. With
webhook_url, the call answers202at once and the result is posted to that URL. - Without storage (
do_not_store_data=true) application_idisnull,info_resume.urlisnull,face_urlis valid 10 minutes. Withwebhook_url, the202names noapplication_ideither, and the result waits until it is delivered, 24 hours at most.
curl -X POST https://hirelayer.co/api/v4/resumes/parse \
-H "X-API-Key: $HIRELAYER_API_KEY" \
-F "file=@resume.pdf;type=application/pdf"import os
import requests
with open("resume.pdf", "rb") as file:
response = requests.post(
"https://hirelayer.co/api/v4/resumes/parse",
headers={"X-API-Key": os.environ["HIRELAYER_API_KEY"]},
files={"file": ("resume.pdf", file, "application/pdf")},
timeout=150,
)
response.raise_for_status()
data = response.json()
print(data["resume"]["info_candidate"])import { readFile } from 'node:fs/promises'
const form = new FormData()
form.append(
'file',
new Blob([await readFile('resume.pdf')], { type: 'application/pdf' }),
'resume.pdf'
)
const response = await fetch('https://hirelayer.co/api/v4/resumes/parse', {
method: 'POST',
headers: {
'X-API-Key': process.env.HIRELAYER_API_KEY!,
},
body: form,
signal: AbortSignal.timeout(150_000),
})
if (!response.ok) {
throw new Error(`HireLayer ${response.status}: ${await response.text()}`)
}
const data = await response.json()
console.log(data.resume.info_candidate){
"application_id": "file_2c982f413a5e9e79563f8a1fcb5c7caa",
"request_id": "bbf29879-8b02-4b0d-8f66-59bfb2f87662",
"stored": true,
"already_stored": false,
"resume": {
"info_resume": {
"date_parsing": "2026-10-26T22:38:42",
"language": "FR",
"url": "https://files.hirelayer.co/originals/cv-paul-verse.pdf",
"face_url": "https://files.hirelayer.co/faces/cv-paul-verse-face.jpg",
"face_url_expires_at": null,
"text": "FREELANCE\n\nIngénieur IA / Développeur full stack\n\nEXPÉRIENCES\n2024-25\n\nINGÉNIEUR IA - Novalys AI\nDéveloppement d’une plateforme SaaS intégrant\nplusieurs fonctionnalités basées sur l’IA générative.\nConception du produit de bout en bout : backend,\ninfrastructure cloud, orchestration des modèles et\nmonitoring des usages.\n- Stack API (serverless AWS) : API Gateway, Lambda, S3,\nDynamoDB, Terraform, Nod…"
},
"info_candidate": {
"full_name": "PAUL VERSE",
"last_name": "VERSE",
"first_name": "PAUL",
"email": "paul.verse@example.com",
"phone_number": "+33600000000",
"birth_date": null,
"age": null,
"availability_now": true,
"availability_date": null,
"driver_license": [],
"job_title": "Ingénieur IA / Développeur full stack",
"education_name": "Master 2 en Ecole d’ingénieur du numérique",
"education_level": "Level 7",
"experience_level": "5 to 10 years",
"linkedin_url": "https://linkedin.com/in/paulverse",
"github_url": "https://github.com/paulverse",
"other_urls": [],
"location": {
"country": "France",
"country_code": "FR",
"region": "Ile-de-France",
"department": null,
"city": null,
"postal_code": null,
"full_address": null,
"latitude": 48.922413,
"longitude": 2.357223
},
"mobility": {
"can_work_in_other_cities": null,
"other_cities": []
}
},
"work_experiences": [
{
"company_name": "Novalys AI",
"job_title": "Ingénieur IA",
"description": "Développement d’une plateforme SaaS intégrant plusieurs fonctionnalités basées sur l’IA générative. Conception du produit de bout en bout : backend, infrastructure cloud, orchestration des modèles et monitoring des usages. Stack API (serverless AWS) : API Gateway, Lambda, S3, DynamoDB, Terraform, Node.js. Stack SaaS : Next.js, React, serveur cloud géré avec Coolify (déploiement, monitoring). Réduction de 50 % de la latence moyenne sur les principales fonctionnalités IA. Solution scalable, 500k+ documents traités.",
"contract_type": null,
"start_date": "2024-01-01",
"end_date": "2025-12-31",
"currently_active": false,
"work_experience_country": "France",
"work_experience_country_code": "FR",
"work_experience_city": null,
"work_experience_postal_code": null,
"experience_duration": 24
},
{
"company_name": "COP1",
"job_title": "Directeur du réseau",
"description": "Gouvernance et direction bénévole d’une association d’aide aux jeunes et étudiants, présente dans 27 villes. 130 000 paniers/an, 3 M€ de budget annuel, 25 salariés. Développement d’une application web ERP personnalisée (Next.JS) intégrant l’intelligence artificielle et optimisant stocks, approvisionnement, suivi des véhicules et gestion des bénévoles. Expansion du réseau : de 20 à 27 antennes. Croissance des bénévoles : de 3 000 à 5 000 membres.",
"contract_type": "Volunteering",
"start_date": "2024-01-01",
"end_date": "2025-12-31",
"currently_active": false,
"work_experience_country": "France",
"work_experience_country_code": "FR",
"work_experience_city": null,
"work_experience_postal_code": null,
"experience_duration": 24
}
],
"educations": [
{
"degree_title": "Master 2 en Ecole d’ingénieur du numérique",
"school_name": "EFREI",
"description": "BIG DATA & IA. Cours intégralement en Anglais.",
"degree_type": "Level 7",
"start_date": "2020-01-01",
"end_date": "2025-12-31",
"currently_active": false,
"location": {
"country": null,
"country_code": null,
"region": null,
"department": null,
"city": null,
"postal_code": null,
"full_address": null
}
}
],
"rome_jobs": [
{
"job_title": "Développeur / Développeuse full-stack",
"job_code": "38976",
"rome_title": "Développeur / Développeuse web",
"rome_code": "M1855",
"prediction_score": 0.817
},
{
"job_title": "Développeur / Développeuse IA",
"job_code": "510526",
"rome_title": "Ingénieur / Ingénieure en Intelligence Artificielle (IA)",
"rome_code": "M1889",
"prediction_score": 0.779
}
],
"languages": [
{
"language": "Anglais",
"level": "Native or Bilingual (C2)"
},
{
"language": "Allemand",
"level": "Introductory Proficiency (A1)"
}
],
"skills": [
{
"skill_title": "Python",
"skill_type": "Software skill",
"status": "normalized",
"domain": "Développement logiciel",
"subcategory": "Langages de programmation"
},
{
"skill_title": "Next.js",
"skill_type": "Software skill",
"status": "normalized",
"domain": "Développement logiciel",
"subcategory": "Développement frontend"
},
{
"skill_title": "Vue.js",
"skill_type": "Software skill",
"status": "normalized",
"domain": "Développement logiciel",
"subcategory": "Développement frontend"
},
{
"skill_title": "React",
"skill_type": "Software skill",
"status": "normalized",
"domain": "Développement logiciel",
"subcategory": "Développement frontend"
},
{
"skill_title": "Node.js",
"skill_type": "Software skill",
"status": "normalized",
"domain": "Développement logiciel",
"subcategory": "Développement backend"
}
],
"certifications": [
"AZURE - DATA SCIENTIST ASSOCIATE",
"HUGGINGFACE - DEEP RL CERTIFICATION",
"AWS - CLOUD PRACTITIONER"
],
"interests": [
"Sports",
"Voyages",
"Course, Trail (longue distance)",
"8 pays visités seuls"
]
},
"warnings": [],
"credits_used": 1
}{
"error": {
"code": "invalid_request",
"message": "file is required: send the resume in the multipart part file.",
"param": "file",
"doc_url": "https://hirelayer.co/api-docs/errors#invalid_request"
},
"request_id": "6f1c2a4e-1d2b-4c3a-9e8f-0a1b2c3d4e5f"
}With a webhook. With webhook_url, the call answers 202 at once and the result is posted to that URL, signed: see Webhooks.
curl -X POST https://hirelayer.co/api/v4/resumes/parse \
-H "X-API-Key: $HIRELAYER_API_KEY" \
-F "file=@resume.pdf;type=application/pdf" \
-F "webhook_url=https://example.com/webhooks/hirelayer"import os
import requests
with open("resume.pdf", "rb") as file:
response = requests.post(
"https://hirelayer.co/api/v4/resumes/parse",
headers={"X-API-Key": os.environ["HIRELAYER_API_KEY"]},
files={"file": ("resume.pdf", file, "application/pdf")},
data={
"webhook_url": "https://example.com/webhooks/hirelayer",
},
timeout=150,
)
response.raise_for_status()
data = response.json()
print(data["request_id"])import { readFile } from 'node:fs/promises'
const form = new FormData()
form.append(
'file',
new Blob([await readFile('resume.pdf')], { type: 'application/pdf' }),
'resume.pdf'
)
form.append('webhook_url', 'https://example.com/webhooks/hirelayer')
const response = await fetch('https://hirelayer.co/api/v4/resumes/parse', {
method: 'POST',
headers: {
'X-API-Key': process.env.HIRELAYER_API_KEY!,
},
body: form,
signal: AbortSignal.timeout(150_000),
})
if (!response.ok) {
throw new Error(`HireLayer ${response.status}: ${await response.text()}`)
}
const data = await response.json()
console.log(data.request_id){
"status": "pending",
"request_id": "bbf29879-8b02-4b0d-8f66-59bfb2f87662",
"application_id": "file_2c982f413a5e9e79563f8a1fcb5c7caa",
"webhook_url": "https://example.com/webhooks/hirelayer"
}Yes, the HireLayer resume parsing API also extracts the candidate’s photo. When a PDF, Word or image resume contains a photo, resume.info_resume.face_url links to the candidate’s face cropped from it as a separate image. There is no option to switch on and no extra credit: the photo comes with the same 1-credit parse.
face_urlisnullwhen no face is found, and always for.txtfiles.- A photo embedded in an HTML resume is returned the same way.
- By default the image is stored and
face_urldoes not expire. Withdo_not_store_data=true(Enterprise plan) it is a temporary link valid 10 minutes, untilface_url_expires_at: download the image if you need to keep it.
Yes, the HireLayer resume parser API handles scanned PDFs and resumes in multiple languages. A scanned PDF or a phone photo of a CV (JPG, PNG or BMP) is read with OCR and returns the same JSON as a text PDF, in any of the 70 supported languages, for the same single credit. There is no language parameter and no OCR option to switch on: send the file as it is.
- Scans take longer than the usual 25 to 35 seconds; keep a client timeout of 150 seconds.
- When no text can be read even with OCR, the call answers
422 resume_unreadableand no credit is used. info_resume.languagegives the main language of the resume as an uppercase ISO 639-1 code such asEN,FRorDE.- Free-text values stay in the language of the resume, with no translation.