ZTS Docs

REST API

OpenAPI-backed REST surface, API keys, and CLI — for integrations outside the monorepo.

Zero To Shipped exposes one tRPC router (@zts/trpc) through several transports. This page covers REST and the CLI. For typed clients see SDK. For IDE agents see MCP.

SurfaceURLAuthBest for
tRPC/api/trpcSession cookie / Better AuthWeb, mobile, extension (first-party)
REST/api/restx-api-key: zts_…Integrations, webhooks, scripts
OpenAPI/api/openapi.jsonSpec onlySDK generation, API explorers
Swagger UI/api/docsBrowserInteractive REST docs
MCP/api/mcpOAuth + API keysCursor and other MCP hosts

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):

.meta(
  apiExposure({
    description: "Get the authenticated user's profile",
    mcpName: "get_current_user",
    path: "/users/me",
    summary: "Get current user",
    tags: ["Users"],
  })
)
ResourceLocation
REST handlerapps/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 (Authorize with your zts_ key as x-api-key)
Committed specpackages/sdk/openapi.json (regenerated by @zts/sdk build)

API keys

REST authenticates 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).

curl -X POST "$BASE_URL/api/rest/users/me" \
  -H "x-api-key: zts_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{}'

Most exposed POST endpoints expect an empty JSON body {} when there is no input.

CLI (zts)

packages/cli publishes the zts binary for terminal access to the same REST surface.

Build:

pnpm cli:build
VariablePurpose
ZTS_API_KEYzts_… API key (required)
ZTS_BASE_URLREST base URL (default http://localhost:3000/api/rest)
export ZTS_API_KEY=zts_...
export ZTS_BASE_URL=http://localhost:3000/api/rest   # optional
 
zts request POST /users/me '{}'
zts request DELETE /images/your-image-id
zts me
zts preferences
zts config

From the monorepo without linking: pnpm --filter @zts/cli run dev me.

Boundaries (what to use when)

flowchart LR
  subgraph firstParty [First-party apps]
    Web[apps/web]
    Mobile[apps/mobile]
    Ext[apps/extension]
  end
  subgraph external [External / automation]
    SDK[@zts/sdk]
    CLI[zts CLI]
    MCP[MCP host]
    REST[HTTP client]
  end
  TRPC["/api/trpc"]
  RESTAPI["/api/rest"]
  MCPAPI["/api/mcp"]
  Web --> TRPC
  Mobile --> TRPC
  Ext --> TRPC
  SDK --> RESTAPI
  CLI --> RESTAPI
  REST --> RESTAPI
  MCP --> MCPAPI
  TRPC --> Router["packages/trpc appRouter"]
  RESTAPI --> Router
  MCPAPI --> Router
  • Inside the monorepo UI apps → tRPC + shared @zts/trpc types.
  • Partner integrations, cron scripts, serverless workers → REST + SDK or CLI + API key.
  • IDE agents (Cursor)MCP + OAuth/API keys.

Adding a new public endpoint

  1. Implement the procedure in packages/trpc/src/routers/.
  2. Add .meta(apiExposure({ … })) with Zod v4 inputs (use emptyInput for no-body POSTs).
  3. Use protectedProcedure so ctx.session or ctx.apiKey is required.
  4. Regenerate: pnpm --filter @zts/trpc run generate:openapi then pnpm sdk:build.

See Better Auth for session-based access on tRPC and Database for migrations when auth tables change.

On this page