DEV Community

Daniel Meshulam
Daniel Meshulam

Posted on

Workday's job API tells you there are 2,000 jobs, then says 0 on page two

Workday is where large enterprises actually post. NVIDIA has 2,000 open roles
there, Salesforce 1,477, Adobe 832. It answers an anonymous POST with no key.

It also has two behaviours that are not in any documentation you can read
without an account, and both of them fail silently. One of them costs you 98%
of the board without raising anything.

The number that changes after page one

Ask for the first twenty postings and the response carries a total:

POST /wday/cxs/nvidia/NVIDIAExternalCareerSite/jobs
{"appliedFacets":{}, "limit":20, "offset":0, "searchText":""}

  20 jobPostings,  total: 2000
Enter fullscreen mode Exit fullscreen mode

Ask for the next twenty and the count is gone:

offset 20   ->  20 jobPostings,  total: 0
offset 40   ->  20 jobPostings,  total: 0
Enter fullscreen mode Exit fullscreen mode

Not null, not absent. Zero. The postings keep coming; only the count collapses.

Measured on four enterprise tenants:

tenant total at offset 0 at offset 20 at offset 40
NVIDIA 2000 0 0
Salesforce 1477 0 0
Adobe 832 0 0
Sony 94 0 0

Same shape every time, so this is Workday and not one tenant's configuration.

Why that costs you 98% of the board

Here is the loop everyone writes, and it is not a bad loop:

offset, out = 0, []
while True:
    page = fetch(offset)
    posts = page["jobPostings"]
    if not posts:
        break
    out += posts
    offset += len(posts)
    if offset >= page["total"]:      # looks obviously right
        break
Enter fullscreen mode Exit fullscreen mode

On page two page["total"] is 0, and 20 >= 0 is true. The loop exits,
reports no error, and hands back what it has.

I ran both versions against NVIDIA:

declared total on page one              2000
the obvious loop collected                40      2%
keeping the first total instead         2000    100%
Enter fullscreen mode Exit fullscreen mode

Forty postings out of two thousand, and nothing anywhere says so. No exception,
no warning, no partial-result flag. Just a job board that looks very quiet.

The fix is one line moved:

offset, out, total = 0, [], None
while True:
    page = fetch(offset)
    posts = page["jobPostings"]
    if not posts:
        break
    out += posts
    offset += len(posts)
    if total is None:                # the first answer is the only honest one
        total = page.get("total") or 0
    if total and offset >= total:
        break
Enter fullscreen mode Exit fullscreen mode

Two changes that matter. Keep the first total. And guard on total being
truthy, so a board that never reports a usable count still runs to the empty
page instead of stopping immediately.

The page size is capped at 20, and 21 is a 400

The other undocumented one. limit is not a suggestion:

limit=20  ->  HTTP 200
limit=21  ->  HTTP 400
limit=25  ->  HTTP 400
limit=50  ->  HTTP 400
Enter fullscreen mode Exit fullscreen mode

Exactly twenty. And the failure arrives as a 400, which reads as "your request
body is malformed" rather than "that number is too big", so the natural
reaction is to go looking at your JSON.

Two thousand postings at twenty a page is a hundred round trips. That is the
price, and there is no parameter that lowers it.

You cannot build the URL from a company name

A Workday address carries three separate unknowns:

https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite
        ^^^^^^ ^^^                    ^^^^^^^^^^^^^^^^^^^^^^^
        host   data centre            career site name
Enter fullscreen mode Exit fullscreen mode

The host label is usually the company, but the data centre is wd1 through
wd12 with no pattern, and the site name is whatever whoever set it up typed.
External_Career_Site, external_experienced, SonyGlobalCareers,
NVIDIAExternalCareerSite. Guessing across that space is mostly 404s.

The API path adds a fourth part that the public URL does not show:

public   https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite
api      https://nvidia.wd5.myworkdayjobs.com/wday/cxs/nvidia/NVIDIAExternalCareerSite/jobs
                                              ^^^^^^^^^^^^^^^
Enter fullscreen mode Exit fullscreen mode

On all four tenants I checked, that middle segment is the host label again.
Build it with a literal None in there and you get a 422, not a 404, which
at least fails in a way that does not look like a wrong company name.

So take the careers URL from the customer. It is the one thing they always
have, and it is the only reliable source for all three parts.

What the postings look like

Sparse, and worth knowing before you design a schema around it:

{
  "title": "Distinguished Software Architect - Deep Learning",
  "externalPath": "/job/US-CA-Santa-Clara/...",
  "locationsText": "US, CA, Santa Clara",
  "postedOn": "Posted Today",
  "bulletFields": ["JR2001234"]
}
Enter fullscreen mode Exit fullscreen mode

Five keys. No department, no employment type, no salary, no description, and
postedOn is a relative phrase rather than a date.

Counted across 80 NVIDIA postings:

Posted 30+ Days Ago    20
Posted 5 Days Ago      13
Posted 13 Days Ago     13
Posted 2 Days Ago      12
Posted Today            5
Posted Yesterday        3
Enter fullscreen mode Exit fullscreen mode

Do not parse that into a timestamp. A quarter of them are "30+ Days Ago",
which has no date in it at all, and a timestamp you invented is worse than a
string you kept: the string is obviously approximate and the timestamp is not.
Give it its own field and let the consumer decide.


Measured on 2026-08-02. Four tenants carried a usable count: NVIDIA,
Salesforce, Adobe and Sony. Dell reported 0 at offset 0 as well, which is the
third trap below rather than a fifth data point. I
maintain a set of ATS scrapers that normalise
nine of these systems into one row shape, which is how I ran into all of it.

Top comments (0)