API access is a paid capability
The documentation on this page and at /api-docs is public — no account, no token, no plan. Calling the API needs a workspace API token, which the Team and Scale plans can create; a Free workspace has none. The paid entitlement is re-checked on every request, so a downgrade revokes access immediately rather than only blocking new tokens.
See plansWorkspace-isolated contract
A workspace is a hard boundary, not a filter. Every operation resolves its organization from the trusted auth context — never from the request payload — and every query carries a workspace predicate.
- The organization id comes from the token, never from the body: a payload cannot select a workspace
- Server functions derive context from the Better Auth organization role; the API derives it from the token hash, scopes, and integration source
- Both boundaries converge on the same shared operations, so the UI and the API cannot drift apart in behaviour
- Source-scoped resources additionally verify the integration source that wrote them
Scoped Bearer tokens
Tokens are least-privilege by construction. You pick the scopes at creation time, the token is shown once, and only its SHA-256 hash is stored.
- Independent read/write/delete scopes per resource: products, leads, contacts, companies, deals, orders, customers, inventory, payments, shipments, returns
- Only a SHA-256 hash is persisted; the plaintext token is displayed exactly once
- The narrowest required scope is checked before any business work runs
- A per-IP failed-auth guard rejects a repeatedly-failing caller before the token lookup costs a read
Idempotent endpoints with typed conflict responses
Intake is safe to retry. Send the same externalId twice and you get the same record back — not a duplicate, and not a silent overwrite.
- Replaying an identical payload returns the existing record
- A different payload under the same externalId returns a typed 409 IDEMPOTENCY_CONFLICT rather than clobbering data
- Concurrent replays are serialized by a transaction-scoped advisory lock keyed on workspace + operation + external id
- Durable idempotency records live in the database, so retries stay safe across deployments and instances
Distributed Postgres rate limiter
Fixed-window limits are enforced in the database, so every serverless instance shares one counter instead of each keeping its own.
- Per workspace + integration source + endpoint — one noisy integration cannot spend another's budget
- x-ratelimit-limit and x-ratelimit-remaining on responses; retry-after on a 429
- Redis-backed limiting layers on when configured, and degrades gracefully to the database when it is not
- The browser widget has its own separate limit and never carries a workspace Bearer token
One error envelope, everywhere
Every endpoint — Bearer and adapter webhook alike — fails the same structured way, with a request id you can quote to us.
- { error: { type, code, message, requestId } } on every failure
- 400 VALIDATION_ERROR · 401 UNAUTHORIZED · 402 SUBSCRIPTION_INACTIVE · 403 FORBIDDEN / PLAN_LIMIT_EXCEEDED · 404 NOT_FOUND · 409 CONFLICT · 429 RATE_LIMITED
- Validation runs at the boundary through Zod, so a bad payload never reaches business logic
- Internal errors are never leaked: 5xx responses are captured server-side with request context and no secrets
Signed webhooks, in and out
Outbound events are HMAC-signed so you can verify us. Inbound adapters verify the platform's own signature scheme rather than trusting the sender.
- Outbound: HMAC-SHA256 signed deliveries for events such as deal.won, lead.converted, order.delivered
- Durable async delivery with retries when QStash is configured; synchronous delivery when it is not
- Inbound adapters verify each platform's real scheme — HMAC-SHA256, SHA1, ECDSA, or OAuth — read from that platform's official docs, never copied between adapters
- One universal endpoint accepts { resource: lead | order, data } from Zapier, Make, n8n, or your own backend
MCP server
A third boundary over the same shared operations, so an AI client operates the workspace under exactly the permissions a human would get.
- Streamable HTTP, stateless — nothing to install and no SSE to keep alive
- OAuth 2.1 one-click connect, or the same scoped Bearer token as the REST API
- Tools are projected from your workspace role; each one re-checks its scope, the subscription, and its own rate limit
- No direct database access: every tool goes through the shared operations layer
OpenAPI 3.1 and a live request builder
The contract is written down and kept in step with the routes it describes.
- OpenAPI 3.1 spec at /api/v1/openapi.json
- Interactive reference at /api-docs with a live request builder — public, no account required
- A machine-readable widget descriptor at /widget.json for agents building against the browser boundary
- llms.txt, roadmap.json, and changelog.json publish the same facts for AI agents
Build against it
Read the reference, create a scoped token in Settings → Developers, and integrate. The docs are open to everyone.