API Routes
File-based HTTP endpoints for JSON, webhooks, resources, and agent-facing APIs
API routes are ordinary TypeScript or JavaScript modules under src/routes/api/.
They are the right place for HTTP endpoints: JSON APIs, webhooks, resource
downloads, upload handlers, health checks, and agent-facing tools.
Do not put API endpoints in middleware. Middleware is for cross-cutting request policy around routes; API route modules are for route-specific HTTP behavior.
File conventions
The default API root is src/routes/api, and the default URL prefix is /api.
Each .ts or .js file under that root becomes one endpoint.
src/routes/api/index.ts -> /api
src/routes/api/v1/health/index.ts -> /api/v1/health
src/routes/api/v1/status.ts -> /api/v1/status
src/routes/api/v1/platform/sites/[id]/files.ts -> /api/v1/platform/sites/:id/files
src/routes/api/v1/r2/[bucket]/[...key]/index.ts -> /api/v1/r2/:bucket/*
Rules:
- Only
.tsand.jsfiles are discovered as API routes. - File extensions are stripped from the URL.
- Any path segment named
indexis dropped. [id]creates a dynamic parameter.[...key]creates a catch-all parameter.- API routes do not use
.kozefiles, layouts, or page templates.
For example, src/routes/api/v1/filename.ts maps to /api/v1/filename.
A filename.txt file is not an API route module; it will be ignored by the
API route scanner.
Handler exports
Export one function per HTTP method. Supported method exports are GET, POST,
PUT, PATCH, DELETE, HEAD, and OPTIONS.
// src/routes/api/v1/health/index.ts
export function GET() {
return Response.json({ ok: true });
}
Handlers return a standard Response or a promise for one.
// src/routes/api/v1/items/[id].ts
import { params } from 'koze:request';
export async function GET() {
const item = await loadItem(params.id);
if (!item) return Response.json({ error: 'Not found' }, { status: 404 });
return Response.json(item);
}
export async function PATCH({ request }: { request: Request }) {
const body = await request.json();
const item = await updateItem(params.id, body);
return Response.json(item);
}
If a request uses a method the route does not export, Koze returns a method error for that route instead of falling through to middleware.
Request data
API routes can use the same server-side request modules as src/server/* code:
import { request, url, headers, params, locals } from 'koze:request';
import { cookies } from 'koze:cookies';
import { env } from 'cloudflare:workers';
Use these for route-local work:
// src/routes/api/v1/webhooks/stripe.ts
import { headers } from 'koze:request';
import { env } from 'cloudflare:workers';
export async function POST({ request }: { request: Request }) {
const signature = headers.get('stripe-signature');
const payload = await request.text();
await verifyAndHandleStripeEvent(payload, signature, env.STRIPE_WEBHOOK_SECRET);
return new Response(null, { status: 204 });
}
Use locals for data middleware has already attached, such as the current user
or tenant:
import { locals } from 'koze:request';
export function GET() {
const { user } = locals as App.Locals;
if (!user) return Response.json({ error: 'Unauthorized' }, { status: 401 });
return Response.json({ user });
}
Route metadata
API route modules may export a manifest object. Koze merges it with
framework metadata such as route kind, file, pattern, and exported methods.
Use this for documentation, agent discovery, or internal tooling.
export const manifest = {
summary: 'List platform sites',
auth: 'required',
tags: ['platform', 'sites'],
};
export async function GET() {
return Response.json(await listSites());
}
For Cloudflare API Shield, prefer a route-adjacent *.api-shield.ts sidecar
such as index.api-shield.ts. Koze uses that file to generate
_cloudflare/api-shield/openapi.json. Legacy static manifest fields still feed
the same artifact for compatibility. See API Shield
for schema validation setup.
Middleware boundary
Middleware should stay small and general:
- authenticate or attach request-scoped data to
ctx.locals - apply rate limits or security headers
- route a whole protocol or hostname to another handler
- log, trace, or transform responses
API routes should own endpoint behavior:
- validate request bodies
- choose response status codes and JSON shapes
- call database, KV, R2, Durable Object, or service modules
- implement webhooks, uploads, health checks, and agent-visible endpoints
This split keeps middleware readable and prevents large if pathname.startsWith
switchboards from becoming the application router.
Custom API root or prefix
The compiler defaults are equivalent to:
// vite.config.ts
koze({
api: {
root: 'src/routes/api',
urlPrefix: '/api',
},
});
Change these only when the project needs a different directory or public URL prefix. The same file rules still apply.
Read next
- Routing - page routing and layout conventions
- Middleware - cross-cutting request lifecycle hooks
- Request APIs - request context, cookies, and env