apps/hubspot-clone, name hubspot) is an API-only
clone of the HubSpot CRM API:
a NestJS backend on Postgres that models the CRM the way HubSpot does — every
object (contacts, companies, deals, tickets) is a generic record carrying a
free-form properties map, wired together by an associations graph, with
properties metadata, pipelines + stages, and owners on the side.
It’s faithful at the path + wire-format level: HubSpot’s CRM API is REST with
stable routes, so the clone matches GET /crm/v3/objects/contacts path-for-path,
uses HubSpot’s { results, paging: { next: { after } } } collection envelope, the
{ total, results } search envelope, the { status, message, correlationId, category } error envelope, and returns numeric-string ids. Associations are on
the v4 surface (/crm/v4/…), and requests are metered by a HubSpot-accurate
rate limiter (see below).
What’s inside
| Piece | Notes |
|---|---|
backend/ | NestJS REST API, Postgres via pg. Health at /api/health. Port 3005. |
supabase/migrations/ | 001_initial_schema.sql — the single DDL migration (schema only), run on spin and replayed on reset. |
seeds/ | acme.sql — the bundled deterministic fixture (stock properties, pipelines, stages, owners, token, sample records). |
Modes
| Mode | Services | Use it for |
|---|---|---|
api (default) | backend | Agents that talk to the HubSpot-style REST API. |
Authentication — private-app tokens
Real HubSpot apps authenticate with a private-app access token (pat-na1-…) in the Authorization header. The clone mirrors that: the token is
the canonical credential, sha256-hashed and resolved to its owning portal
(tenant). Every spin applies the acme fixture, which seeds a deterministic
token:
POST /api/oauth/v1/token, and a parity POST /api/auth/login
(admin@acme.test / password123) that issues a portal-scoped JWT so the
web/CLI path works — HubSpot itself has no email/password login route. Mint
additional private-app tokens via POST /api/account-info/v3/api-keys (plaintext
shown once).
Rate limiting
Requests pass through a HubSpot-accurate limiter (a global interceptor behind the auth guard). Successful responses carry the exact five HubSpot headers:429 whose body uses HubSpot’s documented policyName values
(TEN_SECONDLY_ROLLING, DAILY):
Seeding
contacts, companies, and deals (the template’s
declared entities) through real CRM v3 create calls. See
Seeding.
The object model — read this before pointing an agent at it
Everything in HubSpot’s CRM is a record with a property bag. The clone keeps that shape exactly:batch/{read,create,update,archive,upsert},
merge, and gdpr-delete. Associations are the full v4 surface — default and
labeled PUT (the label body array is honored), DELETE, the four
batch/{create,read,archive,labels/archive} endpoints, and GET …/labels.
Properties carry CRUD plus batch/{read,create,archive} and property
groups CRUD; pipelines and their stages have full read and write paths.
Fidelity
The template ships anAPI_PARITY.md re-audited 2026-06-28 against HubSpot’s
live docs. Scope is the CRM resource groups the clone targets (objects,
associations, properties, pipelines, owners) plus the OAuth token endpoint —
55 endpoints:
| Status | Count |
|---|---|
| ✅ exact (path + shape align) | 50 |
| ⚠️ shape-mismatch | 0 |
| ❌ missing | 5 |
POST / PUT / DELETE …/associations/{ft}/{tt}/labels)
and the two property-group batch endpoints. Seven extra routes are clone-local
control-plane (/auth/*, /account-info/v3/api-keys, /health); an eighth is a
deprecated v3 association-read alias retained for back-compat.
On the strict dimensions the audit grades: id-format passes (numeric-string
record ids, plus HubSpot’s own mixed string-id / numeric-toObjectId convention
inside association payloads) and rate-limits passes (the limiter above).
Webhooks/events and an MCP server are unimplemented, so those two dimensions are
N/A — the clone never claims to emit events or expose MCP. Whole HubSpot
families outside the core — engagements/timeline, workflows, marketing, CMS,
conversations, files, quotes, custom object schemas — are unimplemented by design.
See apps/hubspot-clone/API_PARITY.md for the per-endpoint breakdown.
The
verify command will fold this kind of live fidelity
scoring into the CLI. Until then, API_PARITY.md and the /verify-api workflow
are how fidelity is tracked.Inspect a running clone
crm_objects, property_defs, property_groups, pipelines,
pipeline_stages, associations, owners, …) comes straight from
001_initial_schema.sql — read it to know what’s queryable.