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

Companies

What does a company hiring profile contain, and how is is_hiring decided?

View .md
Cost: 1 credit / profile

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 a confidence and a plain-English basis.
  • 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.

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, false or null.
  • 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.

FieldTypeDescriptionnull / [] meansDerivationExample
companyCompanyCompany reference: id, name, domain.Never nullnormalized{"id":"cmp_4Rk7TzP1aQ","name":"Acme Robotics","domain":"acme-robotics.example"}
open_jobs_countintegerOpen canonical jobs right now.Never nullnormalized42
is_hiring.valueboolean | nullWhether the company is hiring.Unknown. Never treat null as false.inferredtrue
is_hiring.confidencenumber (0-1)Confidence in is_hiring.value.Never nullinferred0.96
is_hiring.basisstringPlain-English reason for the value.Never nullinferred"42 open jobs corroborated by 4 sources in the last 30 days"
hiring_pulse.directionup | flat | downDirection of open jobs over the last 30 days.Never nullinferred"up"
hiring_pulse.open_jobs_30d_changeintegerChange in open jobs over the last 30 days.Never nullnormalized9
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"}]

The fields of the embedded company object (company.id, company.name, company.domain) are described with the job fields in the Field dictionary.

The three profiles in companies.json cover the three states of is_hiring. Illustrative data: companies and domains are fictional.

companies.json — record 1 (illustrative)
{
"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" }
]
}

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: []).

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 own open_jobs and last_seen_at. [] means no source sees this company.
  • is_hiring.value: null with a basis such as No careers page or ATS found for this domain marks a coverage gap, not a hiring answer.
  • open_jobs_count counts canonical jobs. It is usually lower than the sum of sources[].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.

  • sources[].last_seen_at shows when each provider last saw the company.
  • hiring_pulse covers the last 30 days.
  • The underlying jobs carry first_seen_at, last_seen_at and last_verified_at. Fetch them with company_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.

Terminal window
curl -s https://api.betterjobs.cc/v1/companies/acme-robotics.example \
-H "Authorization: Bearer $BETTERJOBS_API_KEY" \
-H "BetterJobs-Version: 2026-10-01"
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_or set 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.