The data model
Dimensions, provenance, honest absence, coverage, and the change feed's guarantees.
Bixel's spine is simple: a Company has Facts, facts point at Captures, and confirmed transitions between facts become ChangeEvents. Categories group companies for comparison.
Dimensions
Every fact lives in one of ten dimensions:
pricing · features · security · positioning · stack · hiring · reliability · funding · location · legal
Fact keys are dot-paths inside a dimension (pricing.tier.starter.monthly_usd, features.integration.langchain, security.cert.soc2-type-ii). Keys are stable and machine-checkable; the key registry is enforced at write time, so a key you see once will mean the same thing next month.
Fetching one dimension at a time (?dimension=pricing on the facts endpoint, or the dimension argument on MCP tools) is the cheapest way to work: full company records are large, and most questions only need one dimension.
Provenance
Every fact carries provenance, one of exactly three values:
observed— Bixel measured it independently: response headers, DNS, TLS, detected technologies.company_stated— the company published it on its own surfaces: pricing tables, feature claims, security pages, its own ATS job board.sec_form_d— read from a public SEC filing (funding rounds, incorporation), with the filed document archived as the capture.
Stated and observed are deliberately kept apart, and the /claims endpoint (check_company_claims over MCP) cross-reads them: what a company states versus what is observable. Where both provenances carry the same key, that is dual measurement, not a contradiction.
Every fact also carries:
as_of— the capture date the fact was last supported by. Facts are only as current as their last capture;as_ofsays exactly how current that is.source_url— the page that actually stated it.
There is deliberately no per-fact confidence score. Facts carry evidence
states (provenance, capture pointers, as_of), and pipeline quality is
published as measured accuracy on the methodology page. A number between
0 and 1 invented per fact would be decoration, not measurement.
Honest absence
A missing fact means "not captured", never "false". Bixel does not infer values it has not seen, and endpoints answer gaps explicitly: a fact with no stored capture behind it answers proof_not_available rather than pretending, and companies whose sites block crawling carry visibly thinner records rather than padded ones.
Coverage
Coverage varies by company, structurally: category-by-category growth always trails thin coverage behind a dense core. So depth is contract, not inference: every company payload carries a coverage block —
"coverage": {
"dimensions_captured": 7,
"dimensions_total": 10,
"per_dimension": { "pricing": 11, "features": 42, "security": 17, "positioning": 7, "stack": 35, "hiring": 4, "reliability": 3, "funding": 0, "location": 0, "legal": 0 },
"thin": false
}per_dimension is zero-filled — absence is explicit, never by omission. thin is the honest coarse bit: fewer than 3 dimensions captured (calibrated against the real distribution — capture-blocked members sit at 1–2, everything with a working crawl at 3+). Category payloads carry it too: a per-member { dimensions_captured, dimensions_total, thin } plus a category-level rollup { members, deep, thin }. Read depth from these blocks, never from raw fact counts.
Jobs: ATS-tracked vs page-stated
Hiring is measured two ways on purpose:
jobs.active_postings(and the/jobsendpoint) — live postings from the company's ATS board (Ashby, Greenhouse, Lever...). Only companies with a detected board have this; it is precise, dated, and includes compensation where stated.hiring.open_roles(a fact) — what the company's careers page states, extracted like any othercompany_statedfact.
A company can honestly show 0 tracked postings and a careers page claiming open roles: no board is detected, so the postings count is not tracked while the page statement stands. The payload says so itself — the jobs block is labeled:
"jobs": { "active_postings": 0, "ats_tracked": false, "note": "Job postings are not ATS-tracked for this company; hiring.open_roles reflects their careers page." }Read the pair as coverage information, not contradiction.
The tracked tier
Deep records cover the member catalogue. Beyond it, millions of mechanically verified domains answer with an honest liveness envelope instead of a 404:
{
"data": {
"company": { "domain": "example-tracked.com" },
"tier": "tracked",
"status": "active",
"tracked_since": "2026-07-28",
"last_verified_live_at": "2026-07-30",
"verification": "mechanical checks only; zero published facts",
"not_yet": "not yet verified as a member; no dimensions, no facts"
}
}status is one of active, dormant, dead, or blocked — blocked
means the domain resolves and a server answers but refuses automated
reading, so liveness is recorded as unverifiable rather than guessed
(the refusal date is served under last_refused_automated_check_at).
A tracked record claims exactly what its checks measured and nothing
more; the methodology page defines
each tier's claim precisely. Authenticated lookups of tracked domains
register demand, and enough distinct demand promotes a company into
member verification.
The change feed
/v1/changes (and per-company /history) is the dated event log. Every event is labeled with how it was derived, and the label is the feed's honesty mechanism:
derivation | Meaning |
|---|---|
confirmed | A live change confirmed by two independent captures. The pipeline holds a first observation as pending, invisible to every public surface, until a strictly newer capture shows the same state; a revert before confirmation discards it. Confirmed events cannot flap. |
historical | A diff between two web-archive generations, dated by the later generation. Real and dated, but archive sampling is sparse: treat dates as "by this date", not "on this date". |
baseline | First observation when a company entered tracking. Not a company change. Hidden by default. |
legacy | Events from earlier pipeline generations. Hidden by default. |
orphaned | The trailing part of an event chain the current record no longer corroborates (reprocessing artifacts). Hidden by default. |
The default feed serves confirmed + historical only. Everything else stays queryable via ?derivation= — the data is inspectable, it just never poses as live change.
detected_at is always the first-observation date (the capture day), not the date the pipeline wrote the row. Because confirmation waits for a second capture, an event can appear in the feed a few days after its detected_at.
Events carry significance (normal or high) and, authenticated, old_value / new_value / summary. The access ladder: keyless callers see that something changed, its key, and when — capped at 90 days / 50 events; a free key unmasks values within the same 90-day window (with working cursors); Pro unlocks the full archive. Depth is the paid axis, and it is never laddered above Pro.