Choose a strategy
Which waterfall strategy should my request use, and when should I pin providers?
waterfall.strategy decides which sources a request asks and when it stops. The default, cheapest_first, is right for most searches. This page tells you when it is not. What each strategy does is defined on The waterfall.
Decide in five questions
Section titled “Decide in five questions”Stop at the first “yes”.
-
Are you on Free or Starter? Use
own_only. These plans route to the BetterJobs index only, socheapest_first,freshest_first,max_coverageandown_onlyask the same single source, andconsensuscannot run (it needs at least 2 sources).own_onlysays so explicitly. -
Do you need every matching job? Backfills, daily sync, market sizing, territory counts. Use
max_coverage. It asks every provider your plan enables. A missed job costs you more than a credit. -
Will a person or a sequence act on each job? Outbound, recruiter alerts, CRM tasks. Use
consensuswithmin_sources: 2. You get fewer jobs, each seen by at least two independent sources. -
Do the last few hours matter more than completeness? Alerting on fresh postings, “posted today” feeds. Use
freshest_first. It asks the fastest-refreshing sources first. -
None of the above? Keep
cheapest_first. It starts with the BetterJobs index and adds providers only until the page is full.
By job to be done
Section titled “By job to be done”| You want to | Strategy | Settings | Why |
|---|---|---|---|
| Look up who is hiring for a role this week | cheapest_first |
max_credits |
Fills the page at the lowest cost. See Find companies hiring. |
| Build or refresh a local copy | max_coverage |
max_credits, async for more than 100 jobs |
Same sources every run, so gaps do not appear between runs. See Sync patterns. |
| Size a market or a territory | max_coverage |
dry_run: true first |
The free estimate gives expected_unique_jobs_range before you spend. |
| Trigger outbound on a job | consensus |
min_sources: 2, filter on p_real |
Corroborated jobs only. Fewer wasted touches on stale or ghost postings. |
| Alert on brand-new postings | freshest_first |
short posted_within_days, timeout_ms |
Fast sources answer first. Pair with a watch for push delivery. |
| Show jobs to your own end users | own_only or pinned providers |
check license.display |
Simplest licensing. See Licensing. |
| Keep using a provider you already trust | any | providers: [...] |
Pin the sources. See Pin providers. |
Compare strategies for free
Section titled “Compare strategies for free”Run the same filters with dry_run: true once per strategy. Nothing is fetched or charged. Compare providers_planned and credits_range.
for strategy in cheapest_first max_coverage consensus; do curl -s https://api.betterjobs.cc/v1/jobs/search \ -H "Authorization: Bearer $BETTERJOBS_API_KEY" \ -H "BetterJobs-Version: 2026-10-01" \ -H "Content-Type: application/json" \ -d '{ "filters": { "title_or": ["Head of RevOps"], "country_code_or": ["DE", "AT", "CH"], "posted_within_days": 7 }, "waterfall": { "strategy": "'"$strategy"'" }, "limit": 100, "dry_run": true }' echodoneimport os
import requests
API = "https://api.betterjobs.cc/v1"HEADERS = { "Authorization": f"Bearer {os.environ['BETTERJOBS_API_KEY']}", "BetterJobs-Version": "2026-10-01",}FILTERS = {"title_or": ["Head of RevOps"], "country_code_or": ["DE", "AT", "CH"], "posted_within_days": 7}
for strategy in ["cheapest_first", "max_coverage", "consensus"]: resp = requests.post( f"{API}/jobs/search", headers=HEADERS, json={"filters": FILTERS, "waterfall": {"strategy": strategy}, "limit": 100, "dry_run": True}, timeout=30, ) resp.raise_for_status() est = resp.json()["estimate"] print(strategy, est["providers_planned"], est["credits_range"])const API = 'https://api.betterjobs.cc/v1';const headers = { Authorization: `Bearer ${process.env.BETTERJOBS_API_KEY}`, 'BetterJobs-Version': '2026-10-01', 'Content-Type': 'application/json',};const filters = { title_or: ['Head of RevOps'], country_code_or: ['DE', 'AT', 'CH'], posted_within_days: 7 };
for (const strategy of ['cheapest_first', 'max_coverage', 'consensus']) { const res = await fetch(`${API}/jobs/search`, { method: 'POST', headers, body: JSON.stringify({ filters, waterfall: { strategy }, limit: 100, dry_run: true }), }); if (!res.ok) throw new Error(`${res.status} ${await res.text()}`); const { estimate } = await res.json(); console.log(strategy, estimate.providers_planned, estimate.credits_range);}One estimate (illustrative):
{ "request_id": "req_3Kd8PwZ1uY", "estimate": { "expected_unique_jobs_range": { "min": 40, "max": 75 }, "providers_planned": ["betterjobs", "reqbeat", "signalsapi", "theirstack", "jobspipe", "coresignal", "techmap"], "credits_range": { "min": 40, "max": 75 } }}After a real request, metadata.providers.contributions shows how many unique jobs each source added first, and metadata.field_coverage shows how complete each field was. Those two numbers, on your own queries, are the best guide to which strategy pays off.
Pin providers
Section titled “Pin providers”waterfall.providers overrides the strategy’s choice of sources. Use it to keep a provider you already know, or to leave one out.
{ "filters": { "title_or": ["Head of RevOps"], "country_code_or": ["DE", "AT", "CH"] }, "waterfall": { "strategy": "max_coverage", "providers": ["betterjobs", "theirstack", "techmap"] }}Every slug must be enabled on your plan. GET /v1/providers shows enabled_on_your_plan per source, and GET /v1/account lists providers_enabled. Asking for a source outside your plan returns 403 plan_required with required_plan.
Build one
Section titled “Build one”Pick a strategy and copy the request.
Budget knobs
Section titled “Budget knobs”These work with every strategy.
| Field | What it does | Set it when |
|---|---|---|
waterfall.max_credits |
Hard cap on credits for this request. Results stop at the cap. | Always, in production. |
waterfall.timeout_ms |
Time budget, 1000 to 30000, default 10000. Slow providers are dropped and the result is partial. |
A user is waiting on the response. |
waterfall.min_sources |
Sources that must agree for consensus, 2 to 7, default 2. |
Raising it trades volume for certainty. |
dry_run |
Free estimate, nothing fetched. | Before any large or new query. |
Pitfalls
Section titled “Pitfalls”cheapest_firstis not for sync. It stops when the page is full, so the set of sources can change between runs. Usemax_coveragefor copies you keep.freshest_firstchanges order, not data. It asks fast-refreshing sources first. Judge freshness on each job withfirst_seen_at,last_seen_atandlast_verified_at. See Freshness and lifecycle.- Strategies cost the same per job. Every source costs 1 credit per unique job. A strategy changes how many jobs you get, not the price of one.
- Partial is not failure. Any strategy can return
metadata.status: partialwhen a provider times out. You pay only for returned jobs.
- The waterfall: how fan-out, merge and billing work.
- Provenance and confidence:
p_realandsources[]. - Providers: what each source sells.