# API Routes

> File-based HTTP endpoints for JSON, webhooks, resources, and agent-facing APIs

Package: Koze
Canonical: https://kuratchi.dev/docs/koze/api-routes
Markdown: https://kuratchi.dev/docs/koze/api-routes.md

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.

```text
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 `.ts` and `.js` files are discovered as API routes.
- File extensions are stripped from the URL.
- Any path segment named `index` is dropped.
- `[id]` creates a dynamic parameter.
- `[...key]` creates a catch-all parameter.
- API routes do not use `.koze` files, 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`.

```ts
// 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.

```ts
// 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:

```ts
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:

```ts
// 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:

```ts
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.

```ts
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](/docs/koze/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:

```ts
// 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](/docs/koze/routing) - page routing and layout conventions
- [Middleware](/docs/koze/middleware) - cross-cutting request lifecycle hooks
- [Request APIs](/docs/koze/request-apis) - request context, cookies, and env
