Create an async search
Create an async search. Starts a large search (up to 10,000 jobs) and returns `202` with a search `id`.
Cost: 1 credit / unique jobCharged as jobs are collected. When credits run out the search moves to on_hold and resumes after a top-up.
const url = 'https://api.betterjobs.cc/v1/searches';const options = { method: 'POST', headers: { 'BetterJobs-Version': '2026-10-01', 'Idempotency-Key': '7b8f2c4e-1a3d-4f5b-9c6e-0d2a4b6c8e1f', Authorization: 'Bearer <token>', 'Content-Type': 'application/json' }, body: '{"filters":{"title_or":["Data Engineer","Analytics Engineer"],"country_code_or":["DE","FR","NL","ES","PL"],"posted_within_days":30},"waterfall":{"strategy":"max_coverage","max_credits":5000},"limit":5000,"webhook_url":"https://hooks.northwind.example/betterjobs"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.betterjobs.cc/v1/searches \ --header 'Authorization: Bearer <token>' \ --header 'BetterJobs-Version: 2026-10-01' \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: 7b8f2c4e-1a3d-4f5b-9c6e-0d2a4b6c8e1f' \ --data '{ "filters": { "title_or": [ "Data Engineer", "Analytics Engineer" ], "country_code_or": [ "DE", "FR", "NL", "ES", "PL" ], "posted_within_days": 30 }, "waterfall": { "strategy": "max_coverage", "max_credits": 5000 }, "limit": 5000, "webhook_url": "https://hooks.northwind.example/betterjobs" }'Starts a large search (up to 10,000 jobs) and returns 202 with a search id.
Poll GET /searches/{id} or pass webhook_url to receive search.completed.
Branch on status, not on the HTTP code. A 200 from GET /searches/{id} can carry
queued, running, completed, partial, failed or on_hold.
Authorizations
Section titled “Authorizations”Parameters
Section titled “ Parameters ”Header Parameters
Section titled “Header Parameters”Example
2026-10-01Date-pinned API version. Changes within a version are additive only. Defaults to your account’s pinned version.
Example
7b8f2c4e-1a3d-4f5b-9c6e-0d2a4b6c8e1fUnique key (for example a UUID). Retrying with the same key and body returns the first response and never charges twice. Same key with a different body returns 409 idempotency_conflict.
Request Bodyrequired
Section titled “Request Bodyrequired”Has no dry_run. Size an async search first with POST /jobs/search and dry_run: true
(its expected_unique_jobs_range covers every page), and bound the spend with waterfall.max_credits.
Unknown properties return 400 invalid_request.
object
Filter grammar with suffix operators. Unknown fields return 400 unknown_filter.
object
Match any of these title keywords.
Exclude titles containing any of these.
ISO 3166-1 alpha-2 codes.
Jobs first seen within this many days.
true remote only, false non-remote only, null or omitted = any.
Keep jobs whose salary.min is at least this value (yearly, in the job’s currency). Jobs with salary null are excluded when set.
Include jobs with status: closed.
How BetterJobs routes the request across providers.
object
cheapest_first stops once enough unique jobs are found, starting with the BetterJobs index.
freshest_first tries the sources with the fastest refresh first.
max_coverage queries every provider on your plan.
consensus returns only jobs seen by at least min_sources sources.
own_only uses the BetterJobs index only.
Override the providers to query. Must be enabled on your plan.
Hard cap on credits this request may charge. Results stop at the cap.
Per-request time budget. Slow providers are dropped and the result is partial.
Minimum agreeing sources for consensus.
Receives search.completed when the search finishes.
Examples
30-day backfill of data engineering roles in Europe
{ "filters": { "title_or": [ "Data Engineer", "Analytics Engineer" ], "country_code_or": [ "DE", "FR", "NL", "ES", "PL" ], "posted_within_days": 30 }, "waterfall": { "strategy": "max_coverage", "max_credits": 5000 }, "limit": 5000, "webhook_url": "https://hooks.northwind.example/betterjobs"}Responses
Section titled “ Responses ”Search accepted.
Async search. Branch on status, not on the HTTP code.
object
on_hold = out of credits; resumes after a top-up. partial = finished but at least one provider failed.
Unique jobs collected so far.
A page of results once completed or partial. Empty otherwise.
A canonical job: one real opening, merged from every source that saw it.
Null semantics: null = unknown; [] = verified none; field omitted = not part of this payload
(for example the lifecycle-only job in job.closed and job.reposted events).
object
Canonical job id. Stable across sources and requests.
Company reference embedded in jobs, events and profiles.
object
Primary web domain. null when unknown.
object
ISO 3166-1 alpha-2.
true remote, false on-site or hybrid, null unknown.
Plain-text job description.
Date the employer posted the job, if known.
Earliest time any source saw the job.
Latest time any source saw the job.
Latest time the job was confirmed live at its origin.
Why the job closed. null while open.
Times the same job was re-listed.
Probability (0-1) that the job is a real open req, from cross-source corroboration.
One provider’s view of the canonical job.
object
Source slug. betterjobs is the BetterJobs index.
Job fields this source contributed to the canonical record.
object
You may show this job to your end users.
You may resell or redistribute this job as data.
object
partial = at least one provider failed or timed out. You are billed only for returned jobs.
Returned jobs you had already paid for (free).
Provider records merged into existing canonical jobs (free).
object
Providers that returned at least one match.
object
Source slug. betterjobs is the BetterJobs index.
provider_timeout = the provider missed timeout_ms. provider_error = the provider returned an error.
Unique jobs each provider contributed first.
object
Fraction (0-1) of returned jobs with a non-null value, per field.
object
Examples
{ "id": "srch_2Vd9KqL4mN", "status": "queued", "created_at": "2026-10-11T09:00:00Z", "updated_at": "2026-10-11T09:00:00Z", "completed_at": null, "webhook_url": "https://hooks.northwind.example/betterjobs", "jobs_found": 0, "data": [], "next_cursor": null, "metadata": null}Headers
Section titled “Headers”Example
req_7Hc2LmQ9xTUnique id for this request. Quote it to support.
Example
60Requests allowed in the current window.
Example
59Requests left in the current window.
Example
42Seconds until the window resets.
Invalid request (invalid_request) or unknown filter field (unknown_filter).
object
object
Present on insufficient_credits.
Present on insufficient_credits and plan_required.
Offending field on invalid_request and unknown_filter.
Examples
unknown_filter
{ "error": { "type": "invalid_request_error", "code": "unknown_filter", "message": "Unknown filter field 'job_title_or'. Did you mean 'title_or'?", "param": "filters.job_title_or", "doc_url": "https://docs.betterjobs.cc/platform/errors/#unknown_filter", "request_id": "req_4Bn8CxV2zA" }}invalid_request
{ "error": { "type": "invalid_request_error", "code": "invalid_request", "message": "limit must be between 1 and 100.", "param": "limit", "doc_url": "https://docs.betterjobs.cc/platform/errors/#invalid_request", "request_id": "req_2Cm7DwB1yZ" }}Headers
Section titled “Headers”Example
req_7Hc2LmQ9xTUnique id for this request. Quote it to support.
Missing or invalid API key.
object
object
Present on insufficient_credits.
Present on insufficient_credits and plan_required.
Offending field on invalid_request and unknown_filter.
Examples
{ "error": { "type": "authentication_error", "code": "unauthorized", "message": "Missing or invalid API key. Send 'Authorization Bearer bj_live_...'.", "doc_url": "https://docs.betterjobs.cc/platform/errors/#unauthorized", "request_id": "req_5Dl6EvN0xY" }}Headers
Section titled “Headers”Example
req_7Hc2LmQ9xTUnique id for this request. Quote it to support.
Not enough credits for this request.
object
object
Present on insufficient_credits.
Present on insufficient_credits and plan_required.
Offending field on invalid_request and unknown_filter.
Examples
{ "error": { "type": "billing_error", "code": "insufficient_credits", "message": "This request needs at least 25 credits; 3 remain.", "credits_needed": 25, "upgrade_url": "https://betterjobs.cc/pricing", "doc_url": "https://docs.betterjobs.cc/platform/errors/#insufficient_credits", "request_id": "req_6Ek5FuM9wX" }}Headers
Section titled “Headers”Example
req_7Hc2LmQ9xTUnique id for this request. Quote it to support.
Your plan does not include this feature or provider.
object
object
Present on insufficient_credits.
Present on insufficient_credits and plan_required.
Offending field on invalid_request and unknown_filter.
Examples
{ "error": { "type": "permission_error", "code": "plan_required", "message": "Provider 'coresignal' is not enabled on your plan. Pro and above include all six providers.", "required_plan": "pro", "upgrade_url": "https://betterjobs.cc/pricing", "doc_url": "https://docs.betterjobs.cc/platform/errors/#plan_required", "request_id": "req_7Fj4GtL8vW" }}Headers
Section titled “Headers”Example
req_7Hc2LmQ9xTUnique id for this request. Quote it to support.
Same Idempotency-Key reused with a different body.
object
object
Present on insufficient_credits.
Present on insufficient_credits and plan_required.
Offending field on invalid_request and unknown_filter.
Examples
{ "error": { "type": "conflict_error", "code": "idempotency_conflict", "message": "Idempotency-Key '7b8f2c4e-1a3d-4f5b-9c6e-0d2a4b6c8e1f' was used with a different request body.", "doc_url": "https://docs.betterjobs.cc/platform/errors/#idempotency_conflict", "request_id": "req_9Hh2IrJ6tU" }}Headers
Section titled “Headers”Example
req_7Hc2LmQ9xTUnique id for this request. Quote it to support.
Too many requests. Wait Retry-After seconds.
object
object
Present on insufficient_credits.
Present on insufficient_credits and plan_required.
Offending field on invalid_request and unknown_filter.
Examples
{ "error": { "type": "rate_limit_error", "code": "rate_limited", "message": "Rate limit exceeded. Retry after 12 seconds.", "doc_url": "https://docs.betterjobs.cc/platform/errors/#rate_limited", "request_id": "req_0Ig1JqH5sT" }}Headers
Section titled “Headers”Example
req_7Hc2LmQ9xTUnique id for this request. Quote it to support.
Example
12Seconds to wait before retrying.
Example
60Requests allowed in the current window.
Example
59Requests left in the current window.
Example
42Seconds until the window resets.
Something failed on our side. Safe to retry with the same Idempotency-Key.
object
object
Present on insufficient_credits.
Present on insufficient_credits and plan_required.
Offending field on invalid_request and unknown_filter.
Examples
{ "error": { "type": "api_error", "code": "internal_error", "message": "Unexpected error. Retry with the same Idempotency-Key; you will not be charged twice.", "doc_url": "https://docs.betterjobs.cc/platform/errors/#internal_error", "request_id": "req_1Jf0KpG4rS" }}Headers
Section titled “Headers”Example
req_7Hc2LmQ9xTUnique id for this request. Quote it to support.