Job board jobs: one keyword across Indeed and the company boards you name, merged and de-duplicated into one feed — the same role on two boards is one row and one charge. One price for the whole feed, no add-on events, from $1.80 per 1,000 job listings; a scheduled run pays only for new jobs.
The whole "Maximum cost per run" you set is now spent on jobs. Before this build the run held back about a tenth of your cost cap before it started — a cushion for platform running costs which, on this actor's pricing, your cap does not pay for at all: your cap buys jobs, and the running costs are ours. So the cushion was simply cap you had asked to spend and could not get. On a run capped at $0.10 with jobs at $0.0018 the plan asked for 50 jobs where the cap paid for 55 — 5 jobs of your own cap, unspendable by construction. From this build the plan uses your cap in full, so a run asks for as many jobs as your cap can really pay for. Nothing you are charged changes, and nothing can be charged above the cap you typed: every charge is still checked against what is left of your cap at the moment it is posted, and a run that reaches your cap still stops cleanly, ships what it had already fetched, and tells you what is left. No price, input field or output column changes.
1.0.71 — 2026-09-19
The page now says how often job listings change and what it costs to come back. This actor remembers every job it delivered for your account and charges nothing for one you already have, but nothing on the page told you when a second run is worth making — so the memory only paid off for buyers who worked that out themselves. A new section says what a company board and an Indeed keyword search each do between runs, names a daily and a weekly cadence and who each one suits, refuses to invent a turnover rate we have not measured, and states the part that decides it: with no fee for starting a run and repeats never charged, a tighter schedule costs the same money over the same week as a looser one. It also names the outer bound — the memory's own 90 days. Nothing you type, no output column, no charged event and no price moved.
1.0.70 — 2026-09-18
The status line can no longer run past the run page's own limit, and the sentences about your money are now the last to go. At the counts a real run reaches, 9 of the 128 endings a run can have measured over the 500-character cut — the widest 556 — and the platform trims the END, where the repeat-check clause and the cap that stopped the run both sit. The order it gives way in was also upside down: "charged once" and the uncharged list yielded before a clamped setting that costs you nothing, and at the floor the line dropped the note saying counts had moved to OUTPUT while the "open an issue" ask stayed. Now the non-money counts yield first, then the ask, then the sample preamble shortens — and every drop still says so.
1.0.69 — 2026-09-18
The Google Jobs search fee this page mentions is live now, so the page states it plainly instead of announcing it. The job-board shelf section told you that Google Jobs would begin adding a $0.004 search fee on a coming date. That fee took effect, so the sentence is now in the present tense: Google Jobs adds one search fee of $0.004 per search that returns listings, and a search that returns nothing pays nothing. The shelf line naming the three actors that carry a small delivery-conditional fee reads the same way. This build changes nothing about this actor: no input field, output column, charged event or price of its own moved.
1.0.68 — 2026-09-14
A hiring board that is not this company's is no longer opened, delivered or charged. When we read a company's careers page we take the hiring board it links. But a page can link several boards — a portfolio page, a partner list, an agency site — and we took whichever one appeared FIRST in the page's source, which is arbitrary. When that was another company's board, its openings were delivered under the company you asked about and charged as theirs. The board carrying the company's own name is now the one we take, wherever it sits on the page. If a page names several boards and none of them belongs to the company you asked about, none of them is opened and none is charged — and the row names the addresses we found, so you can pass the right one in directly. A page that names a single board is unchanged: that is the company pointing at its own board, and its tenant name often is not the domain name (yeti.com hires on yeticoolers), so nothing there had to move. This can only ever remove a charge, never add one.
A careers address that redirects somewhere else is no longer read as this company's. Some domains redirect to a different company's careers site, and we read whatever answered — so a redirect could hand us a stranger's board and its openings were delivered and charged under your company. We now re-check the address that actually answered, before anything is read off it. When it belongs to somebody else, nothing on it is fetched and nothing is charged, and the row says which address answered. A redirect that stays with the company — www., a careers. or jobs. host, the company's own country domain — is unchanged and still resolves exactly as before.
A board we will not attribute is never read as "this company is not hiring". Those are two different facts, and the coverage row now says which one it is.
Nothing else moved: no input field, output column, event or price changed, and no run charges more than it did.
1.0.67 — 2026-09-14
A token that can read your repeat memory but not write to it no longer costs you the same jobs all over again. This actor remembers every job it delivers for your account, under every identity that job carries, so a re-run skips what you already have and never charges for it twice. Permission to read a key-value store and permission to write one are granted separately, so a run started with a scoped API token could open that memory, recognise your repeats perfectly, and still be refused every update to it. Nothing said so: the run looked completely normal, and your next run collected, re-fetched every job board that saw each role, and charged for all of it again. The run page now says the memory could not be updated and what that will cost next time, one uncharged row in your dataset says the same thing in full and names the permission to grant, and the run log says it beside the platform's own message — the moment the first refusal happens, not at the end of the run. Give the token key-value store Write (and Create) under Settings → API & Integrations, or set Actor runs to Full access. A run whose memory writes normally is unchanged in every respect. Nothing you type, no output column, no event and no price moved.
The sentence the run page shows when the repeat check is off is now the same on every actor we publish. It said the same thing in different words on each one; it now reads identically everywhere, so running two of our actors under one limited token shows you one sentence instead of two. The full wording — the exact permission to grant and where to grant it — is unchanged and still on the uncharged row in your dataset and in the run log.
A run started with a limited API token now tells you why it charged you for jobs you already had. This actor remembers every job it has delivered to your account, under every identity the job carries, in a key-value store in your own account — so a re-run skips what you already have and never charges for it twice, which is what makes a schedule cheap. A run started with a scoped API token in restricted-access mode cannot open that store at all unless the token is allowed key-value store Read, Write and Create — and when it cannot, nothing can be recognised as a repeat, so everything is collected and charged again. The run page used to say only that the repeat check was unavailable, which gave you nothing to do about it. It now names the cause and the fix, one uncharged row at the top of your dataset says the same thing (so an agent reading only rows sees it too), and the run log says it beside the platform's own message. Grant the token those three permissions under Settings → API & Integrations, or set Actor runs to Full access, or start the run from the console. Every other reason the store cannot be read reads exactly as before. Nothing you type, no output column, no event and no price moved, and the run still delivers and charges exactly as it did.
1.0.66 — 2026-09-13
The store card no longer reads as though a keyword searched every company's own hiring board — it searches the boards you name. The card said "one keyword across Indeed and any company's own hiring board". Your keyword does search Indeed on its own; a company board is read only when you put that company in Company boards — a domain, a careers URL, a board URL or a plain name — and the keyword then filters what that board returns. In the console this was mostly hidden, because the form arrives with an example company already filled in. From the API, or from an AI agent sending {"query": "..."} and nothing else, it was not: the run came back with Indeed rows only and said nothing about why. The card now says "the company boards you name". No input field, output column, charged event or price moved.
Two things the card never mentioned, both already true, are now on it. This actor charges ONE event and has no add-on fee of any kind, so the card says "one price for the whole feed, no add-on events". And every run remembers the postings it delivered for your account, so a repeat run skips what you already have — no row, no charge — which is what makes a schedule cheap; the card now says a scheduled run pays only for new jobs. Both were already how the actor works and both are documented on this page; only the listing changed.
1.0.65 — 2026-09-13
The input form now opens on a first run of 20 jobs, not 100. The form arrives with an example keyword and an example company, and Max unique jobs for the whole run used to sit empty and fall back to 100 — so pressing Start on the example searched for up to 100 unique jobs, about $0.72 on the Apify free plan. The form now opens with that cap set to 20, so the example run returns up to 20 jobs for about $0.14 and finishes in a couple of minutes. This is a starting value in the form only: the cap still falls back to 100 whenever it is left out by an API call, a scheduled task or an integration, so nothing you have already wired up changes. Nothing else moved: no input field, output column, event or price changed.
1.0.64 — 2026-09-13
A hiring board that refuses the request we send no longer tells you to re-run. When a company's board answered with an error about the request we sent rather than about the company — a page size it does not serve, a parameter it will not accept — the row said "The board was unavailable for this run … please re-run", and a re-run came back with the identical answer. That advice cost you a second run for nothing. Those answers now get their own row: it says the board refused the request we sent, that the answer is about our request and not about the company, and that it comes back the same on every re-run — so you know the run is not worth repeating. Nothing was charged for those rows before and nothing is charged now, and a board that is genuinely rate-limited, walled or down is unchanged and still says to re-run.
Nothing else moved: no input field, output column, event or price changed.
1.0.63 — 2026-09-13
The input form's own description now says how to call this actor and what a job costs. An AI agent shopping for actors never opens this page — it reads the input schema, and ours said little more than what the actor does. It now opens with the call that works ({"query": "backend engineer", "countries": ["US"]}), says which field is required and that every other one can be left out, gives the price of one unique job with the arithmetic for a 100-job run, lists what is never charged, and names the run option that caps the bill. The search keyword field now leads with the value shape and says where a single company's own board or a plain Indeed search belongs instead. Nothing else moved: no input field, output column, event or price changed.
1.0.62 — 2026-09-13
A company whose hiring board runs on Workday now returns its openings. Paste a Workday board link — anything of the form company.wd5.myworkdayjobs.com/… — or name a company whose careers page leads to one, and the run used to come back in about a second with no openings at all and the line "the board was unavailable for this run … please re-run". Re-running gave the identical answer, because nothing about the board was unavailable: we were asking it for 200 roles in one go and Workday serves at most 20 per request, so it refused every request we ever sent it. We now ask for the page size each hiring platform actually serves, and a Workday board delivers its openings like every other platform in the list. Nothing was ever charged for those empty runs, and nothing about what you are charged has changed.
Your own "Max roles per company" setting still does exactly what it did. It is a cap on what you get, never a floor: ask for 5 roles from a company and the run still asks that board for 5.
Nothing else moved: no input field, output column, event or price changed.
1.0.61 — 2026-09-13
A company board that stopped answering part-way through no longer reads as the whole board. When a hiring platform blocked or rate-limited us in the middle of reading one company's openings, the run kept the openings it already had — that part was right — and then reported them exactly as if it had read the board to the end: "returned 3 live openings", nothing to re-run, beside a count on the same row saying the board publishes 2,000. There was no way to tell "the site cut us off" from "that is all there is". That company's row now names the hiring platform that stopped answering, says this is not the whole board and how many openings it publishes, and is marked as worth re-running, which it is — nothing was charged for the openings we could not read, and nothing about what you were charged for the ones we did read has changed. A run cut short by your own time limit says that instead, and names the setting to raise.
A search or a company board that answered with nothing now appears on the run page and in the run's own record. Ask about a keyword no board has a match for, or a company whose hiring board is empty, and the run used to finish saying only "Delivered 0 unique jobs" — no count of what answered, and a run record that could not say what had been asked. Each of those now shows as its own uncharged count ("2 listed no jobs", "1 with no board found"), each still has its own row explaining itself, and the run's summary now reconciles: what you asked about equals what was delivered plus what is named. Nothing was charged for these runs before and nothing is charged for them now.
Nothing else moved: no input field, output column, event or price changed.
1.0.60 — 2026-09-12
An aborted or migrated run now leaves its receipt from the first moment of the run, instead of only after start-up finishes. The record that says how a run ended was only put in place once the run had finished starting up — reading your input, opening its stores, working out what a previous attempt had already delivered and charged. A run stopped inside that window ended with nothing written about it at all, which mattered most on a re-started run, where the thing not written about was the earlier attempt's work. It is now in place from the run's first moment. Nothing else moved: no input field, output column, event or price changed, and a run that reaches its work behaves exactly as before.
1.0.59 — 2026-09-12
Asking for more than this actor can do no longer stops the run before it starts. A cap above 10,000 unique jobs, above 5,000 roles from one company board, a run time outside 30–3,600 seconds, a radius above 160 km or "Posted within (days)" above 30 used to be refused by Apify itself: no run, no rows, no explanation — just an error, which is what an API call or an AI agent guessing a round number got. Those ceilings are unchanged and still hard, but they now live in the actor instead of in the form: the run starts, continues at the nearest limit, and writes one extra uncharged row saying what you asked for and what bound it. The radius and the posting window are Indeed's own ceilings, and the note row names them as such. Nothing about a run inside the limits is different, and the note row is never charged.
This actor has a new title on the store: Multi Job Board Scraper — Career Pages, Listings Aggregator. Same actor, same id, same URL, same input, same output, same price — only the words on the listing changed, so nothing you have saved, scheduled or wired into an integration needs touching. The title now carries the phrase buyers actually search for, which is the only reason it moved.
The cost of the try-it run is now stated as a real ceiling, not a guess. Start this actor with nothing filled in and it delivers up to five de-duplicated jobs; this page used to call that "a cent or two at most". On the Apify free plan those five jobs come to about four cents. The page now says at most $0.04 on the Apify free plan and less on paid plans — an upper bound you can hold us to. Nothing about what a run charges changed: one price for the whole feed, still no per-source fee and no per-search fee.
Nothing else moved: no input field, output column, event or price changed.
1.0.58 — 2026-09-11
The one-link agent pin now sits at the top of this page. An AI agent that reads this listing sees only its opening, and the pin — along with the actor id, the single input field a run needs, and the run option that caps what a run can spend — used to sit thousands of characters below it. All four are now in the first few lines, so an agent can pin this actor and start a capped run without reading further. The links to our other job actors on this page also carry the names those actors actually go by on the store. Nothing about the actor, its input or its output changed.
A maintenance change besides that: nothing about your runs changes. The private run-report this actor writes for our own support — counts only, never anything you typed — gained two more facts. It now records the per-event prices the run was charged under, so a question about a bill can be answered from the run's own record rather than from a price table read afterwards; it records the prices that already applied and does not set them. And a run that stopped at your row cap now records whether that cap really filled — one true-or-false fact about the run, never the number you typed, so we can see a run that named your cap as the reason it stopped while it had in fact delivered fewer rows than you allowed.
Nothing else moved: no price, event or output column changed, and only delivered results are charged — no result fee.
1.0.57 — 2026-09-10
A maintenance build: nothing about your runs changes. The private run-report this actor writes for our own support — counts only, never anything you typed — gained room for four figures it does not fill in yet: how many targets a run was given, how long it waited on a blocked source, how many of those waits recovered, and a reason code for an item our own size limit refused. The build carries the shared contract so a later one can report them; on this actor every one of them is left blank, and nothing a run does or costs is different.
Nothing else moved: no price, event or output column changed, and only delivered results are charged — no result fee.
1.0.56 — 2026-09-09
Two runs started at the same time no longer erase each other's memory. The account's repeat memory is now merged on every write, so an item one run delivered stays remembered, and a later re-run hands it back instead of charging it again.
The company-board leg no longer starts a request the run does not have time to finish. Each board page, careers-page look and platform probe now begins only if the run can complete it and still write the result, so a short maxRunSeconds stops the leg immediately and uncharged instead of running over your cap. A company we ran out of time to check is never reported as a company with no hiring board, and a platform we never got to is no longer listed as one we checked.
Nothing else moved: no price, event or output column changed, and only delivered results are charged — no result fee.
1.0.55 — 2026-09-09
A company board that answers "nothing here" in a way we cannot verify is no longer reported as "it listed no matching openings", and is no longer charged for. One board platform answers an empty page identically for a company with no openings and for a company id it has never heard of, so a stale or mistyped board link came back as a confident permanent answer about that company. Its coverage row now says exactly what happened: the board answered, its answer cannot tell those two apart, and nothing was billed.
A board that returns an empty page while its own count says it holds thousands of roles is no longer sold as a definitive answer. It is now an uncharged coverage row saying the board could not be read.
A board that answers "not found" is still a definitive answer, and is still charged. Nothing changed for the platforms that turn away a company they do not know.
A block page that arrives with a "not found" status is no longer called a dead board address. The reason a board gave is now read from its reply, not guessed from the status alone.
A run whose sources all answered and listed nothing no longer ends by asking whether something went wrong. The closing line on the run page asks you to open an issue when a run ends badly, and it was deciding that from the row count alone — so a run where every source was reached, read and honest about holding nothing for your query still pointed you at the Issues tab. It now says so only when nothing that happened explains the empty result.
The same correction applies to the record the run writes back to us, which reads that closing line: a clean run is no longer recorded as a problem run.
Nothing else moved: no price, event or output column changed, and only delivered results are charged — no result fee.
1.0.54 — 2026-09-07
The same role on two boards is now one row and one charge even when the two boards write the company's name differently. A posting listed by "Acme Pty Ltd" on one board and plain "Acme" on another was read as two different employers, so it arrived as two rows and was charged twice. Only English-style company endings were recognised, which meant the ones used across Australia, the Nordics, Italy, Singapore, India and much of Europe — Pty, Oy, AB, S.r.l., S.p.A., Pte, Pvt, SE — were treated as part of the name. Any company ending of that shape is now recognised, in any country, and the two listings become one row with both boards named in sources.
A company board that publishes no company name is now matched by its web address instead of being left unmatched. Some boards return the role without naming the employer. That address was being compared as if it were a name, so the same role on Indeed never lined up with it and you paid for both. It now resolves to the company itself.
Two genuinely different jobs are still two jobs. Nothing merges unless the title, the employer and the place all agree across two different sources, two postings from the same source are never merged, and an ambiguous group is still kept whole and tagged rather than collapsed — a job you should have seen is never dropped to save a row.
Nothing else moved: no price, event or output column changed, and only delivered results are charged — no result fee.
1.0.53 — 2026-09-07
A source name this actor does not recognise no longer runs both sources and charges you for them. "Sources" lets you pick Indeed, company boards, or both. If a value in that list could not be read, it used to be dropped without a word — and if nothing in your list could be read, the empty list was taken to mean "run everything", so a run you had narrowed to one source read both and charged for both. That is fixed. A list this actor cannot read anything in now reads nothing at all: no source is searched, nothing is charged, and one row names the values that do work. A list where only some values could not be read still runs the ones that could, and now names the ones it left out instead of dropping them silently.
The names shown on the form now work when you type them. Capitals and spaces no longer matter, and the option's own label works as well as its value, so "Indeed" and "Company boards (ATS)" are both understood.
Leaving the field empty still means both sources. That is a choice you make, and it is unchanged.
Nothing else moved: no price, event, output column or charge changed, and only delivered results are charged.
1.0.52 — 2026-09-06
A block of links pasted into one row is now read as the list you meant, whatever separates them. "Company boards" takes one company per row. A whole block pasted into a single row was read as ONE company — the first host in it — so a paste of fifty companies read one board and never said the rest had been dropped. A pasted block is now split back into its individual links whether they are separated by spaces, line breaks, tabs, commas, semicolons, pipes or nothing at all, so a column copied straight out of a spreadsheet works. Each one is then read, deduplicated and charged on its own, exactly as if you had pasted them one per row.
A link that carries another link inside it is still one link. An address holding a second address in its query or its path is left whole, not broken in two, and a single link pasted on its own is never rewritten.
Text pasted around a link no longer breaks it. A number, a bullet or a note sitting beside a link is ignored and the link itself is used. A row holding no link at all is still answered as one row, not one row per word.
Nothing else moved: no price, event, output column or charge changed, and only delivered results are charged.
1.0.51 — 2026-09-06
The run's own record now counts the postings your limits left undelivered. The sources are collected first and delivered afterwards, so when your row cap filled, your maximum cost per run was reached, or the run's clock ended the delivery, the postings still in hand were absent from the report this run writes back to us: a run that collected twenty and delivered five closed its books with fifteen postings named by nothing. They are now counted under the limit that stopped each one, so a run that stops short of what it collected is visible to us without you having to report it.
Nothing you can see changed. Your rows, your columns, your receipt and your bill are exactly as they were — only delivered postings are charged, and a posting that was not delivered is not charged.
1.0.50 — 2026-09-06
Jobs already collected when the run's time limit lands are now delivered, never dropped. This actor collects from every source first and delivers afterwards. If the clock ran out during collecting — which is exactly what a wide search on a default time limit does — the delivery step then threw away every job it was holding: a run that had fetched a full set of postings, paid for across company boards and Indeed pages, finished with zero rows and a receipt saying so. Those jobs are now delivered, and charged once each exactly as they always were. Only your own limits still cut the delivery: your row cap is your own cut, your maximum cost per run is never exceeded, and the run keeps back enough time to write the receipt.
What a time limit means has not changed. It still ends the collecting — no board and no Indeed page is fetched after it — and the summary still names maxRunSeconds as the limit that bound and gives you the dataset ID to continue from. Nothing about your charges changed: only delivered jobs are charged, a repeat is still skipped and never charged, and a run whose time limit is generous behaves exactly as it did.
1.0.49 — 2026-09-05
A company board that keeps rate-limiting is now waited out, the same way a blocked Indeed search already was. When a hiring board answered "too many requests", this actor took one more look fifteen seconds later and, if that was refused too, shipped an uncharged "temporary, please re-run" row for that company — often less than a minute into a run whose time limit is twenty. The Indeed half of the same run delivered normally, so you got half an answer and a request to spend the very minutes the run was still holding. A board whose address this run confirmed — one you pasted, or one the company's own careers page links — now gets one pause of a minute and a half to three minutes and one more look on a fresh connection before any such row ships. A board that is simply not there (a dead address, a page that answers "no openings", a host with no website) is answered at once, exactly as before, and a board this run only guessed at never buys the pause.
The uncharged company row now says what was tried. When the run waited, the row states how long it waited and across how many tries before it was still refused — placed before the charge sentence, so "nothing was charged" stays the last word. A company the run never waited for reads exactly as it did.
Waiting is bounded and uncharged. All waiting on the company-board leg shares one ceiling for the whole run, never uses more than the share of remaining time the size of your request allows, always keeps back enough time to finish and report, and stops entirely once the run has no room left to deliver another posting. Nothing about your charges changed: only delivered postings are charged, a refused board is never charged, and a run with a short time limit behaves exactly as it did.
1.0.48 — 2026-09-05
A posting already in your account, and a whole board that would not answer, are now counted against what the run asked for. Every run of this actor keeps its own arithmetic of what was asked and what came back. That arithmetic used the row limit as the ask, so a run whose boards listed 73 postings under a limit of 500 recorded itself as owing hundreds of rows nobody had ever asked for — and a posting skipped because an earlier run of yours had already delivered it was recorded as nothing at all. The run now asks for exactly what its boards listed once duplicates were merged, plus one row for a board that refused or found nothing. Nothing about your rows or your bill changed: the same postings are delivered, the same ones are charged, and a repeat is still skipped and never charged.
A target stopped by a setting this actor cannot read is now counted too. You already got an uncharged row for each such target; the run's own arithmetic counted the setting once instead of the targets it stopped.
The receipt row and the run's own record now state the same ask. The receipt was written a moment before the ask was settled, so on some runs it quoted the row limit while the record quoted something else — one run with two answers to the same question.
1.0.47 — 2026-09-05
A run that waits now always keeps room to finish and report. When Indeed turns every connection away, the run can pause and walk the whole ladder again — but the check that decided whether a pause was affordable kept back only half a minute for that second walk, while a real walk here takes about three and a half minutes. On a run whose time limit fell in the middle band — roughly eight to ten minutes, including a shorter limit you set yourself — a pause could be allowed and the walk after it then ran past the end of the run, so the honest uncharged coverage row it was taken for never landed. The check now keeps back a whole walk, measured from this ladder's own rungs and cooldowns, so a pause is only taken when the run can finish the walk the pause was for. A run with room to wait waits exactly as it did before.
Charges are unchanged. Waiting is uncharged, a walled search is uncharged, and only delivered listings are charged.
1.0.46 — 2026-09-05
A run started with no input at all now has room to wait out a short block instead of stopping. Press Start on the untouched form and this actor runs a small real sample — "software engineer" on Indeed US plus vercel.com's own hiring board, up to 5 jobs, charged like any run. Indeed sometimes turns every connection away for a few minutes at a time, and one full round of attempts could use up the whole of the sample's own three-minute limit — so the pause-and-try-again this actor gained in the last build could never happen on the most common first run of all: it reported "blocked, please re-run" while the block was still lifting. The sample's time limit is now ten and a half minutes, sized to hold one full round of attempts, one pause, one more round, and enough time left over to stop cleanly and report. A sample Indeed answers still finishes in ten to twenty seconds; only a blocked one uses the extra time, and nothing is charged unless jobs are delivered.
How much of its remaining time one blocked search may spend waiting now follows the size of what you asked for. A run with several searches and company boards still to fetch keeps exactly the limit it had — no single search may spend more than a third of the time left, so a wide block still leaves the rest of the run its share. A run with one thing left to fetch — a single keyword in one country with no company boards, or the no-input sample's own Indeed half — may spend what is left on its one pause, because nothing else is waiting for that time. Every other bound is unchanged: at most three rounds per search, never into the time kept back for reporting, and never when the run could not afford another round.
A run with no input is now always a live read of both boards. It no longer checks, and no longer adds to, the list of postings your account already has — so pressing Start twice shows you real results twice instead of handing back the first run's rows, and those few sample rows are charged like any run's each time. Every run you actually configure is unchanged: what your account already has is still skipped before it takes a row slot, still uncharged, and still remembered under every identity a posting carries.
1.0.45 — 2026-09-05
The Indeed leg now varies its connection type and device shape, and reaches a rendered fallback, before it gives up on a search. A search Indeed turned away used to get three quick looks from one kind of connection and then an uncharged "blocked" row. It now takes four looks across two connection types and two device shapes, with a real pause between them, and when every one is turned away it fetches the search page through a rendered fallback before any row ships. A search that answers on the first look costs exactly what it did before. Rows the fallback delivers are charged like any other row; a walled or empty fallback is never charged.
When Indeed turns every connection away and the run still has time, it waits and tries again. Up to three attempts at the whole ladder, a couple of minutes apart, inside a third of the time the run has left — so a run with a long time limit rides out a short wall instead of handing you a "please re-run" with most of its budget unspent. A run with a short time limit (a 60-second limit, the sample) makes one attempt and reports at once. A "not found" answer and an empty results page are answered at once and never waited on.
The uncharged Indeed coverage row now says what was tried. It carries how many connections were made, over how many minutes and tries, whether the run waited (and how often), and how much time was left when it gave its answer — and says "waited" only on runs that did. The same facts ride the summary row and the run's OUTPUT record.
1.0.44 — 2026-09-05
Indeed searches now stop cleanly at the run's time limit. When Indeed rate-limits or challenges a search, this actor pauses and tries again on a fresh connection. Those pauses and retries now watch the run's own clock — the same clock the company-board leg already reads — so a run that reaches its limit ends with the jobs it already has instead of being cut off mid-retry.
A "not found" answer from Indeed is reported as an answer, not as a block. The Indeed coverage row used to count a search Indeed answered "not found" together with blocked or rate-limited ones and tell you to re-run. It now says how many searches were blocked (temporary, uncharged, worth a re-run) and how many were answered "not found" (uncharged, and a re-run will not change them), carries retryable: true only when at least one search was blocked, and names the kind of refusal it actually saw. Two counters, blocked and notFound, are added to that row; the run summary counts a not-found board apart from a blocked one.
1.0.43 — 2026-09-04
Every run now reports what you asked for in the same unit it reports what it delivered. The run's own record counted your ask in searches and company boards while it counted the result in jobs, so a run that returned 73 jobs from one search and one board recorded an ask of 2 — an ask smaller than its own delivery. Your ask is now your row cap ("Max unique jobs for the whole run"), and the summary row states it beside the delivered count so the receipt reconciles against itself. The source list stays on the summary row as its own separate field.
Wording: the 1.0.41 entry below now records what that build actually did.
1.0.42 — 2026-09-04
Fixes a run failure in 1.0.41. Build 1.0.41 stopped early with "Unexpected error" on runs that wrote an uncharged row — a missing internal reference, ours entirely. Jobs already delivered on those runs were charged as their rows say; nothing else was. This build restores the 1.0.41 behaviour intact.
1.0.41 — 2026-09-04 (withdrawn)
This build was withdrawn within minutes and is not usable. The sample path referenced a message it did not load, so any run that wrote an uncharged row stopped early with "Unexpected error". Jobs already delivered on those runs were charged exactly as their rows say; nothing else was. 1.0.42 restores everything below, unchanged.
Narrowing one setting no longer costs you the sample run. Call this actor with no input at all and it runs a real 5-job sample — "software engineer" on Indeed US plus vercel.com's own board, merged — so you see real rows. Change one thing first, though — an Indeed country, a sort order, "Remote jobs only", a row cap — and leave Search keyword and Company boards empty, and you used to get a single row asking for a target instead of any output at all: the caller who set nothing was served better than the caller who narrowed a field. Now your settings are kept and the same sample runs under them, charged like any run. Everything you did not set takes the sample's own value, and one extra uncharged row names which settings were yours and how to run your own search.
A limit you set can only shrink that sample, never grow it. "Max unique jobs for the whole run", "Max jobs per company board" and "Max run seconds" are ceilings: ask for at most 10,000 jobs and you get the sample's own 5; ask for at most 2 and you get 2.
One combination still answers with guidance instead of rows, and says why. With Sources set to company boards only andOnly these hiring platforms set to anything that excludes Greenhouse, there is no board the sample may read at all — the sample company hires on Greenhouse and the Indeed half is switched off. That case keeps its uncharged row, and the row names the field, the reason and the ways out. A platform filter beside a live Indeed leg samples as normal.
Two other things still answer with guidance, unchanged: a field name this actor does not recognise, and a keyword or company list you set and left empty.
A setting the sample could not use is never listed as one it kept. An Indeed country with no country site, or a sort order this actor does not offer, still gets its own uncharged row — and the note row leaves that field out rather than claiming your setting was honoured.
No row on such a run opens by saying nothing was set — it says which half was missing.
1.0.40 — 2026-09-04
"Only these hiring platforms" is now honoured or refused — never quietly widened. A value this actor does not read (bamboo, say) used to be dropped in silence, and the filter then behaved as "check all of them" — so asking for Greenhouse only could bill you for every platform's rows. A filter naming nothing this actor reads is now refused: no board is read, nothing is charged, and each company you sent gets its own uncharged row saying so. The filter also accepts a plain string ("greenhouse"), which is what a templated API call sends.
A refused setting no longer swallows the rest of your ask. Send a keyword with a country this actor has no Indeed site for, or a sort order it does not offer, and every target you sent now comes back with its own uncharged row naming the field that stopped it — instead of one guidance row for the whole run.
A refused country now says which of three things went wrong. A country that really exists but has no Indeed site says so and names the nearest country sites this actor does cover — it is not a spelling mistake, so you are not sent to fix one. A region or whole-world value (Europe, worldwide) says Indeed is searched one country site at a time. A value naming no country at all says there is no honest way to guess, and lists what is accepted.
Every input the run refuses now names the input field it is about — query, countries, sortBy, companies, atsFilter or resumeFromDatasetId — on the row itself and in the run's own record, so a run that delivered nothing can say which field to fix.
The run's record now reports what you asked for. It used to report the number of sources you picked, whatever you actually asked for; it now reports the Indeed searches planned (your keyword across the countries you set) plus the company boards you listed, counted before anything was refused.
1.0.39 — 2026-09-04
A job already delivered to your account is never charged a second time. Every run now remembers what it delivered for your account, in a key-value store called multi-job-watch-account in your own Apify account — under every identity a job carries, one per board that saw it. Re-run the same search and those jobs are skipped before they take a row slot: no row, no charge, no bite out of your row cap, and the status line says how many. Storing every identity is what makes it hold here: a role you first got from Indeed alone is still recognised when a company-board sighting merges with it later, so a merged row can never read as new and bill you again. Delete that store to forget everything; entries older than 90 days count as new again.
New option: "Include jobs you already have" (includeSeen). Off by default. Turn it on and rows your account already has come back in every run's dataset anyway, marked repeat: true with firstSeenAt and firstSeenRunId naming the run that first delivered them — and still not charged.
A sort order written the ordinary way no longer stops the whole Indeed search.Date posted — the console's own name for the option — plus newest, most recent, latest and date_posted used to count as unrecognised values, and an unrecognised sort skipped the entire Indeed leg: one guidance row, and nothing from Indeed for the keyword you typed. They all land on the order they name now. A value that names no order is still refused rather than answered in a different order.
A country written the ordinary way now works in "Countries".USA, United States, uk, United Kingdom, Germany and Canada all resolve to the Indeed country site they name. A country Indeed runs no site for is still skipped by name and never answered with a neighbour's listings.
A memory that cannot be read never stops a run. The run delivers and charges as usual and the status line says the repeat check was unavailable, so you know a repeat may have been charged that once.
A mistyped "Continue from an earlier run" ID no longer creates an empty dataset in your account. The ID is now checked and looked up read-only before anything else happens, so a typo, a pasted link or pasted rows come straight back as one honest uncharged row naming what the value looked like — instead of quietly making a storage under that name and leaving you to find it. A run stopped that way now says so in one short sentence on the run page and keeps the full explanation on its row, where nothing is cut.
1.0.38 — 2026-09-04
An Indeed search now tells you what actually happened, and uses the wait it takes. When Indeed answered with a server error, the run reported it as an access check that never happened; and after waiting out Indeed's cool-down it walked away instead of taking the second look the wait was for. Both are fixed: the answer you get is the one Indeed gave, and a search that can be retried is retried. Nothing is charged for a search that returns no jobs.
1.0.37 — 2026-09-04
A run that stops before it starts now reports its outcome too — a run refused for its memory setting now reaches us the same way every other run does: counts and reason codes only, never your input or your rows.
1.0.36 — 2026-09-04
Every run now reports its own outcome to us — counts and reason codes only, never your input or your rows — so a run that goes wrong reaches us even when nobody shares it.
1.0.35 — 2026-09-04
Run status lines fit the run page again; sample-run wording shortened.
1.0.34 — 2026-09-04
The end-of-run summary now fits on the run page. It was long enough to be cut off part-way through; the wording is tighter and every count, cap and charge statement survives.
1.0.33 — 2026-09-04
A run that filled the row cap you set is no longer treated as a run that went wrong. The Issues-tab line now appears only when something actually did.
1.0.32 — 2026-09-04
A run that ends with a problem now says where to reach us. A miss, an input error, an early stop or a failed run closes by pointing at the Issues tab and naming the reply time; a fully delivered run is left alone.
One support promise across this page — issues are answered in a couple of hours, always within a day.
1.0.31 — the screenshots load from Apify's own storage
The two console screenshots on this page — the input form and the dataset table a real run delivers — are now served from Apify's own storage instead of an outside host, so every picture and every link on this page stays on Apify. Same pictures, same page. What the actor does and what it charges are unchanged.
1.0.30 — the not-affiliated note
This page now states plainly that this is an independent actor: not affiliated with, endorsed by, or sponsored by Indeed or any of the hiring-board providers it reads, and those marks belong to their owners. What the actor does and what it charges are unchanged.
1.0.29 — the suite blocks name the sibling fees that start 16 September 2026
The suite blocks at the bottom of this page now state the three delivery-conditional fees that start 16 September 2026 on sibling actors — keyword volume's fresh lookup, profile posts' profile lookup, and Google Jobs' search fee. This page also now says "nothing charged for starting a run" wherever it used to say "no start fee" — the same promise, in the words the whole shelf uses. Nothing changes on this actor: same price, same events, same rows.
1.0.28 — real screenshots of the input form and the merged feed
Two pictures from the Apify console are now on this page: the input form you fill in, and the dataset table a real run delivers, with the merged rows and their duplicate counts. The search-keyword hint on the form, and one FAQ answer, still described the retired placeholder sample — both now say plainly that a bare Start is a real sample run, charged like any other. The suite links at the bottom were re-checked against the live store listings. What the actor does and what it charges are unchanged.
1.0.27 — run it with no input and get a real sample, not placeholder rows
Running the actor with an empty input ({} from the API, an AI agent, or a saved task that was never filled in) used to return uncharged placeholder rows and no jobs. It now runs a small real feed — "software engineer" on Indeed US plus vercel.com's own hiring board, merged and de-duplicated, up to 5 jobs, inside three minutes — charged like any run, so the first thing you see is actual output. The status line and the summary row say it was the sample and how to run your own search. If a source does not answer at that moment, its coverage row says so, uncharged — never an empty result. Real runs are unchanged.
1.0.26 — run it from an AI agent, and where feedback goes
Two additions to this page, nothing else: a one-line setup for running this actor from an AI agent over MCP (pin it by URL, no store search needed), and a note on where issues and feedback go. What the actor does and what it charges are unchanged.
1.0.25 — 2026-09-02
A continued run now accounts for every delivered row exactly.
Each delivered job row now carries its own charge record, so a run started with "Continue from an earlier run" recognises every row you already have — including rows from an earlier run that was not charged — and never fetches or charges them again. Nothing about what is charged changed.
1.0.24 — continue an earlier run without paying twice
A run that stopped at a cap can now be continued, and nothing you already have is charged
again. Until this build the summary row's advice after a cap was to raise the cap and re-run —
and the re-run, starting from an empty dataset, fetched and charged the whole head of the list a
second time. There is now a "Continue from an earlier run" input (resumeFromDatasetId): give it
the earlier run's dataset ID and every job already in that dataset is skipped — never fetched,
never charged — so a higher cap collects only what is new. The summary row now carries the run's
own datasetId (what you paste next time) and, on a continued run, carriedOver: how many rows
were skipped because you already had them; the coverage rows are unchanged.
The advice is honest again. A capped run's summary now tells you exactly how to collect the
rest without paying for it twice, instead of telling you to re-run.
A dataset that cannot be read stops the run before it fetches anything, uncharged. Continuing
blind could have billed you twice, so the run refuses, says why on an uncharged row, and asks you
to check the ID.
Small caps now deliver a mix of boards. Under a low maxItems, Indeed used to fill the whole
cap before any company board you typed in got a turn. Delivery now rotates across boards, so a cap
of 6 over Indeed plus two companies returns rows from all three; the cap itself is unchanged.
1.0.23 — the skipped-Indeed row now names the real reason
When an unrecognised Sort-by value stops the Indeed leg, the Indeed row says so. In 1.0.22
that row still read "no keyword was given" — which was not true if you had given one. It now
names Sort-by and the two values that work, so the run's own rows point at the field to fix. A
run that genuinely gave no keyword reads exactly as before.
1.0.22 — a Sort-by value we do not offer stops the Indeed leg instead of quietly re-ordering it
An unrecognised Sort-by value is now refused, not run on relevance. Until this build, asking
for a sort order that is not relevance or date ran the Indeed side of your search in relevance
order and said so on an uncharged row. Saying it does not make it right: listings ordered by
relevance look exactly like listings ordered by date, so you could not see that you had been
answered a different question — and you were charged for the answer. That search is no longer run.
You get one uncharged row naming the two values that work, nothing is fetched for it and nothing
is charged.
Your company boards keep running. Sort-by only ever applied to Indeed, so an unrecognised
value stops that leg alone — the boards named in "Company boards" are collected and delivered as
usual in the same run.
Nothing else about Sort-by changes.relevance and date behave exactly as before, and
leaving the field out — or sending null — still means relevance.
1.0.21 — "New York, NY (HQ)" is New York, US
A note in brackets after the state no longer costs you the country. Company boards
commonly write a job's location as "New York, NY (HQ)" or "Springfield, IL (Hybrid)" — the
bracketed word is an office note, not part of the place. Yesterday's build read the whole
tail as one unknown fragment and left location.country empty; it now reads the state and
the country underneath it. Both legs of the merged feed use the same parser, so Indeed rows
and board rows still agree field for field.
1.0.20 — the country on a job is a country, and an optional field can be sent as null
location.country is now a real two-letter country code, or empty — never a state and
never a postcode. This actor merges Indeed rows with company-board rows, so the old
comma-split country poisoned the merged feed from both sides: a job in "Kemmerer, WY 83101"
arrived with a country of "WY 83101", "Los Angeles, CA" arrived as "CA" — Canada's
country code, not California's — and a board writing "Berlin, Germany" arrived as
"Germany" rather than "DE". Every leg now ships the same parse: the state in
location.region, the postcode in the new location.postcode, and a real country code or
nothing at all in location.country. De-duplication is unchanged — it reads the untouched
line in location.raw, which is still exactly what the board printed.
Optional fields now accept null. Templated callers — n8n, agent frameworks, anything
that fills a request from a form — send null for every field left unset, and the platform
used to refuse those runs before they started. Every optional field now accepts null and
reads it as "use the default"; the paste-block note that told you to avoid nulls is gone,
because it is no longer true. query is still required.
The Sort-by dropdown is checked by the actor now, not by the platform — and a value it
does not recognise is reported rather than swapped. A fixed list of choices is checked
separately by the platform, and that check refuses null however the field is declared, so
one dropdown was enough to fail a fully-templated request before it started. Sort-by keeps
the same two choices; a value that is neither runs on relevance, as before, and now also
returns one uncharged row naming what was not recognised and what the run used.
1.0.19 — 2026-08-31
Company boards are read several at a time, a throttled board gets a second look, and the
sample rows say they are samples.
Company hiring boards are now read several at a time instead of strictly one after
another. A run naming twenty companies used to wait out each board in turn; the same
run now finishes markedly sooner for exactly the same money. Nothing about the results
moves: rows come back in the order you listed the companies whichever board answers
first, de-duplication is unchanged, and maxItems still binds exactly.
A hiring board that rate-limits or drops the request is now given one short pause and
one more look before the run calls it unreachable. That verdict used to go into your
dataset the first time a board said "too many requests", and the re-run it asked for was
yours to start. Boards that answer normally are untouched, and a 404 or a dead address
still gets its immediate permanent answer with no delay at all.
The careers-page lookup we fall back on when a company's own site blocks us now
retries a dropped connection once. A single network drop used to end that lookup, so
the company came back "please re-run" even though the page was readable a second later.
What that lookup costs you is unchanged: it has never been billed to you.
Both new retries stand down when your run is nearly out of time, so a run close to
its limit spends its last seconds delivering rows and writing the summary, never waiting.
The sample rows a run with no input returns now say they are samples, instead of
looking exactly like delivered rows on the Output tab.
The input page and the README now explain the note about null. They said only "do
not send null"; they now say why — your input is checked against the input page before
the run starts, so an empty field has to be left out rather than sent as null.
1.0.18 — 2026-08-30
Clearer release notes — this page's notes now read more plainly. Nothing about what this actor delivers or charges changed.
1.0.17 — 2026-08-30
Suite links now point at the full live shelf — every actor named in this README is a live
store link. Nothing about what this actor delivers or charges changed.
1.0.16 — 2026-08-30
A moved or restarted run never repeats a row, and several interruption edges are closed.
A moved or restarted run can no longer repeat its sample rows, input notices, coverage
rows or summary. Those rows used to be re-appended by the part of a run that continued
on a new server, so a moved run's dataset could carry doubles of rows that are not job
listings. Every such row is now written exactly once per run, and a continued run adds at
most one summary of its own — never a twin of the standing one.
A run paused for a server move now always continues on the new server. The pause
itself could previously end the run as finished — directly under a status message
promising it would continue. The promise is now kept: the run picks up where it left off,
and nothing is delivered or charged twice.
A run revived long after an interruption now gets a bounded window to finish instead
of stopping on its first step and pointing at your maxRunSeconds — a limit that had
simply elapsed on the clock while the run sat interrupted, not one the run ever used.
Finishing an interrupted run's billing can never risk a double charge. If settling an
earlier attempt's delivered-but-unbilled rows is itself interrupted, the run now stops at
the exact point of failure and records only what actually went through — a charge that may
already have landed is never sent again, and the rest is finished by the next resume.
Every kind of account-side refusal from our careers-page lookup provider now stops the
lookups straight away. 1.0.14 stopped asking when our key was refused; one further
refusal shape could still be retried per company. All such refusals now stand down
immediately — that lookup was never billed to you either way.
A numeric setting sent as null or an empty value now means "use the default" instead
of being read as the smallest allowed value, and every part of the run now reads the one
documented set of defaults and limits for the run-length and item caps.
1.0.15 — 2026-08-29
The careers-page lookup's spending allowance now counts correctly.
The per-run allowance on our careers-page lookup is now measured exactly. Some lookups could previously be under-counted, so a lookup-heavy run could spend past the allowance before its clean stop engaged. That allowance is ours and was never billed to you — nothing changes about what a run delivers or charges.
1.0.14 — 2026-08-29
Two Indeed settings that already worked are now listed properly, and a dropped connection no longer ends a search.
"Radius" and "Sort by" are now proper input options. Both already worked when sent through the API, but they were missing from the input list — so a run that set one could be told the field was not recognised while the run was quietly using it. They are now on the form, documented, and never reported as unknown.
A dropped or refused connection no longer ends an Indeed search with "not found". Indeed had answered nothing. The search now retries on a fresh connection, and if it still cannot get through you get an uncharged row saying the problem was temporary and asking you to re-run. When Indeed itself answers "not found" for a page, that is still reported as a real answer.
If our careers-page lookup provider ever refuses our key, the run stops asking straight away rather than retrying it for every company. That lookup was never billed to you.
1.0.13 — 2026-08-29
Companies whose careers pages block automated visitors now get one more chance through an alternate route.
A careers page that blocks every direct read is no longer a dead end. Some company sites answer every automated visitor with a block page, so those companies always came back "temporarily unreachable — please re-run", even though re-running hit the same wall. The run now checks such a page once through an alternate route: if the page links a hiring board, the company resolves and its openings are delivered as normal; if it genuinely links none, the row can finally say so definitively instead of suggesting another try.
A block page on the alternate route is never mistaken for an answer. If the alternate route is walled off too, the row stays "temporarily unreachable" — exactly as before — rather than turning a blocked read into a false "no board found". The alternate route always reads the live page, never a stale cached copy, so a site that has stopped blocking is seen as it is now.
Charging is unchanged: jobs are charged exactly as before, only after they are delivered, and nothing about this check is ever billed to you.
1.0.12 — 2026-08-29
A typo in an input field name now gets a helpful pointer instead of sample rows.
A run that sets a field this actor does not have is answered, not demoed. Sending something like queries instead of query, or companys instead of companies, used to look exactly like an empty run, so it came back with the uncharged sample rows — and nothing said the field had been ignored. Such a run now returns one uncharged row that names the field it did not recognise, names both ways to tell this actor what to look for (query for a job title, companies for company boards) and shows the shape to send. Changing a setting such as the country without giving either is answered the same way. A run that really is empty still returns the sample rows, and a run with a real keyword or real companies is unchanged.
1.0.11 — 2026-08-29
Two answers that were the wrong way round: one asked for a re-run that could never help, the other stopped you from re-running when it would have.
Companies with no public hiring board now get a final answer that names any board which could not be checked, instead of "temporarily unreachable, please re-run" — a re-run that could never have changed the result.
A careers page that answers with a bot check or a security screen no longer counts as a look at that company; those are reported as temporary and uncharged.
A board link that no longer exists now says so and names the address, instead of reporting that the board answered with no openings. A board that really is empty reads exactly as before.
1.0.10 — 2026-08-28
Special characters in job text (like & or < written as HTML codes) are now decoded exactly once — text that spells out an HTML code literally can no longer turn into the character it names.
1.0.8 — French and Swiss salaries are read the way those pages write them
A French salary is no longer inflated 100×. France writes decimals with a comma, so
11,88 € an hour was being read as 1188. France now joins the countries whose comma is a
decimal point, and the number on the row is the number on the page.
Switzerland's apostrophe no longer splits a salary in two.CHF 50'000 (either
apostrophe character) is one number, not 50 and 000.
par heure and par an are recognised, so French listings get hour and year in
salaryPeriod instead of nothing.
A run that stopped at your row cap or deadline no longer tells you to re-run with
resumeCursor. It is a field on the summary row, not something you can pass in, so the
summary now says what actually works: raise the caps and run again.
1.0.7 — see a real run's output before you spend anything
The page now links a real run's full dataset: eight live jobs pulled from four different company
boards in one run, the coverage row for each board, and the summary. Every row that was charged
is marked charged: true; nothing else is, so you can check the promise before you buy.
Links to the sibling actors that went live today (Trends Now, Social trends, YouTube videos,
YouTube channels, Speech to Text) instead of naming them.
1.0.6 — your maxRunSeconds is also measured from when the run started
1.0.5 stopped the actor's own one-hour ceiling from restarting when Apify moves a run to
another server. The maxRunSeconds you set was still being measured from the moment the new
server started, so a moved run could keep working for a second full window. Both limits are
now measured from when the RUN started, which is what you asked for when you set the number.
1.0.5 — a run moved to another server never delivers or charges the same job twice
Apify sometimes moves a running job to a different server, and a resurrected run restarts the
same way. Until now that wiped the run's memory: jobs already delivered could be collected
again, written into your dataset a second time, and charged a second time. The run now reads
back what it has already delivered before it does any new work, so a moved or resurrected run
picks up exactly where it stopped — same rows, same charges, nothing repeated.
If a move landed in the gap between a row being delivered and being charged, that one missing
charge is now settled once when the run comes back, instead of the row sitting in your dataset
marked as charged when it never was.
maxItems is now counted across the whole run rather than per server. A moved run could
previously deliver up to twice the row cap you set.
The run's own time limit is now measured from when the RUN started, not from when the current
server started, so a moved run no longer gets handed a fresh full hour.
The same company board reached two ways in one run — by domain and by pasting its board URL —
now collapses to one row and one charge. It used to be two of each.
If a run cannot read back what it already delivered AND it has already charged for some of it,
it now stops and tells you, rather than risk charging you twice for the same job.
1.0.4 — review-fix build: two different Workday jobs no longer collapse into one row
Workday jobs are now identified by the job's own URL slug (which carries the requisition id)
instead of the board card's first bullet. That bullet is a per-tenant display setting: on a
board that shows something like employment type there, every job carried the same value, so
distinct roles shared a dedupe key and got merged into a single row — you lost real jobs from
the feed, and were charged once for what should have been several separate jobs.