All platforms

Recruitee Jobs API

Query jobs from Recruitee boards through one API — and how Recruitee's offers endpoint, multi-city locations and string-typed salaries behave.

?source=recruiteesource reference

Recruitee is collaborative hiring software, popular with small and mid-sized European companies. Boards sit at <company>.recruitee.com, and one unauthenticated call returns every live offer with its description inline. The data model is unusually generous — Recruitee publishes structured education, seniority, category, working hours and salary where most platforms publish a string — and the one thing it will do to you is rate-limit you.

Try it#

BASH
curl -G https://api.hyperjobs.io/v1/jobs \
  -H "Authorization: Bearer $HYPERJOBS_KEY" \
  --data-urlencode "source=recruitee" \
  --data-urlencode "time_frame=7d" \
  --data-urlencode "limit=2"
Illustrative — the shape, not a live response
{
  "data": [
    {
      "id": "recruitee:heliolabs_senior-frontend-developer",
      "title": "Senior Frontend Developer",
      "company": {
        "slug": "helio-labs",
        "name": "Helio Labs",
        "domain": "heliolabs.nl",
        "website": "https://heliolabs.nl",
        "logo": "https://media.licdn.com/dms/image/…",
        "industry": "Information Technology",
        "employee_count": 70
      },
      "location": {
        "remote": false,
        "countries": ["Germany"],
        "cities": ["Berlin"],
        "regions": ["Berlin"],
        "raw": ["Berlin, Berlin, Germany"]
      },
      "employment_type": ["FULL_TIME"],
      "work_arrangement": "Hybrid",
      "seniority": "Mid-Senior level",
      "education_requirements": ["bachelor degree"],
      "job_language": "en",
      "salary": {
        "min": 5200,
        "max": 6800,
        "currency": "EUR",
        "unit": "MONTH",
        "annual_min": 62400,
        "annual_max": 81600,
        "summary": null
      },
      "skills": ["react", "typescript"],
      "taxonomies": ["Software"],
      "apply_url": "https://heliolabs.recruitee.com/o/senior-frontend-developer/c/new",
      "url": "https://heliolabs.recruitee.com/o/senior-frontend-developer",
      "source": "recruitee",
      "posted_at": "2026-07-10T08:00:00.000Z",
      "expires_at": null,
      "updated_at": "2026-07-14T06:12:55.000Z"
    }
  ],
  "total": 2,
  "limit": 2,
  "offset": 0
}

Note unit: "MONTH" with annual_min filled in beside it. Monthly pay is normal on European boards, and min: 5200 is not a low salary. This is the trap salary data is about.

How Recruitee boards actually work#

One GET, no auth, no pagination, whole board:

BASH
curl "https://<company>.recruitee.com/api/offers/"

<company> is the subdomain. Response is { "offers": [...] }. The trailing slash matters more than it should.

The description is only half the description#

description holds the body. requirements is a separate HTML block holding the qualifications — present on roughly 90–100% of offers. They are two different fields, and the posting page renders both. Read only description and you store a job ad with its requirements section missing, from a field that looks perfectly populated.

We concatenate them.

Structured fields, and they're actually filled#

Recruitee's coded fields are close to fully populated where they exist:

FieldVocabulary
education_codebachelor_degree, master_degree, high_school, … — near 100%
category_codehealthcare, engineering, information_technology, … — near 100%
experience_codeentry_level, mid_level, experienced, manager, …
employment_type_codefulltime, parttime, contract, …
max_hours_per_week~98% on EU boards

experience_code is Recruitee's own vocabulary, not LinkedIn's, so experienced has to be mapped rather than passed through.

Three workplace booleans#

remote, hybrid and on_site all ship, all near-100% present. They are not mutually exclusive: a hybrid job also reads remote: true on some boards. So hybrid has to win first, then remote, then on_site — check remote first and every hybrid role is mislabelled. Older boards omit the newer flags entirely and only carry on_site, where on_site: false is the only remote signal available.

The language is in the key-set#

translations is an object keyed by language code. Exactly one key means the posting is in that language — a genuinely structured language signal, on roughly 95% of offers, hiding in the shape of an object rather than in a field.

What bites you#

Salary bounds are strings

Recruitee returns {"min": "1400", "max": "2100"} — quoted. Arithmetic on them concatenates instead of adding, and a strict schema rejects the whole offer. Coerce before you use them.

Roughly 30% of offers are multi-city, and the flat fields carry only one. city / state_code / country describe one of a posting's locations. The full set is in locations[], each entry with a full region name rather than a code. Worse, on multi-city offers the display location string is frequently a useless Remote job. Read the flat fields and you lose most of a posting's geography while everything still looks populated. Older boards omit locations[], so you need the flat fields as a fallback and the array as the primary.

country_code is ISO-2 and will be read as a US state. Berlin, DE parsed naively becomes Berlin, Delaware. Resolve the code to a canonical country name before you build the string.

Slugs are only unique within a company. Two companies both have an offer slugged sales-manager. A key of recruitee:<slug> collides across boards — ours is scoped to recruitee:<company>_<slug>.

close_at exists but is nearly always null — around 2% of offers. A null expiry doesn't mean the job runs forever; it means Recruitee didn't say. Expiry here comes from board diffing, not from the field.

The requirements bullets are mostly not skills. They read like Je bent van onbesproken gedrag (politiescreening) — whole sentences, not tags. Harvesting every <li> as a skill fills your skill index with prose. We keep only bullets short enough to plausibly be a term.

Or use ours#

Recruitee rate-limits per IP, and we measured 38% of requests lost to 429s at concurrency 5. That's why it sits in the proxy-only tier here alongside Workable — it works direct at low volume and falls over at scale, which is the failure mode you don't discover until you've built the thing.

Past the fetch: description and requirements joined, locations[] read so multi-city offers keep their cities, ISO codes resolved to real country names, the hybrid-first boolean order, salary bounds coerced from strings and normalised so a monthly EUR band is comparable to an annual USD one, company-scoped ids, and the company join. We track 1,344 Recruitee boards.

Recruitee is attempted every third cycle — see coverage — so a new posting can take a few hours to land.

Quickstart · Pricing

Every source above normalises onto the same schema, with the hiring company already joined onto each posting.

Get an API key