packages/api is the auth and billing tier’s HTTP surface. It exports a Hono app that core-api mounts under /api through honoGateway, and the oRPC router that defines the typed procedures the web app calls. It is a different API from /v1. It has its own routing, its own auth check and its own error format.

Exports

The Hono app

src/index.ts builds the app in this order: The oRPC handlers receive { headers } as their initial context. If neither handler matches, Hono answers its own 404.
The CORS method list has no PUT, PATCH or DELETE. One procedure, notifications.updatePreference, is declared with method PUT. A cross origin browser call to its REST path would fail preflight. Calls made through the RPC transport use POST and are not affected, and same origin calls through the web app’s proxy involve no CORS at all.

Two transports for the same procedures

Every procedure is reachable two ways:
  • RPC, under /api/rpc/.... This is what the typed oRPC client in the web app uses.
  • OpenAPI style REST, under /api/..., at the path and method each procedure declares with .route({...}).
openApiHandler adds two plugins:
  • SmartCoercionPlugin, which coerces query and path strings to the types the Zod schema expects.
  • OpenAPIReferencePlugin, which serves an API reference at /api/docs. Its spec merges the oRPC routes with better-auth’s own OpenAPI schema (auth.api.generateOpenAPISchema()), with the auth paths prefixed by /auth.

Procedure builders

src/orpc/procedures.ts:
There is no studio role in this tier. A procedure that acts on an organization must check membership itself. The billing procedures do it with requireOrganizationMember and requireBillingManager in modules/payments/lib/billing.ts.

Locale middleware

localeMiddleware in src/orpc/middleware/locale-middleware.ts reads the NEXT_LOCALE cookie from the headers and adds locale to the context, defaulting to the i18n default. createCheckoutLink and createCustomerPortalLink use it.

The router

src/orpc/router.ts:

users

organizations

payments

See Billing and plan limits for the rules.

notifications

admin

All built on the admin procedures in modules/admin/procedures/. All REST routes are relative to /api.

Errors

Procedures throw ORPCError with a code such as UNAUTHORIZED, FORBIDDEN, BAD_REQUEST or INTERNAL_SERVER_ERROR, and optional data. Billing refusals carry data.reason:
Both handlers register a client interceptor that logs every error with logError('oRPC handler error', error). Responses are in oRPC’s own error format, not the /v1 envelope. Admin deletion refusals are mapped in modules/admin/lib/deletion-refusal.ts.

Rate limiting and body size

/api is mounted before the /v1 limiter and is not rate limited by Express. Its bodies pass through the global express.json parser first (the gateway forwards the captured raw bytes), so the 24mb limit applies.

Files that look important and are not wired

  • src/lib/backend-client.ts and src/lib/backend-config.ts: a client that signs requests to a separate backend with X-Service-Token headers and a different envelope (success, error). It comes from before the consolidation. Nothing in the package imports the client, and the config module would throw at import if BACKEND_URL, BACKEND_SERVICE_SECRET or BACKEND_LIFECYCLE_SECRET were missing.
  • src/lib/openapi-schema.ts: mergeOpenApiSchemas, a helper to merge two OpenAPI documents. The handler builds its spec without it.
  • src/config.ts: an empty config object kept for consistency with the other packages.

Adding a procedure

1

Create the procedure file

Add src/modules/<area>/procedures/<name>.ts. Start from protectedProcedure unless the call is really public.
2

Check authorization yourself

The builder only proves there is a session. Verify the user’s membership of the organization named in the input before reading or writing anything.
3

Register it

Add it to the area’s router.ts. The key becomes the client method name.
4

Rebuild

pnpm --filter @repo/api build. core-api loads the package from dist. The web app gets the new method through the ApiRouterClient type.
Domain features belong in /v1/web, not here. This tier is for things tied to the auth user or the organization’s billing.