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.
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:
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.
Every source above normalises onto the same schema, with the hiring company already joined onto each posting.
Get an API key