Skip to content

Guides

Migrate from HrFlow.ai

Keep the code that reads HrFlow.ai profiles: point HrFlow.ai’s SDK at HireLayer, or call the Resume Parser with output_format=hrflow. Field mapping, errors and webhooks.

HireLayer answers in HrFlow.ai’s JSON format, so the code that reads your profiles stays. Choose how you call it: keep HrFlow.ai’s SDK and change two settings, or call the Resume Parser with output_format=hrflow. Both parse, bill and store the resume the same way: 1 credit, the resume stored in the workspace under its application_id.

HrFlow.ai-compatible APIoutput_format=hrflow
CallPOST /api/hrflow/v1/profile/parsing/filePOST /api/v4/resumes/parse?output_format=hrflow
ForHrFlow.ai’s SDK, or HTTP calls written for HrFlow.aiNew calls to HireLayer, read with your HrFlow.ai code
What changesThe API URL and the keyThe call; the code that reads the result stays
WebhooksNone: every parse answers 201webhook_url, signed

Set the API URL to https://hirelayer.co/api/hrflow/v1/ and the secret to your HireLayer key. The parsing call and its answer keep HrFlow.ai’s format.

import os

from hrflow import Hrflow

client = Hrflow(
    api_url="https://hirelayer.co/api/hrflow/v1/",
    api_secret=os.environ["HIRELAYER_API_KEY"],
    api_user="you@company.com",  # ignored
)

with open("resume.pdf", "rb") as pdf:
    response = client.profile.parsing.add_file(
        source_key="acme",
        profile_file=pdf,
        reference="cand_123",
        sync_parsing=1,
    )
profile = response["data"]["profile"]
curl -X POST https://hirelayer.co/api/hrflow/v1/profile/parsing/file \
  -H "X-API-KEY: $HIRELAYER_API_KEY" \
  -F "source_key=acme" \
  -F "reference=cand_123" \
  -F "file=@resume.pdf"
HrFlow.ai requestAt HireLayer
X-API-KEY headerYour HireLayer key
X-USER-EMAIL headerIgnored
fileThe resume
source_keyThe workspace: each Source keeps its profiles apart. Absent, default.
reference, else keyThe resume’s application_id, when it is a valid id; else one derived from the file. reference comes back as sent.
tags, metadatas, labels, created_atBack in the profile, your tags before HireLayer’s
sync_parsing and the other fieldsIgnored: the answer is always the real-time 201

Only parsing is served. Every other path under /api/hrflow/v1/ (profile retrieval, search, scoring, Sources) answers 404 in HrFlow.ai’s format. If your Source parses asynchronously (202, then a webhook or a GET), read the 201 body instead.

Add ?output_format=hrflow to POST /api/v4/resumes/parse: the same parse, answered 201 with HrFlow.ai’s body. output_format=hirelayer, the default, answers the Resume Parser’s own JSON. The legacy /api/v3/parser takes the same parameter and gives the same body.

curl -X POST -g "https://hirelayer.co/api/v4/resumes/parse?output_format=hrflow" \
  -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?output_format=hrflow",
        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["data"]["profile"]["info"])
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?output_format=hrflow', {
  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.data.profile.info)
201, cut
{
  "code": 201,
  "message": "Profile parsed successfully.",
  "data": {
    "parsing": {
      "key": "file_d7e21d373637a897b17b8fdf63122807",
      "…": "…"
    },
    "profile": {
      "key": "file_d7e21d373637a897b17b8fdf63122807",
      "reference": "file_d7e21d373637a897b17b8fdf63122807",
      "source_key": null,
      "info": {
        "full_name": "Elias MOREAU",
        "email": "elias.moreau@example.com",
        "…": "…"
      },
      "…": "…"
    }
  }
}

The full answer is the 201 example of Parse a resume. Send HireLayer-Workspace to name the profile’s Source (source_key). An unknown output_format is refused with 400 invalid_request before anything is billed.

HrFlow.ai fieldValue
keyThe resume’s application_id; without storage, the parse’s request_id. Not a 40-character HrFlow.ai key.
referenceYour application_id (or reference through the compatible API).
source_key, source.keyThe workspace, when you send HireLayer-Workspace (or source_key); else null.
created_at, updated_atParsing time, UTC: 2026-10-26T22:38:42+0000.
text, text_languageThe full text; its language as a lowercase ISO 639-1 code.
info.full_name, first_name, last_name, emailAs in the resume.
info.phoneE.164 (+33612345678), where HrFlow.ai keeps the number as written.
info.date_birth, info.driving_licenseDate of birth; licences joined with , .
info.locationtext, lat, lng, gmaps and the 21 fields keys; fields.country is ISO 3166-1 alpha-3 (FRA).
info.urls[]The LinkedIn, GitHub and other links found, typed by host.
info.pictureThe candidate’s photo cut from the resume, a link that expires.
experiences[], educations[]Company or school, title, description, location (text only), date_start and date_end as 2019-05-01T00:00:00, a key per entry.
experiences_duration, educations_durationYears over the periods with an end date, overlaps counted once.
skills[]{name, type, value: null}: hard and software skills are hard, soft skills soft; names in lowercase.
languages[]name as written, in lowercase; value the CEFR level (HrFlow.ai: null).
certifications[], interests[]{name, value: null}, in lowercase.
attachments[]The original file, or empty without storage.
tags[]Values HrFlow.ai has no field for: job_title, education_name, education_level, experience_level, age, availability_now, driver_license, can_work_in_other_cities, mobility_city, rome_code, rome_appellation_code.
ranges[]availability, when the resume gives a date.

data.parsing holds the same values in HrFlow.ai’s parsing layout: dates as {iso8601, text, timestamp}, persons, emails, phones and urls lists, and is_active on each experience and education.

Errors keep their HTTP status and headers, the request id included, with HrFlow.ai’s body: code is the status, message the HireLayer message, error_code the HireLayer error code to branch on.

401
{
  "code": 401,
  "message": "Invalid API key: it is unknown, malformed or revoked.",
  "error_code": "invalid_api_key"
}

With webhook_url and output_format=hrflow, the call answers the usual 202, and the resume.parsed event carries HrFlow.ai’s body in data, signed as every event. It is not HrFlow.ai’s event envelope: read data.data.profile. A failed parse sends resume.parse_failed with the HireLayer error.

  • Set a client timeout of 150 seconds: a parse takes about 25 to 35 seconds, and HrFlow.ai’s SDK sets none.
  • Search and scoring on stored profiles have their own HireLayer endpoints: Talent Pool Search and the Resume Scorer, on the resumes you parsed.
  • To keep nothing, send do_not_store_data=true (Enterprise plan): key is then the request_id and attachments is empty.