# Deel Jobs API

> How Deel's job boards actually work, what its RSC flight payload really contains, and what ?source=deel returns in the HyperJobs API.

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

Deel is best known as an Employer of Record and global payroll platform, and as
of 2026 it runs a real multi-tenant ATS of its own. Customer boards live at
`jobs.deel.com/<slug>` — Klarna, Dott and Offensive Security among them — and
the boards are genuinely public: one unauthenticated GET returns a whole board
with every description already in the response. No auth, no pagination, no
detail pass.

## What the board looks like

```bash
curl "https://jobs.deel.com/job-boards/<slug>"   # 301 → /<slug>
```

There is no JSON API — `robots.txt` disallows `/api/` — and no JSON-LD. The board
is a Next.js app, and the data arrives as an **RSC flight payload**: a run of
`self.__next_f.push([1,"…"])` chunks embedded in the HTML. Everything is in
there, including all the bodies. Klarna's board is 81 jobs and 81 descriptions
from that one call.

Parsing it is the whole job, and it bites in three places:

1. **Decode and concatenate before parsing anything.** The chunks are JS string
   literals that split at arbitrary offsets — mid-row, mid-token, even
   mid-escape-sequence. Parse a chunk on its own and you get garbage.
2. **The rows are not newline-delimited.** They look like `{hexId}:T{hexLength},{payload}`,
   and the next row starts immediately after the payload with no newline between
   them. On Klarna, one newline precedes the first row and then 272,733
   characters run with none. A newline-anchored scan finds the first description
   and nothing else, which reads as "the list ships one body and the other 80
   need a detail fetch". It doesn't. It ships all 81.
3. **That length is in bytes, not characters.** Descriptions are full of
   multi-byte UTF-8 — curly quotes, "Nykøbing Falster", "Düsseldorf". Slice by
   character count and the parser desynchronises on the first one and silently
   corrupts every row after it.

Descriptions themselves are lazy references: a posting carries
`richtextDescription: "$23"`, which points at row `23` in the same payload. Get
the three above right and all of them resolve locally, with no second request.

Pagination doesn't exist, and that was proven rather than assumed: `?page=2`,
`?offset=50` and `?limit=5` all return a byte-identical board. The completeness
was checked against an independent oracle — the per-org sitemap that `robots.txt`
advertises — at 81/81, 29/29 and 21/21 across three tenants.

## What bites you

**The location is often a legal entity, not a place.** Deel is an EoR, so the
field carries the contracting entity the employer typed: `CellPoint Digital
India LLP`, `Natural Capital Research Limited`, `Head Office - Calgary`. Fed
straight to a location parser, 12 of 95 distinct values wrote **company names
into the cities column**. Stripping the org's own tokens and the entity suffix
recovers the real signal those strings carry — `India`, `Calgary` — rather than
throwing them away.

**The workplace type is hidden in that same string.** Deel publishes no
structured remote/hybrid field at all. What it has is `Brussels, Belgium (Hybrid)`,
`Tbilisi On-site`, `Remote - US`. If you don't lift it out of the location, the
arrangement is simply lost.

<Warning title="Compensation has no period, and the periods are mixed on one board">
  `currentCompensation` is `{currency, minAmount, maxAmount}` and there is no
  frequency key anywhere in the payload. Klarna lists `SEK 55,206` (monthly)
  beside `SEK 752,814` (annual) in the same field, same currency, with nothing
  distinguishing them. Assume a period and you store a Stockholm engineer's
  monthly pay as their annual salary — a 12x error large enough to clear every
  sanity check. Deel's own front-end renders these ranges period-less too.

  So a Deel posting gets its compensation as text and no structured salary. A
  wrong salary is worse than none.
</Warning>

**An unknown tenant returns HTTP 200, never a 404.** It's a ~15KB shell. The only
failure signal is the absence of the board props. A real but empty board is
distinguishable — it ships its settings plus an empty postings array — but you
have to look for the difference rather than trust the status code.

**Deel itself is not a tenant of Deel.** `jobs.deel.com/deel` redirects off the
platform to `www.deel.com/careers`, because Deel hires through Ashby.

## What you get

The flight payload parsed rather than worked around: every description resolved
out of the one call its posting already came in, no detail pass, and after
geocoding, full country coverage — 100% on both, measured across 29 tenants and
349 jobs. Deel is also one of the better platforms for the [company
join](/docs/objects/company): about 46% of tenants publish a privacy-policy link
that resolves to the employer's own domain, which is the only employer-domain
signal the board exposes.

Query it with `?source=deel`. The counts above are a measurement rather than a
promise, and they move — [`/v1/meta`](/docs/api-reference/meta) → `sources` is
the live vocabulary and the honest place to read both the value and the size of
it off.

See [all platforms](/ats) · [coverage](/docs/reference/coverage)
