Skip to main content

From OpenAPI to your TypeScript stack.

@apical-ts/craft generates Zod v4 schemas, route metadata, typed clients, and server wrappers from one OpenAPI document.

Use the generated contract directly or compose it with Hono, MSW, and React Query. By providing mathematical precision to your types, you also give Coding Agents the exact primitives they need to write safer code for you.

generate from one specCLI
npx @apical-ts/craft generate \
-i https://petstore.swagger.io/v2/swagger.json \
-o ./generated \
--client --server

Framework-agnostic routes.

Generate reusable schemas, route metadata, client operations, and server wrappers as small strong typed building blocks for your stack and agents.

01schemas/

Schema

Validate payloads and infer exact runtime-safe types.

import { UserSchema } from "./generated/schemas/User.js";
const result = UserSchema.safeParse(apiResponse);
if (result.success) {
console.log(result.data.email);
}
02routes/

Route

Access methods, paths, and maps for custom adapters and tooling.

import { getPetByIdRoute } from "./generated/routes/getPetById.js";
const route = getPetByIdRoute();
console.log({
method: route.method,
path: route.path,
responseMap: route.responseMap,
requestMap: route.requestMap,
});
03client/

Client

Call operations with discriminated unions. Zero bloat, only import what you use.

import { findPetsByStatus } from "./generated/client/findPetsByStatus.js";
// Import just the operations you need
// without pulling in a huge client bundle.
const response = await findPetsByStatus({
query: { status: "available" },
});
// Strict typing over status code and content type
// using discriminated unions guides agents toward safe code
if (response.status === "200") {
// Zod v4 parsed payload
console.log(response.parsed.data[0].name);
}
04server/

Server

Implement typed handlers with automatic runtime validation.

import type { getPetByIdHandler } from "./generated/server/getPetById.js";
const handler: getPetByIdHandler = async (params) => {
if (!params.isValid) return { status: "400" };
const petId = params.value.path.petId;
const pet = mockPets.find((candidate) => candidate.id === petId);
if (!pet) return { status: "404" };
return {
status: "200",
contentType: "application/json",
data: pet,
};
};