Overview
Section titled “Overview”A company hiring profile answers one question: is this company hiring right now, and in which direction?
You look a company up by its domain. The profile rolls up every open canonical job BetterJobs knows for that company, across all sources:
open_jobs_count: open canonical jobs right now.is_hiring: a three-state answer (true,false,null) with aconfidenceand a plain-Englishbasis.hiring_pulse: the 30-day trend (up,flat,down) and the change in open jobs.top_job_families: where the hiring is.sources[]: how many open jobs each provider sees, and when it last saw the company.
The same company object (id, name, domain) is embedded in every job and event, so you can join profiles to jobs on company.id.
How is_hiring is decided
Section titled “How is_hiring is decided”is_hiring is derived from the company’s canonical jobs and how many sources corroborate them. The decision is never a bare boolean. Every answer comes with:
value:true,falseornull.confidence: 0 to 1.basis: the reason in plain English, for example"42 open jobs corroborated by 4 sources in the last 30 days".
The three values mean different things:
is_hiring.value |
Meaning | Example basis (illustrative) |
|---|---|---|
true |
Open jobs exist and sources corroborate them. | 42 open jobs corroborated by 4 sources in the last 30 days |
false |
Sources see the company and it has no open jobs. | All 6 open jobs closed in the last 14 days across 3 sources |
null |
Unknown. Not enough signal to decide. | No careers page or ATS found for this domain |
company.hiring_stopped fires only when value changes to false. A change to null never fires it. See Events.
Read Provenance and confidence for how corroboration feeds confidence.
Field dictionary
Section titled “Field dictionary”| Field | Type | Description | null / [] means | Derivation | Example |
|---|---|---|---|---|---|
company | Company | Company reference: id, name, domain. | Never null | normalized | {"id":"cmp_4Rk7TzP1aQ","name":"Acme Robotics","domain":"acme-robotics.example"} |
open_jobs_count | integer | Open canonical jobs right now. | Never null | normalized | 42 |
is_hiring.value | boolean | null | Whether the company is hiring. | Unknown. Never treat null as false. | inferred | true |
is_hiring.confidence | number (0-1) | Confidence in is_hiring.value. | Never null | inferred | 0.96 |
is_hiring.basis | string | Plain-English reason for the value. | Never null | inferred | "42 open jobs corroborated by 4 sources in the last 30 days" |
hiring_pulse.direction | up | flat | down | Direction of open jobs over the last 30 days. | Never null | inferred | "up" |
hiring_pulse.open_jobs_30d_change | integer | Change in open jobs over the last 30 days. | Never null | normalized | 9 |
top_job_families | {job_family, open_jobs}[] | Job families with the most open jobs. | [] = no open jobs found. | inferred | [{"job_family":"engineering","open_jobs":18}] |
sources | {provider, open_jobs, last_seen_at}[] | Open jobs per provider for this company. | [] = no source sees this company. | raw | [{"provider":"theirstack","open_jobs":31,"last_seen_at":"2026-10-11T04:02:00Z"}] |
No field matches.
The fields of the embedded company object (company.id, company.name, company.domain) are described with the job fields in the Field dictionary.
Record sample
Section titled “Record sample”The three profiles in companies.json cover the three states of is_hiring. Illustrative data: companies and domains are fictional.
{ "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 }, "top_job_families": [ { "job_family": "engineering", "open_jobs": 18 }, { "job_family": "sales", "open_jobs": 11 }, { "job_family": "operations", "open_jobs": 6 } ], "sources": [ { "provider": "betterjobs", "open_jobs": 38, "last_seen_at": "2026-10-11T06:10:00Z" }, { "provider": "theirstack", "open_jobs": 31, "last_seen_at": "2026-10-11T04:02:00Z" }, { "provider": "techmap", "open_jobs": 27, "last_seen_at": "2026-10-10T23:40:00Z" }, { "provider": "coresignal", "open_jobs": 25, "last_seen_at": "2026-10-10T18:15:00Z" } ]}{ "company": { "id": "cmp_7Jd4FgH1sA", "name": "Globex Analytics", "domain": "globex.example" }, "open_jobs_count": 0, "is_hiring": { "value": false, "confidence": 0.91, "basis": "All 6 open jobs closed in the last 14 days across 3 sources" }, "hiring_pulse": { "direction": "down", "open_jobs_30d_change": -6 }, "top_job_families": [], "sources": [ { "provider": "betterjobs", "open_jobs": 0, "last_seen_at": "2026-10-11T05:00:00Z" }, { "provider": "jobspipe", "open_jobs": 0, "last_seen_at": "2026-10-11T03:30:00Z" }, { "provider": "theirstack", "open_jobs": 0, "last_seen_at": "2026-10-10T20:00:00Z" } ]}{ "company": { "id": "cmp_9Hs2VbN6eW", "name": "Northwind Traders", "domain": "northwind.example" }, "open_jobs_count": 0, "is_hiring": { "value": null, "confidence": 0.0, "basis": "No careers page or ATS found for this domain" }, "hiring_pulse": { "direction": "flat", "open_jobs_30d_change": 0 }, "top_job_families": [], "sources": []}Compare records 2 and 3. Both have open_jobs_count: 0. Record 2 is false because three sources see the company and all its jobs closed. Record 3 is null because no source sees the company at all (sources: []).
Coverage
Section titled “Coverage”BetterJobs does not publish a static count of covered companies during the v1 preview. It is published at GA.
Coverage for one company is visible in its profile:
sources[]lists every provider that sees the company, with its ownopen_jobsandlast_seen_at.[]means no source sees this company.is_hiring.value: nullwith abasissuch asNo careers page or ATS found for this domainmarks a coverage gap, not a hiring answer.open_jobs_countcounts canonical jobs. It is usually lower than the sum ofsources[].open_jobs, because the same job seen by four providers counts once.
Your plan decides which providers can contribute. GET /v1/providers shows what your plan enables.
Freshness
Section titled “Freshness”sources[].last_seen_atshows when each provider last saw the company.hiring_pulsecovers the last 30 days.- The underlying jobs carry
first_seen_at,last_seen_atandlast_verified_at. Fetch them withcompany_domain_or(below) when you need job-level timing.
To be told when a company changes state instead of polling, create a company watch. It delivers company.hiring_started, company.hiring_stopped and job events. See Detect hiring changes.
curl -s https://api.betterjobs.cc/v1/companies/acme-robotics.example \ -H "Authorization: Bearer $BETTERJOBS_API_KEY" \ -H "BetterJobs-Version: 2026-10-01"import osimport requests
resp = requests.get( "https://api.betterjobs.cc/v1/companies/acme-robotics.example", headers={ "Authorization": f"Bearer {os.environ['BETTERJOBS_API_KEY']}", "BetterJobs-Version": "2026-10-01", }, timeout=30,)resp.raise_for_status()profile = resp.json()
hiring = profile["is_hiring"]["value"]if hiring is None: print("Unknown:", profile["is_hiring"]["basis"]) # not the same as Falseelse: print("Hiring" if hiring else "Not hiring", profile["is_hiring"]["confidence"])const resp = await fetch("https://api.betterjobs.cc/v1/companies/acme-robotics.example", { headers: { Authorization: `Bearer ${process.env.BETTERJOBS_API_KEY}`, "BetterJobs-Version": "2026-10-01", },});if (!resp.ok) throw new Error(`BetterJobs ${resp.status}`);const profile = await resp.json();
const { value, confidence, basis } = profile.is_hiring;if (value === null) console.log("Unknown:", basis); // not the same as falseelse console.log(value ? "Hiring" : "Not hiring", confidence);| Operation | Endpoint | Cost |
|---|---|---|
| Get a company hiring profile | GET /v1/companies/{domain} |
Cost: 1 credit / profile |
Create a watch (type: company) |
POST /v1/watches |
Cost: FreeEach job.opened event a watch delivers costs 1 credit; other events are free. |
Pass the domain without scheme or path: acme-robotics.example, not https://www.acme-robotics.example/.
There is no batch profile endpoint. For many companies, pick the pattern that fits:
- Their open jobs: one search with
company_domain_orset to a list of domains. You pay per unique job, not per company. See Find companies hiring. - Ongoing monitoring: one company watch per domain. Changes arrive as events.
- Many profiles: call
GET /v1/companies/{domain}per domain, within your rate limit. Each call costs 1 credit, so cache profiles instead of re-fetching them.