# Uber Jobs API

> Query Uber's postings through one API with ?source=uber — plus the pagination footgun that returns the same 1,000 jobs every call.

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

Uber is not a platform. It's one employer running a bespoke careers site, and this
adapter reaches Uber's postings and nobody else's. Everywhere else, an adapter is
leverage: write [Greenhouse](/ats/greenhouse) once and you reach every company that
bought Greenhouse. Write this one and you reach Uber.

It exists because Uber is big enough for its jobs to matter on their own, and
because a bespoke careers site is invisible to platform discovery — there's no
vendor fingerprint to detect. The cost is an adapter per company, maintained
forever, breaking on that company's schedule.

## How Uber's careers API actually works

A POST, no real authentication, full job objects with markdown descriptions
inline:

```bash
curl -X POST "https://www.uber.com/api/loadSearchJobsResults?localeCode=en" \
  -H "x-csrf-token: x" \
  -d '{"params":{},"page":0,"limit":100}'
```

The `x-csrf-token` header is required and its value is not checked. A literal `x`
is accepted. It's a gate that only stops requests that don't know the gate exists.

Descriptions come inline as **markdown** — not HTML, which is unusual in this pool
and means no detail call and no tag soup.

<Warning title="Nest `limit` and `page` under `params` and you get the same jobs forever">
  `limit` and `page` must be **top-level** keys in the body, sitting beside
  `params`, not inside it.

  Put them inside `params` — which is exactly where they look like they belong —
  and the API doesn't reject them. It **ignores** them, falls back to its default
  page of around a thousand results, and returns that same page for every call you
  make. Your loop runs, every request succeeds, every response is full of real
  jobs, and you collect the same rows over and over into a duplicate storm.

  Nothing errors. The only symptom is a job count that climbs while the number of
  distinct ids doesn't.
</Warning>

### `totalResults` is a 64-bit envelope

The total isn't a number. It's an object, and the value lives at
`totalResults.low`:

```json
"totalResults": { "low": 4123, "high": 0, "unsigned": false }
```

That's a 64-bit integer serialisation leaking through the JSON layer. Read
`totalResults` as a number and you get an object where you expected an integer;
compare it against your offset and the loop's termination condition is nonsense.
The adapter reads `.low`.

### What the adapter reads

| Field | What it becomes |
| --- | --- |
| `id`, `title` | Identity. Rows missing either are dropped |
| `description` | The body, inline, as markdown |
| `department`, `team` | Two levels of grouping, both structured |
| `timeType` | `"Full-Time"`, `"Intern"` — employment type and commitment |
| `creationDate` | The posted timestamp, already ISO |
| `location`, `allLocations[]` | City / region / country objects, joined |

`allLocations[]` is preferred over `location` when present, so a posting open in
several cities keeps all of them instead of collapsing to the first.
`creationDate` arriving as a real ISO string is worth appreciating quietly — it's
the field that goes wrong on [Amazon](/ats/amazon) and is absent entirely on
[IBM](/ats/ibm), whose search endpoint publishes no posting date at all.

## What bites you

**The duplicate storm is silent.** Worth repeating, because it's the failure mode
that produces a confident, wrong dataset instead of an error. Every request
returns 200 and real data.

**There's no salary.** Nothing structured in the payload. Whatever comp exists is
prose inside the markdown.

**A placeholder CSRF token is an invitation to change.** `x-csrf-token: x` working
is a quirk of today's gate, not a contract — precisely the sort of thing that gets
tightened without notice, and when it does, this adapter is what breaks.

**One adapter, one company, forever.** Same deal as [Apple](/ats/apple),
[Amazon](/ats/amazon), [TikTok](/ats/tiktok), [Google](/ats/google),
[Microsoft](/ats/microsoft) and [IBM](/ats/ibm): a dedicated adapter, its own
schedule of breakage, one employer's jobs at the end of it.

## Or use ours

Top-level pagination keys, `.low` read out of the envelope, `allLocations`
preferred over `location`, markdown kept intact — with Uber'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), expiry by board diffing
since the payload declares none, and a single-company adapter's upkeep being ours
rather than yours.

Query it with `?source=uber` — the same string that keys
[`/v1/meta`](/docs/api-reference/meta) → `sources`. That endpoint is the live
vocabulary and carries the counts as they move; it's worth reading rather than
trusting a list on a page.

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