Point a HubSpot client at https://hubspot.sandboxapis.dev — @hubspot/api-client takes basePath on the Client constructor, and every SDK takes a base URL; it replaces https://api.hubapi.com. Paths mirror HubSpot exactly. Any token, or none, is accepted. WHAT IS SERVED: companies, contacts, deals (written /crm/v3/objects/0-3, the spelling HubSpot's own document uses) and tickets, each with its collection, its get-by-id, its POST .../search and its POST .../batch/read, with paging.next.after cursors, ?properties=, ?propertiesWithHistory= and ?associations=; plus the property catalogue and its groups under /crm/v3/properties/, both pipelines and their stages under /crm/v3/pipelines/, the owner roster at /crm/v3/owners, and the custom-object schema list. READ THIS BEFORE YOU START: the CRM data itself arrives in generation hello-14, and every generation after it inherits it. Live rides hello-19. The newest pin, hubspot-v3-g11, rides hello-16; hubspot-v3-g10 rides hello-15 and hubspot-v3-g9 rides hello-14. All four of those carry the CRM. Only hubspot-v3-g8 is older — it rides hello-13 and answers those rows with a 404 whose message says exactly that ("This universe predates the CRM canon..."). It is NOT an empty list, deliberately: an empty collection would tell you this org has no contacts, which is false and which no SDK could detect. Two more 404s to tell apart — a path outside coverage carries x-sandboxapis-coverage-kind: endpoint, and a record that simply does not exist carries no coverage header at all. Values in enumerated properties are this portal's own vocabulary; read /crm/v3/properties/{objectType} to learn it, exactly as you would against a real HubSpot portal. Check https://sandboxapis.dev/providers/hubspot for the row-by-row coverage.
Coverage badge
Full32/32 · 100%
The share of read rows with a final answer — served and verified, a deviation, retired upstream, or a reviewed empty/refusal. Computed in the coverage manifest, copied here.
hubspot.sandboxapis.devServed & verified
31 / 97%
Answering with real universe data, each response checked against HubSpot's published spec by the conformance suite on this build.
The full read API
32
Every read surface HubSpot publishes, deferred long tail included, minus the rows excluded by policy. Writes are out of scope: this universe is read-only.
Not served yet
1
Each returns an explicit, provider-shaped coverage error naming the gap — never invented data.
01 / The swap
No SDK of ours, no shim, no recorded fixtures. The same client library you already use, one environment variable different.
curl "https://hubspot.sandboxapis.dev/crm/v3/objects/companies?limit=5"Verified drop-in clients
No client library is version-pinned against HubSpot yet, so this page claims none. What is asserted is the wire: every response above is validated against HubSpot's own published spec by the conformance suite. Client pins live in coverage/client-pins.yaml and are added as each SDK joins the suite.
02 / Coverage by family
All 32 rows the coverage manifest carries for HubSpot, deferred long tail included and nothing capped. Open a family, or filter by path to find the exact endpoint your client calls.
Status — what a conformance test found
Mode — what kind of answer a row gets
A row with no mode shown has not been judged yet. Modes are the manifest's own words, from coverage/MODES.yaml; every empty and refuse carries a written reason and a review date before it counts as final.
Badge — where Full starts
REST + GraphQL + git rows that are not `excluded`. Write operations are NOT rows (DECISIONS 2026-09-01 decision 9): they are counted in meta.write_operations and never enter this ratio.
32 read surfaces in 14 families
the deal and ticket stage vocabularies. Derived from canon's own DEAL_PIPELINE and TicketStatus sets rather than from artifact rows — a pipeline is org configuration, not simulation output, which is exactly why canon holds it as a static table. Projected into renderer-hubspot/src/pipelines.ts because no renderer in this fleet depends on @sandboxapis/canon, and held to canon stage-for-stage by conformance
SERVED. A pipeline audit lists the CHANGES made to a pipeline, and nothing in this simulation ever edits one: canon's DEAL_PIPELINE is a static table precisely so a deal's probability cannot move under a pinned customer, and the ticket statuses are Zendesk's fixed six. Both pipelines are the portal's DEFAULT pipelines — the document's own `createdAt = 0` case — and an unedited default pipeline has an empty audit trail upstream too. This is not difficulty laundered as coverage: there is no audit canon being withheld, and the day the simulation grows pipeline editing the row goes red rather than hiding it
SERVED, for exactly the reason the pipeline audit above is: a stage on an unedited default pipeline has no recorded changes. The two rows are one fact read at two granularities, and both go red together the day pipeline editing enters the simulation
search is a READ HubSpot expresses as a POST, because its criteria are a document rather than a query string. All thirteen Filter.operator members, both filter-group levels, `query`, `sorts` and cursor paging are evaluated against the property map each record renders to — the same map the object read serves, so a filter written against what a caller can see selects what a caller expects
deals, from the hello-14 CRM canon. The path reads `0-3` rather than `deals` because that is how HubSpot's own pinned document writes the object type — upstream's asymmetry, carried rather than corrected, and the `deals` spelling real HubSpot also answers is deliberately NOT served (no vendored document declares it)
SERVED, and the empty SURVIVED the CRM canon — which is the re-review the prep wave owed this one (review §3: 'if hello-14 introduces a custom object type for olympus-labs, this row becomes derive and the empty is wrong'). It did not. This endpoint lists CUSTOM object schemas, and the document says so three times over — the operation summary is 'Retrieve all custom object schemas.', the single read's is 'Retrieve the schema of a specified custom object.', and info.x-hubspot-introduction reads 'Use the custom objects schema API to define new types of CRM records in your account.' hello-14 adds STANDARD objects (companies, contacts, deals, tickets), which this API never lists. So the empty is true, it is true for a reason that does not expire, and conformance.test.ts now asserts BOTH halves: that the CRM is demonstrably non-empty on this artifact and that this list still is
SERVED. The batch reader returns the subset of the requested CUSTOM object schemas that exist, and none of them ever does — the same non-expiring fact behind the collection row above, read with a request body instead of a query. `inputs: []` and `inputs` naming types that were never defined both answer the same empty `results`
refuse — SERVED as HubSpot's own not-found, which is the truthful answer for every object type a caller can name here. olympus-labs defines no CUSTOM object types, so there is no schema to fetch; and a STANDARD type (companies, contacts, deals, tickets) is refused too, for the same reason the collection above does not list one — this is the custom-object API. The prep wave left this `generate` because 'the shape of that 404 is not settled'; it is settled the way every other refusal on this host is, in HubSpot's own Error envelope, and it carries NO coverage header because the endpoint IS served and the schema is what does not exist
the go-to-market roster, and the identity join review §8.2 called the serving wave's second deliverable: every CRM record's hubspot_owner_id resolves to an owner whose email is a canonical person's, and parity/hubspot-crm-parity.test.ts walks that join through the hosts rather than through the artifact
the property catalogue. The prep wave left this `generate` because 'the pinned documents declare the SHAPE of a property and never the default LIST, so serving one now would mean publishing a catalogue transcribed from memory'. That objection is answered rather than waived: the catalogue is olympus-labs' OWN, derived from canon's columns, and every enumerated property DECLARES its vocabulary in its own `options` — which is how a real HubSpot portal works, since option sets are per-portal configuration. The one thing that IS transcribed from the document is the DEFAULT property set, which each of the four object documents prints in info.description as a table ('Properties returned by default')
03 / What's simulated
Each line below is one assertion in the conformance suite named beside it — run on every build, over one artifact, through the real renderers. Nothing is claimed here that no expect checks.
The company a `domain` search returns here is the Salesforce Account whose `Website` is that domain and the Zendesk organization whose `domain_names` carries it: one name, one description, one address, one created instant, and a `contacts` association resolving to exactly the email addresses the other two hosts serve for that account.
Against Salesforce, Zendesk · parity/crm-crm-parity.test.ts
Every ticket associated to that company here is a Salesforce Case and a Zendesk ticket with the same subject, the same customer message and the same arrival instant, and this host's `hs_pipeline_stage` is canon's finer state — the one Zendesk serves — rather than Salesforce's four-value collapse of it.
Against Salesforce, Zendesk · parity/crm-crm-parity.test.ts
A write is refused by this host's read-only boundary in HubSpot's own Error envelope and by Salesforce's in its array envelope — never by either host's coverage 404, which would say the endpoint does not exist rather than that the universe does not accept writes — and on the pre-CRM generation both refuse a CRM read with a coverage-pointing 404 rather than an empty collection.
Against Salesforce · parity/crm-crm-parity.test.ts
05 / Pinned snapshots
Each pin regenerates byte-identically on every request, so a test written against one never drifts. Generations are DIFFERENT universes, not versions of one — never swap a suffix expecting the same data.
hubspot-v3-g10.snap.sandboxapis.devgeneration 10v3servedhubspot-v3-g11.snap.sandboxapis.devgeneration 11v3servedhubspot-v3-g8.snap.sandboxapis.devgeneration 8v3servedhubspot-v3-g9.snap.sandboxapis.devgeneration 9v3servedNeed an endpoint that is not served yet?
Every row HubSpot's manifest carries is on this page, so “not here” is an answer rather than a gap in the rendering. Tell us which path and which client, and it moves up the queue — the order is set by what people ask for.