Skip to content
API v1 preview — endpoints and fields may change before general availability.

Find companies hiring

How do I find companies hiring for a role in a region?

View .md
Cost: 1 credit / unique jobDuplicates, already-paid jobs, empty pages and dry runs are free.

Goal: a list of companies hiring a Head of RevOps in Germany, Austria or Switzerland in the last 7 days, with the matching jobs under each company.

You search jobs, then group them by company.domain. One request covers every provider on your plan.

  1. Estimate for free. Send the search with dry_run: true. You get expected_unique_jobs_range and credits_range. Nothing is fetched or charged.

  2. Search with a cap. Send the same search without dry_run. Set waterfall.max_credits so one page cannot charge more than you expect, and stop paging once the summed metadata.credits_charged reaches your run budget.

  3. Page through results. Pass next_cursor back as cursor until it is null. Each page holds up to 100 jobs.

  4. Group by company. Key each job on company.domain. Fall back to company.id when the domain is null.

  5. Optional: check each company. Call GET /v1/companies/{domain} for is_hiring and hiring_pulse. That is 1 credit per profile.

Terminal window
curl 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", "Head of Revenue Operations"],
"country_code_or": ["DE", "AT", "CH"],
"posted_within_days": 7
},
"waterfall": { "strategy": "cheapest_first" },
"limit": 100,
"dry_run": true
}'

The estimate looks like this (illustrative):

{
"request_id": "req_3Kd8PwZ1uY",
"estimate": {
"expected_unique_jobs_range": { "min": 40, "max": 75 },
"providers_planned": ["betterjobs", "theirstack", "techmap"],
"credits_range": { "min": 40, "max": 75 }
}
}
Terminal window
# First page. Repeat with "cursor": "<next_cursor>" until next_cursor is null.
curl https://api.betterjobs.cc/v1/jobs/search \
-H "Authorization: Bearer $BETTERJOBS_API_KEY" \
-H "BetterJobs-Version: 2026-10-01" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7b8f2c4e-1a3d-4f5b-9c6e-0d2a4b6c8e1f" \
-d '{
"filters": {
"title_or": ["Head of RevOps", "Head of Revenue Operations"],
"country_code_or": ["DE", "AT", "CH"],
"posted_within_days": 7
},
"waterfall": { "strategy": "cheapest_first", "max_credits": 100 },
"limit": 100
}'

Group the saved pages with jq:

Terminal window
jq -s '[.[].data[]] | group_by(.company.domain // .company.id)
| map({company: .[0].company, jobs: map({id, title, location})})' page-*.json

One canonical job from the search (illustrative, trimmed). Three sources saw it; you pay for it once.

{
"data": [
{
"id": "job_01JC8X4M2Q7RV3T9KD5W6YH0AB",
"title": "Head of Revenue Operations",
"company": { "id": "cmp_4Rk7TzP1aQ", "name": "Acme Robotics", "domain": "acme-robotics.example" },
"location": { "city": "Berlin", "region": "Berlin", "country_code": "DE", "remote": false },
"seniority": "lead",
"salary": { "min": 110000, "max": 135000, "currency": "EUR", "period": "year", "origin": "declared" },
"status": "open",
"p_real": 0.94,
"sources": [
{ "provider": "betterjobs", "provider_job_id": "bj_idx_5521907", "fields": ["title", "description", "apply_url", "location", "employment_type", "posted_at"] },
{ "provider": "theirstack", "provider_job_id": "ts_88213377", "fields": ["salary", "seniority"] },
{ "provider": "techmap", "provider_job_id": "tm_3f9a2c71", "fields": ["job_family"] }
]
}
],
"next_cursor": "cur_8fJ2kQ",
"metadata": {
"status": "complete",
"credits_charged": 1,
"jobs_already_paid": 0,
"duplicates_merged": 2,
"providers": { "tried": ["betterjobs", "techmap", "theirstack"], "hit": ["betterjobs", "techmap", "theirstack"], "failed": [] }
}
}

After grouping (illustrative):

[
{
"company": { "id": "cmp_4Rk7TzP1aQ", "name": "Acme Robotics", "domain": "acme-robotics.example" },
"jobs": [{ "id": "job_01JC8X4M2Q7RV3T9KD5W6YH0AB", "title": "Head of Revenue Operations", "p_real": 0.94 }]
}
]

A search tells you who posted matching jobs. A company profile tells you whether the company is hiring overall and in which direction.

Terminal window
curl https://api.betterjobs.cc/v1/companies/acme-robotics.example \
-H "Authorization: Bearer $BETTERJOBS_API_KEY" \
-H "BetterJobs-Version: 2026-10-01"
{
"company": { "id": "cmp_4Rk7TzP1aQ", "name": "Acme Robotics", "domain": "acme-robotics.example" },
"open_jobs_count": 42,
"is_hiring": { "value": true, "confidence": 0.96, "basis": "42 open jobs corroborated by 4 sources in the last 30 days" },
"hiring_pulse": { "direction": "up", "open_jobs_30d_change": 9 }
}
Call Cost
POST /v1/jobs/search with dry_run: true Free
POST /v1/jobs/search 1 credit per unique job returned. Duplicates, jobs you already paid for and empty pages are free.
GET /v1/companies/{domain} 1 credit per profile

A company with 30 matching jobs costs 30 credits through search. If you already have an account list and only need yes or no, GET /v1/companies/{domain} at 1 credit each is cheaper. See Credits and billing.

  • posted_within_days counts from first seen. It matches jobs first seen within that many days. A job posted earlier but discovered late still matches. Check posted_at if you need the employer’s date.
  • Partial pages are normal. A provider timeout gives 200 with metadata.status: partial. You pay only for returned jobs. Re-run later to fill gaps; jobs you already paid for come back free.
  • max_credits stops results. When the cap is hit, the page stops early. Raise the cap or narrow the filters.
  • Before outbound, prefer corroborated jobs. Use p_real, or the consensus strategy. See Choose a strategy.
  • Unknown filter fields fail. A typo returns 400 unknown_filter with the field in param. See Filters.