HTTP Contracts
HTTP Contracts at the Askr server boundary, with validated input and explicit failure states.
Example
Define an executable schema once and reuse its JSON Schema representation in the HTTP contract and generated-client workflow.
import { schema } from '@askrjs/schema';
export const projectInput = schema.object({
name: schema.string({ minLength: 1 }),
ownerId: schema.uuid(),
});
const parsed = projectInput.safeParse(await request.json());
const jsonSchema = projectInput.jsonSchema;Endpoint descriptors
get(), post(), put(), patch(), del(), head(), and options() from @askrjs/fetch each return an EndpointBuilder you chain .params<T>(), .query<T>(), .headers<T>(), .body(codec), .returns(status, codec), and .errors({...}) on to describe one HTTP operation. defineApi({...}, metadata?) collects a map of these builders into an ApiDefinition that both the client and the server side of the contract can share. Paths use OpenAPI-style {name} parameters — colon params and wildcards are rejected, and a declared parameter name must exactly match one in the path.
Typed client boundary
createClient(api, { baseUrl, ... }) turns an ApiDefinition into an object whose methods mirror the keys you passed to defineApi(), each one typed from that endpoint's params, query, headers, and body plus its registered response and error codecs. Call inputs and outputs come straight from the descriptor, so nothing about fetch, headers, or serialization needs to leak into calling code. The same descriptors also drive createFetch() for one-off calls that don't need a full client.
Schemas
@askrjs/schema builds executable schemas — schema.object(), schema.string(), schema.array(), and so on — that double as runtime validators and OpenAPI documentation. Any object with a safeParse(value) method satisfies the Validator contract that @askrjs/fetch's codecs and parameter specs expect, so you aren't locked into @askrjs/schema specifically to validate params, query values, headers, or bodies.
OpenAPI artifacts
@askrjs/server/openapi's createApi() attaches request and response schemas to routes as you register them, and api.toOpenApiDocument() produces a deterministic, deeply frozen OpenAPI 3.1.2 document from that registration. The askr CLI's askr openapi command writes that document to YAML on disk, and askr openapi --check compares it byte-for-byte without writing, which makes it a clean CI gate against undocumented drift.