Objects / The Company object

The Company object

Every field on a company profile, its type, when it's null, and how the embedded version on a job differs from the full one.

The shape returned by /v1/companies/{slug} and /v1/companies, and embedded as company on every job.

158,545 companies have a profile. Coverage within a profile is uneven — name and slug are dependable, funding and stock exist for a small minority — so treat every field below as optional.

Example#

A company with most fields populated
{
  "slug": "linear",
  "name": "Linear",
  "domain": "linear.app",
  "website": "https://linear.app",
  "logo": "https://media.licdn.com/dms/image/…",
  "industry": "Software Development",
  "type": "Privately Held",
  "description": "Linear is a purpose-built tool for planning and building products…",
  "slogan": "The issue tracking tool you'll enjoy using",
  "specialties": ["issue tracking", "project management", "developer tools"],
  "employee_count": 120,
  "size_range": "51-200",
  "followers": 48210,
  "founded_year": 2019,
  "hq": {
    "country": "US",
    "region": "California",
    "locality": "San Francisco",
    "raw": "San Francisco, California, United States"
  },
  "funding": {
    "rounds": 4,
    "last_round_type": "Series B",
    "last_round_date": "2022-06-15",
    "last_round_amount_usd": 35000000,
    "total_investment_usd": 52000000,
    "crunchbase_slug": "linear-2",
    "crunchbase_categories": ["Software", "Productivity Tools"]
  },
  "stock": null,
  "affiliated_companies": [],
  "data_tier": "remco",
  "jobs_count": 12,
  "updated_at": "2026-07-02T09:14:33.000Z"
}

The three shapes on a job#

Before the field list, the distinction that causes the most confusion: job.company is not always this object.

ShapeWhenHow to detect
Full PublicCompanyThe posting resolved to a profile. 99.9997% of jobs.company.slug is present
Minimal fallbackNo profile matched. Six fields, built from what the posting itself declared.company is non-null, company.slug is absent
nullThe posting carries no employer name at all.company === null

The fallback carries only name, domain, website, logo, industry, description. It's structurally a subset of the full object, which is exactly what makes it easy to miss — the code path that reads company.name works fine and the one that reads company.employee_count quietly gets undefined.

Detect the fallback with `slug`, not with field presence

slug is the only field guaranteed on a full profile and impossible on a fallback. Every other candidate — industry, logo, description — is nullable on the full object too, so its absence proves nothing.

JS
const isEnriched = job.company?.slug != null;
 
// Fine on both shapes.
const name = job.company?.name ?? "Unknown";
 
// Only meaningful when enriched — undefined on a fallback, and `undefined > 500`
// is false, so a filter like this silently drops fallback companies.
if (isEnriched && job.company.employee_count > 500) { /* … */ }

A fallback has no slug, so there's nothing to fetch from /v1/companies/{slug}. Guard the lookup — building a URL with undefined in it earns a 404.

The fallback is rare (roughly 2 jobs in 600,000 fail to resolve), but "rare" isn't "never", and a crash on the unenriched path is the kind of bug that surfaces in production weeks later.

Identity#

slug
string

The company's identifier and primary key — linear, stripe. This is what /v1/companies/{slug} takes, and what job.company.slug gives you.

Matched exactly. It's a LinkedIn-style slug, so it usually resembles the company name, but don't construct one from a name and expect it to resolve — read it from a job or from /v1/companies.

name
string | null

Display name.

domain
string | null

Primary domain, e.g. linear.app. Bare hostname, no scheme.

website
string | null

Full website URL, including scheme. Related to domain but sourced separately — the two can disagree, and either can be null while the other isn't.

logo
string | null

Logo image URL. Hosted on the upstream provider's CDN, not by us, so it can rot independently of the profile. Fall back gracefully when it 404s.

Profile#

industry
string | null

Industry label, e.g. Software Development. A large open vocabulary rather than a fixed enum, and not the same vocabulary as a job's taxonomies.

type
string | null

Organisation type as published — e.g. Privately Held. Free-form; treat it as display text.

description
string | null

Company description, plain text. Can run to several paragraphs.

slogan
string | null

Tagline, when the company publishes one.

specialties
string[]

Self-declared specialties. An array — empty rather than null when there are none.

affiliated_companies
string[]

Names of related companies — subsidiaries, parents, regional entities. An array, empty when there are none. These are names, not slugs, so they aren't directly fetchable.

Size and age#

employee_count
number | null

Reported headcount. A point-in-time figure from the last profile refresh, not a live number.

size_range
string | null

Headcount bracket as published, e.g. 51-200. Can be present when employee_count is null, and the two are sourced independently — an employee_count outside its own size_range is unusual but possible.

followers
number | null

Social follower count. The sort key for /v1/companies.

founded_year
number | null

Year founded, as a number — 2019, not "2019" and not a date.

HQ#

hq
object | null

Headquarters location. null when all four fields are null.

HQ country is ISO-2, job countries are not#

Two country vocabularies, one dataset

The same country is spelled two different ways depending on which object you're holding:

FieldValueVocabulary
company.hq.country"US"ISO-2 code
job.location.countries["United States"]Full names

This is a real inconsistency, not a documentation simplification. Two consequences:

  • Joining requires a mapping. job.location.countries.includes(company.hq.country) is always false. You need an ISO-2 → name table between them.
  • The filters absorb it, the data doesn't. ?country= accepts full names or ISO-2 on both /v1/jobs and /v1/companies — but the jobs filter searches the job's location and the companies filter searches HQ, and the field values keep their separate vocabularies.

A company's HQ is also not where its jobs are — a US-headquartered company posts roles worldwide. Filter jobs by the job's location; use hq to describe the employer, not to locate the work.

Funding and stock#

funding
object | null

Venture funding history. null when all fields are null — which is the common case, since most companies in the dataset have no venture funding to report.

stock
object | null

Public listing. null for private companies and for public ones whose listing we don't have — the absence of stock is not evidence a company is private.

Both money fields are USD regardless of where the round was raised, which makes them the only normalised currency in the API — job salaries keep their original currency.

Provenance#

data_tier
string | null

Where the profile's data came from, which is a rough proxy for how complete and how fresh it is. See below.

jobs_count
number

The company's live openings — non-duplicate, non-expired postings in the pool right now. Present on list and retrievenot on the company embedded on a job. Recomputed roughly every 15 minutes; 0 means not currently hiring, not "no data". To filter on it, use has_jobs=true on /v1/companies.

updated_at
string | null

When the profile was last refreshed, ISO 8601 UTC.

Company profiles refresh on their own cadence, unrelated to job ingestion. A job posted an hour ago can carry a company profile that's months old — that's expected, not a bug.

data_tier values#

The tiers you'll care about rank like this:

TierCompaniesMeaning
remco12,392Richest profiles. Sorted first by /v1/companies.
linkedin67,357Standard enriched profile.
free75,880Thinnest profiles. The largest tier.

Ten values exist, not three

Those three cover about 98% of companies, but the field carries ten distinct values in live data. The rest describe how a company was matched to its web presence:

TierCompanies
free75,880
linkedin67,357
remco12,392
bidirectional1,566
sibling467
site-declared349
linkedin-fresh260
bidirectional-variant122
site-declared-mismatch107
site-declared-noweb45
Total158,545

Treat data_tier as an open vocabulary. Code that switches on three values and throws on a fourth will meet sibling eventually — fall through to a default instead. New tiers appear as matching strategies are added, and they arrive without a version bump.

Firmographic filtering#

Server-side, from two directions:

  1. Search companies directly. /v1/companies takes q (name substring), industry, country (HQ), size, employee_count_gte/lt, and has_jobs=true, with a real total and offset paging. "Find all fintech companies in Germany with 200+ employees" is now one query: ?industry=Financial Services&country=DE&employee_count_gte=200.
  2. Filter jobs on company attributes. /v1/jobs has the company axis too — company_industry, company_size, employee_count_gte/lt, funding_gte/lt, company_country, domain — for when the question is really about the postings at companies like that.

For anything not covered by a filter (founded_year, followers, stock), the embedded company on every job still makes client-side predicates free — company data covers all three routes.

Nullability at a glance#

FieldNull when
slugNever on a full profile; absent on a job's fallback company
jobs_countNever — 0 when there are no live openings; absent on a job's embedded company
specialties, affiliated_companiesNever — empty arrays instead of null
hq, funding, stockAll of the object's own fields are null
Everything elseWhenever the upstream profile didn't carry it

Note the split: the two array fields go empty, the three nested objects go null. company.specialties.length === 0 is safe; company.funding.rounds is not.

This differs from job.location, which never collapses because one of its fields always resolves. On a company, the nested objects really do become null — always guard them.

Was this page helpful?