Public API (REST, SDK, MCP)
External API surfaces — REST, OpenAPI, SDK, CLI, and MCP — and how they relate to tRPC.
Zero To Shipped exposes one tRPC router (@zts/trpc) through several transports. Pick the surface that matches your client.
| Surface | URL | Auth | Best for |
|---|---|---|---|
| tRPC | /api/trpc | Session cookie / Better Auth | Web, mobile, extension (first-party) |
| REST | /api/rest | x-api-key: zts_… | Integrations, webhooks, scripts |
| OpenAPI | /api/openapi.json | Spec only | SDK generation, API explorers |
| Swagger UI | /api/docs | Browser | Interactive REST docs |
| MCP | /api/mcp | OAuth + API keys | Cursor and other MCP hosts |
| Auth OpenAPI | /api/auth/reference | Varies | Better Auth routes |
What is exposed
Only procedures tagged with apiExposure() in packages/trpc/src/openapi-meta.ts appear on REST, in packages/sdk/openapi.json, and as MCP tools. Today that covers user profile, user preferences, and utImage.delete (Images tag).
Not exposed (app-internal, session-only tRPC): admin.*, auth.changePassword, polar.*, and other routers without apiExposure().
REST and OpenAPI
REST routes come from tRPC procedures that use apiExposure() (sets both OpenAPI and MCP meta):
Handler: apps/web/src/app/api/rest/[...path]/route.ts
Live spec: /api/openapi.json (REST base {NEXT_PUBLIC_APP_URL}/api/rest)
Swagger UI: /api/docs (loads the same spec; use Authorize with your zts_ API key as x-api-key)
Committed spec: packages/sdk/openapi.json (regenerated by @zts/sdk build)
API keys
REST (and MCP tool calls that use keys) authenticate with the x-api-key header. Keys are issued via the Better Auth API key plugin (zts_ prefix, configured in packages/auth/src/index.ts).
Create keys in the app UI or through Better Auth’s API key endpoints after migrations are applied (pnpm db:migrate).
Example:
TypeScript SDK (@zts/sdk)
packages/sdk ships a generated client (@hey-api/openapi-ts) with two API classes: Users and Images.
Regenerate after changing exposed procedures:
Configure once (defaults baseUrl to http://localhost:3000/api/rest):
Other Users methods: userGetUserForEditingProfile, userUpdatePreference, userGetSinglePreference, userMarkUserAsOnboarded, userResetUserOnboarding.
Env helpers: ZTS_API_KEY (required), optional ZTS_BASE_URL (full REST prefix, e.g. https://demo.zerotoshipped.com/api/rest). Or initializeFromEnv() from @zts/sdk.
Use the SDK for integrations and workers — not first-party web/mobile (use tRPC). See packages/sdk/README.md in the product repo.
CLI (zts)
packages/cli publishes the zts binary (request, me, preferences, config).
Build:
Environment:
| Variable | Purpose |
|---|---|
ZTS_API_KEY | zts_… API key (required) |
ZTS_BASE_URL | REST base URL (default http://localhost:3000/api/rest) |
From the monorepo without linking: pnpm --filter @zts/cli run dev me. After cd packages/cli && npm link, use zts globally.
MCP (Cursor and agents)
MCP exposes the same apiExposure() procedures as REST (shared mcpName / OpenAPI path).
Endpoint: /api/mcp (implemented in apps/web/src/app/api/[transport]/route.ts using trpc-to-mcp).
Cursor configuration
Auth flow
- Create an API key in the app (
zts_prefix). - MCP clients discover OAuth via
/.well-known/oauth-protected-resource(see routes underapps/web/src/app/.well-known/). - Tool calls use the issued OAuth token or API key.
Better Auth’s MCP plugin (packages/auth) and OAuth helpers under apps/web/src/server/mcp/ back this flow. After pulling schema changes for API keys / MCP OAuth tables, run pnpm db:migrate.
Product repo reference: docs/MCP_SETUP.md in the zerotoshipped repository.
Boundaries (what to use when)
- Inside the monorepo UI apps → tRPC + shared
@zts/trpctypes. - Partner integrations, cron scripts, serverless workers → REST + SDK or CLI + API key.
- IDE agents (Cursor) → MCP + OAuth/API keys; keep tool surface minimal via
.meta({ mcp: { enabled: true } }).
Adding a new public endpoint
- Implement the procedure in
packages/trpc/src/routers/. - Add
.meta(apiExposure({ … }))with Zod v4 inputs (useemptyInputfor no-body POSTs). - Use
protectedProceduresoctx.sessionorctx.apiKeyis required. - Regenerate:
pnpm --filter @zts/trpc run generate:openapithenpnpm sdk:build.
See Better Auth for session-based access on tRPC and Database for migrations when auth tables change.