Contracts
A contract is the single source of truth for an API endpoint. It describes the HTTP method, path, parameters, response shape, and error cases — all in TypeScript.
Contract groups
Use createContractGroup to create a group of related contracts. Groups can share configuration like metadata and error schemas.
import { createContractGroup } from "contract-kit";
import { z } from "zod";
const todos = createContractGroup()
.namespace("todos")
.meta({ auth: "required" })
.errors({ 401: z.object({ message: z.string() }) });
The namespace is used for OpenAPI tags. Metadata and error schemas are inherited by all contracts in the group.
Defining contracts
Chain methods to describe the endpoint shape.
GET with path parameters
export const getTodo = todos
.get("/api/todos/:id")
.path(z.object({ id: z.string() }))
.response(200, z.object({
id: z.string(),
title: z.string(),
completed: z.boolean(),
}));
GET with query parameters
export const listTodos = todos
.get("/api/todos")
.query(z.object({
completed: z.boolean().optional(),
limit: z.number().int().min(1).max(100).optional(),
offset: z.number().int().min(0).optional(),
}))
.response(200, z.object({
todos: z.array(TodoSchema),
total: z.number(),
}));
POST with a request body
export const createTodo = todos
.post("/api/todos")
.body(z.object({
title: z.string().min(1),
completed: z.boolean().optional(),
}))
.response(201, z.object({
id: z.string(),
title: z.string(),
completed: z.boolean(),
}));
PATCH with path and body
export const updateTodo = todos
.patch("/api/todos/:id")
.path(z.object({ id: z.string() }))
.body(z.object({
title: z.string().optional(),
completed: z.boolean().optional(),
}))
.response(200, TodoSchema);
DELETE
export const deleteTodo = todos
.delete("/api/todos/:id")
.path(z.object({ id: z.string() }))
.response(204);
Error schemas
Define expected error responses per status code.
export const createTodo = todos
.post("/api/todos")
.body(CreateTodoSchema)
.response(201, TodoSchema)
.errors({
400: z.object({
message: z.string(),
errors: z.array(z.string()),
}),
401: z.object({ message: z.string() }),
});
Errors defined on a contract group are inherited by all contracts. Per-contract errors are merged with group errors.
Metadata
Attach metadata to contracts for use in middleware.
// Authentication
export const getProtectedData = todos
.get("/api/protected")
.meta({ auth: "required" })
.response(200, DataSchema);
// Rate limiting
export const createTodo = todos
.post("/api/todos")
.meta({ rateLimit: { max: 10, windowSec: 60 } })
.body(CreateTodoSchema)
.response(201, TodoSchema);
// Idempotency
export const createPayment = payments
.post("/api/payments")
.meta({ idempotency: { enabled: true } })
.body(PaymentSchema)
.response(201, PaymentResultSchema);
Metadata is available to middleware via meta — you can use it to implement auth checks, rate limiting, or anything else.
Schema libraries
Contract Kit works with any Standard Schema library.
Zod
import { z } from "zod";
const TodoSchema = z.object({
id: z.string(),
title: z.string(),
completed: z.boolean(),
});
Valibot
import * as v from "valibot";
const TodoSchema = v.object({
id: v.string(),
title: v.string(),
completed: v.boolean(),
});
ArkType
import { type } from "arktype";
const TodoSchema = type({
id: "string",
title: "string",
completed: "boolean",
});