# WeRecruit Jobs API

> Query jobs from WeRecruit boards through one API with ?source=werecruit — plus how WeRecruit's sitemap really behaves.

Source: https://hyperjobs.io/ats/werecruit

WeRecruit is a French multi-tenant ATS at `careers.werecruit.io`, used mostly by
French employers and recruitment agencies. (It is not the unrelated UK staffing
agency of the same name — different company, no boards.)

It is also the single best-behaved source of one thing that every other platform
on this site withholds.

## Finding boards is the hard problem, and WeRecruit just answers it

The instinct is that scraping an ATS is about fetching. It isn't. Fetching is the
easy half. The hard half is knowing **which boards exist**, because almost no
platform will tell you: every customer is an isolated subdomain or slug, there is
no global index, and a finished, tested adapter with no board list scrapes exactly
nothing. We have watched that happen to more than one adapter here.

WeRecruit is the exception. One unauthenticated request enumerates the entire
platform:

```bash
curl "https://careers.werecruit.io/sitemap.xml"
```

Measured 2026-07-15: **25,113 URLs, resolving to 22,653 job offers across 2,460
distinct company boards**, each entry carrying a `<lastmod>`. Not a sample, not a
seed list to grow from. The platform.

<Note title="For scale, what the alternative looks like">
  The other exceptions in this pool are [HireHive](/ats/hirehive)'s cross-tenant
  aggregator key and Dover's shared feed. Everywhere else, a board list is
  *assembled*: Common Crawl sweeps, Wayback derivation, public dataset imports,
  and patience. [Lever](/ats/lever)'s 4,803 tracked boards took real work, and
  Lever is a much bigger platform than WeRecruit.

  One request against a sitemap is not how this normally goes.
</Note>

## The sitemap is the only route

The obvious follow-up is to fetch each board page and read its offers. That
returns nothing, and it returns nothing at HTTP 200:

- Board pages are **AngularJS-rendered** and contain **zero** offer links in the
  HTML that arrives.
- The only JSON-LD on an offer page is an `Organization` block. There is no
  `JobPosting`, so a standard schema.org extractor finds nothing to extract.
- There is no JSON API. We checked the bundle.

So the shape is fixed: sitemap for discovery **and** listing, then one HTML fetch
per offer. Fetch the sitemap once and cache it — it's 3.8MB, and re-pulling it
for each of 2,460 boards would be absurd.

## What the adapter reads

Offer pages are parsed rather than deserialized, and the useful fields are in
unusual places:

| Where | What it holds |
| --- | --- |
| `<h1>` | The job title |
| `div.description.rich-text` | The body |
| `<title>` | Contract type, location **and** employer — the only copy of any of them |
| sitemap `<lastmod>` | The only date the platform exposes |

That `<title>` is doing a lot of work. It's structured
`"{title} - {contract} {à|in} {location} | … | {company}"` and it is genuinely
the only place the contract type, the location and the employer name appear —
the page renders them client-side, so there is nothing else to read. Take the
**last** pipe segment as the employer, not the second: some boards insert a
marketing segment (`Offre d'emploi`) in between. Accents arrive as hex entities
(`&#xE9;` for é), and the body is rich HTML with nested divs, so a lazy
`.*?</div>` truncates it at the first one.

Descriptions came back at **100%** on the boards tested (`broadpeak` 2/2,
`aero-interim` 29/29). The `<lastmod>` is a modification stamp rather than a true
publish date, which makes it a freshness signal — WeRecruit re-stamps offers on
publish, so it's close, but it is not the same claim.

## Or use ours

The sitemap is pulled once and cached rather than re-fetched per board, the
`<title>` is split from the right so a marketing segment can't be mistaken for
the employer, the hex entities are decoded, and the body is taken by walking the
div nesting rather than by a regex that stops at the first `</div>`. `<lastmod>`
is carried as the freshness signal it is and not relabelled as a publish date.
Then the usual: dedup and the [company join](/docs/objects/company), normalised
onto the same [schema](/docs/objects/job) as every other source.

One thing worth stating plainly, since this page has spent most of its length on
discovery: best discovery in the pool is not the same as biggest source in the
pool. WeRecruit is complete because its board list is complete, which is a
different virtue from being large. 22,653 offers is a good number and it is not
[SmartRecruiters](/ats/smartrecruiters).

Query it with `?source=werecruit`. The keys of
[`/v1/meta`](/docs/api-reference/meta)`.sources` are the values `?source=`
actually accepts, with counts — read that rather than hardcode a list.

[Sources & coverage](/docs/reference/sources) · [all platforms](/ats) ·
[quickstart](/docs/quickstart).
