Done. Synced 247 contacts in 412 ms.
Ada Tan
CEO · Mariposa Labs ·
The developer platform for HubSpot — syncs, UI extensions, agent tools, and a runtime you own. One install. The whole platform.
CEO · Mariposa Labs ·
Done. Synced 247 contacts in 412 ms.
Deployed in 1.2 s · 124 KiB.
Quote PDF sent · stage → Proposal.
7 active hooks · 1.2k events / day.
Generates a branded quote PDF from the deal record and emails it to the primary contact. Advances stage on send.
412 ms · 247 rows
deployed
184 ms
1.2 s
CEO · Mariposa Labs · ada@mariposalabs.io
Synced 247 contacts in 412 ms.
Deployed in 1.2 s · 124 KiB.
Quote PDF sent · stage → Proposal.
7 active hooks · 1.2k events / day.
Point a Worker at any API and its records land as Contacts, Companies, or any custom object, then stay current on a schedule. Cursors, identity matching, retries, and drift detection are handled for you. You write the fetch.
import { defineSource, defineWorker, env } from "@hs-x/sdk"; const airtableContacts = defineSource({ name: "airtableContacts", auth: { type: "bearer", token: env("AIRTABLE_TOKEN") }, async fetch({ cursor, http }) { const res = await http.get("https://api.airtable.com/v0/appXYZ/Contacts", { query: { pageSize: 100, offset: cursor }, }); return { cursor: res.body.offset, rows: res.body.records.map(r => ({ key: r.fields.Email, data: { email: r.fields.Email, lifecycle_stage: r.fields.Stage, annual_revenue: r.fields.ARR, owner: r.fields.OwnerEmail, }, })), }; }, }); export default defineWorker(({ worker }) => { worker.sync(airtableContacts, { into: "contacts", schedule: "5m", schema: { email: "email", lifecycle_stage: { type: "enum", values: ["subscriber", "lead", "customer"] }, annual_revenue: "currency", owner: "user", }, }); });
@hs-uix gives you React components that match HubSpot's look, typed from your portal's own schema. Every app card gets a backend in your Cloudflare — same types end to end, no glue code, and streaming responses for the slow work.
import { DataTable } from "hs-uix/datatable"; import { KeyValueList, SectionHeader } from "hs-uix/common-components"; import { formatCurrency } from "hs-uix/utils"; import { Tile, hubspot } from "@hubspot/ui-extensions"; function ContactPanel({ context, runServerless }) { return ( <Tile> <SectionHeader title="Properties" /> <KeyValueList items={[ { label: "Lifecycle", value: "Customer" }, { label: "Annual revenue", value: formatCurrency(184000) }, { label: "Owner", value: "Carter McKay" }, ]} /> <SectionHeader title="Open deals" /> <DataTable columns={[ { id: "name", label: "Deal", sortable: true }, { id: "amount", label: "Amount", format: "currency" }, { id: "stage", label: "Stage", filter: "enum" }, ]} fetch={() => runServerless({ name: "listDeals", parameters: { contactId: context.crm.objectId } })} /> </Tile> ); } hubspot.extend(() => <ContactPanel />);
One TypeScript function becomes an agent tool Breeze can call and a custom workflow action your team can drop into any workflow — same code, same auth. It's exposed over MCP too, so coding agents like Claude and Cursor can call it just the same.
worker.tool("createQuote", { label: "Create quote", description: "Generate a quote PDF and email it to the primary contact.", input: { dealId: "deal", template: { type: "enum", values: ["standard", "saas"] }, }, expose: ["workflow", "breeze"], async run({ input, ctx }) { const deal = await ctx.hubspot.deals.get(input.dealId); const contact = await deal.primaryContact(); const pdf = await renderQuote(deal, input.template); await ctx.hubspot.files.upload(`Quote-${deal.id}.pdf`, pdf); await ctx.hubspot.emails.send({ to: contact.email, subject: `Quote ${deal.id}`, template: "deal-quote", attach: [`Quote-${deal.id}.pdf`], }); return { message: `Quote sent to ${contact.email}.` }; }, });
Set up a webhook subscription for deal-stage changes, form submissions, any HubSpot event — and run a handler on your runtime. We verify every signature, drop duplicate deliveries, and queue the flood when HubSpot pushes faster than you can handle. You write the handler.
Your code, data, and customers' tokens live in a Cloudflare account you own — Workers, Durable Objects, Queues, KV, D1, all of it. A deploy needs only your HubSpot and Cloudflare tokens — no hs-x account required. If hs-x disappeared tomorrow, every deployed app would keep running.
A CLI for you. An SDK that's just TypeScript. An MCP server so your coding agent can do everything you can. A REST API for everything else. Same types, same project, same deploy.
Scaffold, deploy, tail logs, manage portals. Token-efficient — coding agents can drive it without burning context.
$ curl -fsSL hs-x.dev | sh $ npx hs-x init my-portal $ npx hs-x deployRead the CLI docs →
Syncs, workflow actions, triggers, UI extensions — declared in plain TypeScript with a schema that types the rest of your code for you.
import { defineSource, defineWorker } from "@hs-x/sdk"; const airtable = defineSource({ … }); export default defineWorker(({ worker }) => { worker.sync(airtable, { into: "contacts", … }); });Read the SDK docs →
A platform control surface for Claude, Cursor, and Codex — scaffold projects, deploy, inspect logs, tail drift. Capability parity with the CLI.
$ claude mcp add --transport http \ hs-x https://mcp.hs-x.dev/mcpRead the MCP spec →
The same control plane the CLI and MCP speak — projects, deploys, environments, rollouts, logs. PATs, scoped tokens, signed webhooks.
POST /v1/projects/{id}/deploys GET /v1/deploys/{id}/logs POST /v1/projects/{id}/rolloutsRead the API docs →
hs-x handles the hard platform plumbing — types, deploys, token storage, per-portal rate limits, audit logs. HubSpot owns the portal. You own the Cloudflare account it all runs on, and we hand you the infrastructure-as-code file as your way out. No lock-in by design.
Need it built? The team behind hs-x ships HubSpot work on hs-x — marketplace apps, syncs, agent tools, card migrations. Retainer or by the project.
Scaffold an app, wire a workflow action to an agent tool, watch Breeze call it against a developer test account. All from hs-x init.