ProductReadyProductReady

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

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 exports

Adding 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/me

System 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.json

Testing

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.

On this page