# Google Jobs API

> Query Google's postings through one API with ?source=google — plus why the board hides in an AF_initDataCallback blob and every field is addressed by number.

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

Google is not a platform. Every multi-tenant source here is software that hosts
many employers' boards — one [Greenhouse](/ats/greenhouse) adapter reaches every
company on Greenhouse. This one reaches Google, and only Google. `company` is
ignored, because there is exactly one.

It's here because Google is large enough for its jobs to matter on their own, and
because it runs a bespoke careers site rather than buying an ATS — so no platform
adapter will ever discover it. If you want Google's postings, you write an adapter
for Google and you own it forever.

We wrote it: query it with `?source=google`.
[`/v1/meta`](/docs/api-reference/meta) is the authoritative live vocabulary, with
the current count per source.

## How Google Careers actually works

No browser, no auth, and — pleasantly — no per-job N+1. The results page
**server-renders** each page of jobs into the HTML as a JavaScript callback, and
`?page=N` walks it twenty jobs at a time:

```bash
curl "https://www.google.com/about/careers/applications/jobs/results/?page=1"
```

Buried in that HTML is a blob:

```js
AF_initDataCallback({ key: 'ds:1', data: [[…jobs…], …, 3202], sideChannel: {} });
```

`data[0]` is the page of jobs, `data[2]` is the whole-board total. Page 1 carries
the total and the rest of the pages fan out in parallel. Each record already holds
the full description — overview, responsibilities and qualifications inline — so
there is nothing to fetch twice.

<Note title="The old search API is gone">
  Google's `/api/v3/search` — the clean JSON endpoint most write-ups still point
  at — is **dead** (404). And it wouldn't help if it weren't: that per-keyword
  search capped out around 2,000 results. The no-query listing paginates the
  entire board with no such cutoff (measured: ~3,200 unique jobs to page 166).
</Note>

### Every field is addressed by number

The `ds:1` record is a **positional array**, not an object. There are no keys — you
read a job by index, and the indices are Google's, not yours:

```
[0] id   [1] title   [3] responsibilities   [4] qualifications
[9] locations   [10] overview   [14] published (unix seconds)   [20] level
```

Slot `[20]` is Google's `target_level`, decoded against the site's own
`?target_level=` facet: `1` early, `2` mid, `3` advanced, `5` intern. It's the one
field that gives you seniority, and a few short records don't carry it at all.

## What bites you

<Warning title="A shifted index mismaps silently, and nothing throws">
  Because the record is positional, there is no field name to disagree with. The
  day Google inserts one element and the description slides from slot `[10]` to
  `[11]`, your titles are fine, your ids are fine, and every body is quietly wrong.
  A named JSON field that vanishes reads null or throws; a reshuffled array keeps
  returning strings — just the wrong ones.
</Warning>

<Warning title="A throttled page looks like a shrunken board">
  Google is a complete-return source: the live set — and therefore what expires —
  is derived from what the scrape returns. So a run that loses pages to throttling
  looks exactly like a board that shrank, and would expire every job it failed to
  fetch. The guard is to **throw** (and retry) when the run comes back with less
  than 70% of Google's own reported total, rather than bank a decimated board.
  Fetch the whole thing or fetch none of it.
</Warning>

**The data lives in page markup, not an API.** It's extracted with a regex over the
`AF_initDataCallback(...)` blob. That's robust enough today and brittle by nature:
markup is not a contract.

**No structured salary.** Nothing in `ds:1` carries comp; whatever pay exists is
prose inside the qualifications HTML.

## Or use ours

Pagination off a total buried in a JS callback, a positional array read by index,
seniority decoded from a facet, and a completeness guard that refuses to expire a
throttled board — with Google's rows in the same [`Job`](/docs/objects/job) shape
as every other [source](/docs/reference/sources), dedup against the same role on
[LinkedIn](/ats/linkedin), and the adapter's upkeep being ours instead of yours.
Same deal as [Amazon](/ats/amazon), [Apple](/ats/apple), [Uber](/ats/uber),
[TikTok](/ats/tiktok), [Microsoft](/ats/microsoft) and [IBM](/ats/ibm).

[Sources](/docs/reference/sources) · [Quickstart](/docs/quickstart)
