All platforms

IBM Jobs API

Query IBM's postings through one API with ?source=ibm — plus the lang:'zz' trick that returns the whole board and the fields named only by number.

?source=ibmsource reference

IBM is not a platform. It's one employer with a bespoke careers site, and this adapter reaches IBM and nobody else. One Greenhouse adapter reaches every company on Greenhouse; this one reaches IBM. company is ignored — there is one.

Its jobs are worth having on their own, and a bespoke site is invisible to platform discovery, so the only way to have them is an adapter you own. We own it: query it with ?source=ibm. /v1/meta is the authoritative live vocabulary, with the current count per source.

How IBM Careers actually works#

The careers UI is backed by Elasticsearch, and it POSTs to a search endpoint that returns the full job body inline — so, like Google, this is a complete-return source with no per-job N+1:

BASH
curl -X POST "https://www-api.ibm.com/search/api/v2" \
  -H "Content-Type: application/json" \
  -d '{"appId":"careers","scopes":["careers2"],"sm":{"query":"","lang":"zz"},"size":100,"from":0}'

hits.hits[]._source carries title, url, body (the full ~3.5k-char JD) and a handful of keyword fields; hits.total.value is the board total. You page with from, and size caps around 100.

`lang: 'zz'` means every locale at once

The magic is the empty query paired with lang: "zz". zz isn't a real language — it's a non-locale that tells IBM's index don't filter by locale, so one query walks the entire global board instead of one country's slice. Send a real language code and you'd silently scope yourself to a fraction of it.

The fields you need are named by number#

The useful facets don't have readable names. Work arrangement, category and location arrive as field_keyword_17, field_keyword_18 and field_keyword_19 — Elasticsearch keyword slots — and you simply have to know which number is which:

field_keyword_17  →  "Hybrid" | "Remote"                              (work arrangement)
field_keyword_18  →  "Professional" | "Entry Level" | "Internship"    (career level)
field_keyword_19  →  "Pune, IN"                                       (location)

field_keyword_18 does double duty — it drives employment type (only "Internship" becomes an intern role) and seniority ("Entry Level" maps through; "Professional" is deliberately left null rather than guessed). The stable id is the requisition number in the URL (jobId=58347), preferred over the Elasticsearch document hash so it survives a reindex.

What bites you#

There is no posting date. IBM's search endpoint exposes none, so posted_at is null on this source and ?time_frame= can't filter what isn't published — the same gap Trakstar Hire has.

The body has a teaser twin. _source.body is the full description; _source.description is a 250-character teaser. Read the wrong one and every posting looks populated while carrying an eighth of its text. The adapter takes body and only falls back to the teaser when it's empty.

Opaque field numbers are a standing liability. field_keyword_17/18/19 are positional in spirit even though they're keyed — a reindex could renumber them, and nothing about the name would tell you the location had moved.

Or use ours#

An Elasticsearch POST with the lang:"zz" whole-board trick, numbered keyword fields decoded, the full body taken over its teaser, a requisition id lifted from the URL, and a source with no posting date handled honestly — with IBM's rows in the same Job shape as every other source, dedup against the same role on LinkedIn, and the adapter's upkeep being ours instead of yours. Same deal as Amazon, Apple, Uber, TikTok, Google and Microsoft.

Sources · Quickstart

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

Get an API key