Indeed API: Passing false to One Filter Returned a 502, Twice
Omit the flag and you get 245 jobs with 38 duplicates. Switch it on and you get 234 with 19. Send it as false and the call fails without charging.

Copy this line to your agent to pull a clean list of job postings.
set up https://monid.ai/SKILL.md and use indeed /search_jobs with exclude_staffing_agencies true
We ran the same job search three times on 2026-10-01, changing one boolean. Omitting it returned 245 postings. Setting it to true returned 234. Setting it to false, which should be the same as omitting it, returned a 502 from the provider, and did so on both attempts. Neither failure was billed. The more useful finding is what the filter actually buys you, and it is not the eleven postings it hides. This guide runs through Monid, the OpenRouter for agent tools.
What does the Indeed API return?
A search over live postings, plus a detail call, and a response shape you should know about before you write the parser.
The two calls
Search jobs takes a query and optional location, fromage for recency in days, jt for job type, explvl for experience level, and a couple of exclusion filters. Get job details takes one posting and returns the full record.
The response is not a jobs array
This is the part that costs people an hour. The search response hands back Indeed's own page-state object under output.data, the same structure their web app uses, with well over a hundred keys covering layout flags, experiment toggles and ad slots.
Two of those keys are the ones you want:
{ "totalJobCount": 245,
"uniqueJobsCount": 207 }
Both sit at the top level of data. Everything around them is app state, and reaching for a field called something like results will give you nothing.
Why two counts
Because postings repeat. The same role gets listed more than once, by the employer and by whoever is recruiting for them, and Indeed tracks both the raw match count and the de-duplicated one. The distance between the two numbers is the interesting part, and the next sections are about moving it.
📖 See also Jooble API and Job Postings Data: Hiring as a Buying Signal
Why did false return a 502?
We do not know why, and we can tell you exactly when, which is enough to avoid it.
Same query, same location, same recency window, one parameter changed:
exclude_staffing_agencies | HTTP | Billed | Result |
|---|---|---|---|
| omitted | 200 | yes | 245 postings |
true | 200 | yes | 234 postings |
false | 502 | no | nothing |
false, retried | 502 | no | nothing |
Twice, not once, so it is not a flaky upstream. The JSON literal false reaches something that cannot handle it, while the absence of the key is handled fine.
Why this particular shape of bug bites
Because false is what a configuration object produces when a feature is off. Code that builds a query from settings will emit the key with a falsy value rather than leaving it out, so the default path through a sensible codebase is the one that fails. You will see it as an intermittent 502 that correlates with a checkbox nobody thought about.
The fix is to omit the key rather than send the negative. Build your query object and delete any key whose value is false before you send it.
The billing detail worth keeping
Both 502s were billed zero. The three successful calls were billed one each. That is consistent with what we have measured elsewhere: a provider that refuses with a visible HTTP error does not cost you, while a 200 that hands back an empty record is a different story. Failing loudly is the cheap kind of failure.
What does excluding staffing agencies actually do?
Not what the name suggests, and the real effect is better.
filter off totalJobCount 245 uniqueJobsCount 207 duplicates 38
filter on totalJobCount 234 uniqueJobsCount 215 duplicates 19
Read the second column again. Switching the filter on lowered the raw count by 11, which is what you would expect from a filter. It also raised the unique count by 8.
How a filter adds results
It does not, really. The two columns are counted differently, and what moved is the duplication. With agencies in the set, 245 raw postings collapsed to 207 unique ones, so 38 were repeats. With agencies out, 234 collapsed to 215, leaving 19.
The filter halved the duplicate load. That is the actual product: staffing agencies repost the same role, often several times and often with slightly different titles, and removing them is less about hiding eleven listings than about stopping the churn that makes a job feed unreadable.
What this means if you are building a feed
Turn it on by default. The cost is eleven postings in our sample, most of them duplicates of things you still have. The benefit is half the noise.
Measure your own ratio before you tune anything else. The gap between totalJobCount and uniqueJobsCount is a one-call diagnostic on how polluted a given query is. A query where the two numbers are close does not need much cleaning. A query where they are far apart is one where your de-duplication will earn its keep.
Do not treat either number as the job market. They are counts of postings, and one open role can be several postings while one posting can be several seats.
How do you run a job search and read the counts?
For agents
Grab an API key at app.monid.ai, then paste this to your agent and hand it the key:
set up https://monid.ai/SKILL.md
It learns the whole discover, inspect, run workflow itself. More in the agent quickstart.
For humans
npm install -g @monid-ai/cli
monid keys add -k <your-api-key> -l main
The call
monid run -p indeed -e /search_jobs \
--query '{"query":"data engineer","location":"Austin, TX","fromage":"7","exclude_staffing_agencies":true}'
fromage is the maximum age in days and takes 1, 3, 7, 14 or 30. location accepts a city and state, a ZIP, or the literal remote. jt filters job type, explvl filters seniority, and start pages in increments of ten.
The one rule about the booleans
Send true or leave the key out. Never send false. If you are generating the query from a config object, strip falsy keys first:
const q = Object.fromEntries(
Object.entries(raw).filter(([, v]) => v !== false && v !== undefined)
);
Reading the result
const { totalJobCount, uniqueJobsCount } = run.output.data;
const duplicateLoad = totalJobCount - uniqueJobsCount;
Log duplicateLoad per query. It is free, it comes with every call, and it tells you which of your saved searches is drowning before a human complains.
Give this to your agent![]()
Set up https://monid.ai/SKILL.md, and then use Monid to search Indeed for data engineer roles in Austin posted in the last week, exclude staffing agencies, and tell me the duplicate load and the ten companies posting most.📖 See also LinkedIn Job Data: Which API Stays Fresh
Which endpoint should I use for which job?
| Endpoint | What it does | Input | Returns | Latency we saw | Billing |
|---|---|---|---|---|---|
indeed/search_jobs | Search live postings | query, location, fromage, filters | Page state with the two counts | Around 17 s | Per call |
indeed/get_job_details | One posting in full | A posting id | The full record | Around 6 s | Per call |
apollo/organizations/{organization_id}/job_postings | One company's open roles | An Apollo organization id | That company's postings | Under 2 s | Per call |
hunterio/companies/find | Who the employer is | domain | Firmographics | Around 2 s | Per call |
Every row was verified with monid inspect on 2026-10-01 and the latency column is what we actually waited. Billing shape is given rather than figures, because shape drives design and current numbers live on monid.ai/tools.
The rows answer different questions and people reach for the wrong one regularly. The first is a market view: what is being hired for, across employers. The third is an account view: what this one company is hiring for, which is the shape you want for a buying signal rather than for a job board. Searching the market and filtering down to one company works and costs far more than asking about that company directly.
When should you not use this?
Four cases.
You want one company's hiring, not a market. Go at it by organization. A market search plus a filter is the expensive way to answer a question that has a direct endpoint.
You need salary data you can trust. Postings carry salary when the employer chose to publish it, which is a minority and a biased minority. A compensation question needs a compensation source.
You are counting open roles. Postings are not roles. One job can be posted several times, one posting can be hiring five people, and stale listings stay up. The two counts here measure postings, and the gap between them is exactly the warning that the mapping is not one to one.
You want to contact the applicants. Nothing here is about candidates, and the parts of this data that touch real people carry obligations that an API does not discharge for you.
And the disclosure: we resell these endpoints. The most valuable line in this post is that you should omit a parameter rather than send it as false, which is worth exactly one 502 to anyone, including people who never buy anything from us.
Conclusion
Three calls, one parameter, on 2026-10-01. Omitting exclude_staffing_agencies returned 245 postings. Setting it to true returned 234. Setting it to false returned a 502 from the provider on both attempts, and neither failed call was billed. If you build your query from a config object, strip the falsy keys before you send it, because false is what that code emits by default and it is the one value this filter will not take.
The filter itself is worth turning on, and not for the reason the name implies. It lowered the raw count by 11 while raising the unique count by 8, which means what really moved was the duplication: 38 repeated postings became 19. Staffing agencies repost the same role, and removing them halves the churn in a feed rather than hiding much real hiring.
The response shape is the last thing to know before you start. It is Indeed's own page-state object, not a jobs array, and the two fields worth reading are totalJobCount and uniqueJobsCount at the top level of data. Their difference is a free, per-query measure of how noisy a search is, and it is the number to log.
Free next step: run monid inspect -p indeed -e /search_jobs, make one call with the filter on and one with it omitted, and print both counts. The duplicate load is the whole argument. Start at monid.ai.
FAQ
Why would a boolean parameter reject false?
We can only tell you what we observed: true and omission both returned HTTP 200, while the JSON literal false returned a 502 from the provider on two separate attempts. A 502 is a gateway error, so the most likely story is that the value reaches a layer that expects the key to be present only when the filter is wanted, and something downstream chokes on the negative rather than ignoring it. What matters operationally is that this is the default output of ordinary code: a settings object with the feature off serializes to exclude_staffing_agencies: false, so the failing path is the one a careful codebase takes. Filter falsy keys out of the query before sending, and the problem disappears.
What is the difference between totalJobCount and uniqueJobsCount?
totalJobCount is how many postings matched. uniqueJobsCount is how many of them Indeed considers distinct after collapsing repeats of the same role. In our unfiltered run those were 245 and 207, so 38 postings were duplicates of something else in the same result set. With staffing agencies excluded they were 234 and 215, leaving 19. The gap is the useful quantity and it is free with every call: a wide gap means your query is pulling in a lot of reposted listings and your de-duplication matters, a narrow gap means the feed is already clean. Neither number is a count of open jobs, because one role can be several postings and one posting can be several seats.
How do you get the actual posting text?
The search response carries counts and page state, not bodies. For the content of a posting you call the detail endpoint with a posting id, which returns the full record and took about six seconds in our testing. That makes the cost shape obvious: a search is one call, and reading every result is one call per posting on top of it. For a market scan, pull the search, use the counts and whatever summary fields you need to rank, and only fetch details for the postings that survive your filter. Fetching every body for a 200-posting market before you have decided what you care about is the expensive mistake here.
Is this for recruiting or for market research?
Both work, but they want different calls. Market research lives on the search endpoint: query a role and a location, watch the counts over time, and track which employers appear. Recruiting and sales intelligence usually want one company at a time, and for that the organization-level postings endpoint is the right tool, because it answers directly instead of making you filter a market down to one name. There is a third use that people underrate: hiring is a buying signal. A company that just posted four roles for a stack you integrate with is a company with budget and a new problem, and that is an account-shaped question rather than a job-board one.
Last updated October 2026.

