OpenAPI REST API
External REST API with oRPC contracts and automatic OpenAPI documentation
OpenAPI REST API
ProductReady includes a contract-first REST API built with oRPC and automatic OpenAPI documentation. It is designed for external integrations, mobile apps, service-to-service calls, and non-TypeScript clients.
Use tRPC for the ProductReady frontend. Use the REST API when another system needs an HTTP/OpenAPI surface or a generated SDK.
Contract Exports
The REST contract and client are exported from the thin oRPC integration layer at ~/lib/orpc:
import {
createProductReadyOpenApiClient,
productreadyOpenApiContract,
} from "~/lib/orpc";
const client = createProductReadyOpenApiClient({
baseUrl: "https://your-app.com",
apiKey: process.env.PRODUCTREADY_API_KEY,
});
const me = await client.users.me();Use productreadyOpenApiContract directly when an external TypeScript package needs to create its own oRPC client.
Documentation Options
Swagger UI
Interactive API explorer with try-it-out support
OpenAPI JSON
Raw OpenAPI 3.0 specification for tools and SDKs
API Reference
Integrated docs rendered from the OpenAPI schema
Project Structure
src/
├── app/api/v1/[[...server]]/
│ ├── app.ts # Test-compatible request wrapper
│ └── route.ts # Next.js route handler
├── domains/openapi/
│ ├── auth.ts # API key auth for regular REST endpoints
│ ├── contract.ts # Regular REST oRPC contract and Zod schemas
│ └── router.ts # Regular REST contract implementation
├── domains/systemadmin/openapi/
│ ├── auth.ts # System admin secret auth
│ ├── contract.ts # System admin oRPC contract
│ └── router.ts # System admin implementation
└── lib/orpc/
├── client.ts # Typed oRPC/OpenAPI client helper
├── spec.ts # OpenAPI spec generation
├── http.ts # Fetch handler and Swagger UI
└── index.ts # Public exportsAdding an Endpoint
Add the procedure to the owning domain contract, for example src/domains/openapi/contract.ts:
import { oc } from "@orpc/contract";
import { z } from "zod";
export const hello = oc
.route({
method: "GET",
path: "/api/v1/hello",
tags: ["General"],
summary: "Say hello",
})
.output(z.object({ message: z.string(), timestamp: z.string() }));Then implement it in the matching domain router, for example src/domains/openapi/router.ts:
hello: os.hello.handler(() => ({
message: "Hello from ProductReady!",
timestamp: new Date().toISOString(),
}));The same contract drives the runtime handler, /api/v1/openapi.json, Swagger UI, and TypeScript client.
Authentication
Protected endpoints use Bearer token authentication:
curl -H "Authorization: Bearer YOUR_API_KEY" \
http://localhost:3000/api/v1/users/meSystem admin endpoints under /api/v1/system-admin/* require the global system admin secret or a system admin API key.
Quick Start
curl http://localhost:3000/api/v1/health
curl http://localhost:3000/api/v1/openapi.jsonTesting
The app.request() wrapper is kept for existing integration tests:
import { app } from "../src/app/api/v1/[[...server]]/app";
const response = await app.request("/api/v1/health");
expect(response.status).toBe(200);For client-side contract checks, import createProductReadyOpenApiClient from ~/lib/orpc.