# Datasheet: AI lab job postings

`postings.jsonl.gz` holds one row per job posting seen on the public job boards
of five AI labs (Anthropic, OpenAI, xAI, Mistral AI and Thinking Machines Lab)
since daily snapshots began on October 8, 2026. It is the data behind
<https://apps.lawrencewu.net/lab-hiring/>, rebuilt every day at about 14:00 UTC.
It is gzipped JSON Lines: one JSON object per line, UTF-8, sorted by lab and id.

## Why it exists

To track what frontier AI labs hire for, in their own words, day by day. Job
boards only show what is open now, so the history exists only because a
snapshot was taken each day. It cannot be backfilled: a role that opened and
closed between snapshots is missing.

## What a row is

One posting, keyed by `lab` + `id` (the board's own posting ID). A posting open
in several cities is one row. A row stays in the file after the posting leaves
the board, with `closed` set to the first snapshot day it was missing.

| Field | Type | Meaning |
|---|---|---|
| `lab`, `lab_name` | string | `anthropic`, `openai`, `xai`, `mistral`, `thinking-machines` and the display name |
| `id` | string | The board's posting ID (Greenhouse or Ashby) |
| `title` | string | Title as posted, whitespace collapsed |
| `department` | string or null | The board's department; for Ashby boards, "Department / Team" when they differ |
| `locations` | list of strings | Locations as posted |
| `metros` | list of strings | Locations grouped by metro area (SF Bay Area, New York, London...) |
| `workplace` | `onsite`, `hybrid`, `remote` or null | As the board labels it |
| `employment_type` | string or null | Ashby's employment type (FullTime, Intern...); null on Greenhouse boards |
| `posted` | date or null | The board's own publish date. Boards reset it on reposts |
| `first_seen` | date | First snapshot that saw the posting. 2026-10-08 means "open when tracking began" |
| `last_seen` | date | Last snapshot that saw it |
| `closed` | date or null | First snapshot where it was missing. Closed is not filled: a role can be pulled, paused or reposted under a new ID |
| `function` | string | One of research, engineering, infra, datacenter, deploy, product, gtm, policy, operations, humandata, other |
| `function_rule` | string | Which rule placed it (see the page's "How postings are sorted") |
| `seniority` | string | Level stated in the title: intern, entry, mid, senior, staff, principal, lead, manager, director, exec, mts (Member of Technical Staff, one title for every level), unstated |
| `family` | string or null | Area within research (pretraining, posttraining, alignment, interp, evals, multimodal, efficiency, agents, science, research-infra, research-other) or within engineering and infrastructure (security, it, ml-infra, hardware, data, applied-ml, product-eng, cloud-infra, backend, eng-other); null for other functions |
| `flags` | list of strings | AI topics the posting text mentions: agents, coding_agents, evals, rl, safety, inference, customer_deployment |
| `skills` | list of strings | Skills the posting text names, from the page's skill dictionary |
| `pay` | object or null | Posted range: `min`, `max`, `currency`, `per` (year or hour), `basis` (base, or ote for on-target earnings), `source` (ashby: the board's compensation field; text: read from the posting text) |
| `text_versions` | list of dates | Each day the posting's text was new or changed. The posting archive began 2026-10-09, so postings open on 2026-10-08 start there |
| `url` | string | The original posting on the lab's board |

## How the tags are made

Plain regular expressions, no model, printed in full on the page under "How
postings are sorted" and kept in `scripts/classify.py`. Function, seniority and
family come from the title (then the department name); the first rule that
matches wins. Skills and flags come from the posting text after removing the
sentences a lab repeats across a quarter or more of its postings (company
blurbs, benefits, equal-opportunity text). A flag means the text mentions the
topic, not that the job is about it.

Tags on open postings are recomputed every day with the current rules; a
closed posting keeps the function it had when it closed. When a seniority,
family or flag rule changes, `scripts/retag.py` recomputes those three tags for
every posting from the stored text. Pay is read conservatively and left null
when the text does not match the expected pattern; equity is never included.

## What is not here

The description text. The full postings, exactly as each board's API returned
them, are kept in the site's repository (`apps/web/lab-hiring/data/postings/`)
but not served; follow `url` for the text. Google DeepMind and Meta's
superintelligence lab are left out because their jobs sit on general careers
sites with no public job-board API.

## Caveats

- Postings are not hires. Some stay up for months, some are evergreen, one
  posting can cover several hires.
- A lab whose board fails to load, or returns under half of the previous day's
  postings, is skipped that day; its postings are not marked closed.
- If a lab changes job-board vendor, its postings will all close on one day and
  reappear under new IDs.
- Titles are the labs' own; seniority reflects how a lab writes titles as much
  as whom it hires (most titles state no level).

## Source and use

Each lab's public job board API (Greenhouse Job Board API or Ashby public
posting API), read once a day by `scripts/snapshot.py`. The postings are the
labs' own public listings; this file republishes their metadata with derived
tags and links back to each one. Cite as: Lawrence Wu, "What the AI labs are
hiring for" dataset, apps.lawrencewu.net/lab-hiring, with the date you
downloaded it.
