> ## Documentation Index
> Fetch the complete documentation index at: https://confect.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# HTTP API

> Define HTTP endpoints using Effect's HTTP modules.

Confect's HTTP integration mounts an Effect HTTP router onto Convex: you register routes with Effect's `effect/unstable/http` and `effect/unstable/httpapi` modules — `HttpApi` endpoints, plain routes, interactive [Scalar](https://github.com/scalar/scalar) docs, middleware — and Confect serves them from a single Convex HTTP action, supplying its [services](/v10/concepts/services) to your handlers on every request.

## Defining an API

Define API groups and endpoints using `effect/unstable/httpapi`, implement the endpoint handlers with `HttpApiBuilder.group`, and export a `layer` that registers the API's routes. Handlers have access to the Confect [services](/v10/concepts/services) available in the HTTP action context.

Endpoint paths are absolute; place an API under a path prefix with `.prefix(...)`.

```ts confect/http/NotesApi.ts theme={null}
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Schema from "effect/Schema";
import * as HttpApi from "effect/unstable/httpapi/HttpApi";
import * as HttpApiBuilder from "effect/unstable/httpapi/HttpApiBuilder";
import * as HttpApiEndpoint from "effect/unstable/httpapi/HttpApiEndpoint";
import * as HttpApiGroup from "effect/unstable/httpapi/HttpApiGroup";
import * as OpenApi from "effect/unstable/httpapi/OpenApi";
import refs from "../_generated/refs";
import { QueryRunner } from "../_generated/services";
import notes from "../_generated/tables/notes";

class ApiGroup extends HttpApiGroup.make("notes")
  .add(
    HttpApiEndpoint.get("getFirst", "/get-first", {
      success: Schema.OptionFromNullOr(notes.Doc),
    }).annotate(OpenApi.Description, "Get the first note, if there is one."),
  )
  .annotate(OpenApi.Title, "Notes")
  .annotate(OpenApi.Description, "Operations on notes.") {}

export class Api extends HttpApi.make("Api")
  .annotate(OpenApi.Title, "Confect Example")
  .add(ApiGroup)
  .prefix("/path-prefix") {}

const ApiLive = HttpApiBuilder.group(Api, "notes", (handlers) =>
  handlers.handle("getFirst", () =>
    Effect.gen(function* () {
      const runQuery = yield* QueryRunner;

      const firstNote = yield* runQuery(
        refs.public.notes_and_random.notes.getFirst,
        {},
      );

      return firstNote;
    }).pipe(Effect.orDie),
  ),
);

/**
 * Registers {@link Api}'s routes, with its group handlers provided.
 */
export const layer = HttpApiBuilder.layer(Api).pipe(Layer.provide(ApiLive));
```

`HttpApiBuilder.layer(Api)` registers the API's routes; `Layer.provide(ApiLive)` supplies its group handlers. Forgetting a group is a **compile-time error** — the unprovided group requirement is rejected where the layer is mounted.

## API documentation

`HttpApiScalar.layer` serves interactive docs, powered by Scalar and generated from your endpoint definitions and `OpenApi` annotations. Set `baseServerURL` to your deployment's [site URL](https://docs.convex.dev/production/environment-variables#system-environment-variables) so the docs page's "try it" requests target the right host — Confect installs a Convex-aware `ConfigProvider`, so `Config` reads (in layer construction, route handlers, and middleware alike) resolve against your [Convex environment variables](https://docs.convex.dev/production/environment-variables).

```ts confect/http/ScalarDocs.ts theme={null}
import * as Config from "effect/Config";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as HttpApiScalar from "effect/unstable/httpapi/HttpApiScalar";
import { Api } from "./NotesApi";

/**
 * Serves interactive API documentation for {@link Api}, powered by
 * [Scalar](https://github.com/scalar/scalar).
 */
export const layer = Layer.unwrap(
  Effect.gen(function* () {
    const siteUrl = yield* Effect.orDie(Config.string("CONVEX_SITE_URL"));

    return HttpApiScalar.layer(Api, {
      path: "/path-prefix/docs",
      scalar: { baseServerURL: siteUrl },
    });
  }),
);
```

## Creating the router

Create the Convex HTTP router in `confect/http.ts` with `HttpRouter.make` from `@confect/server`. It takes a single `Layer` that registers your routes — merge your API and docs layers with any other building blocks from Effect's `effect/unstable/http`:

* `HttpRouter.add(method, path, handler)` registers a plain route.
* `HttpRouter.middleware(fn, { global: true })` applies middleware to every route.

Confect's `HttpRouter` module collides with Effect's, so alias it on import:

```ts confect/http.ts theme={null}
import { HttpRouter as ConfectHttpRouter } from "@confect/server";
import { flow } from "effect/Function";
import * as Layer from "effect/Layer";
import * as HttpMiddleware from "effect/unstable/http/HttpMiddleware";
import * as HttpRouter from "effect/unstable/http/HttpRouter";
import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
import * as NotesApi from "./http/NotesApi";
import * as ScalarDocs from "./http/ScalarDocs";

export default ConfectHttpRouter.make(
  Layer.mergeAll(
    NotesApi.layer,
    ScalarDocs.layer,
    HttpRouter.add("GET", "/health", HttpServerResponse.text("OK")),
    HttpRouter.middleware(flow(HttpMiddleware.cors(), HttpMiddleware.logger), {
      global: true,
    }),
  ),
);
```

## Routing semantics

`HttpRouter.make` registers a single catch-all Convex HTTP action under the path prefix `/`, so the Effect router is the single source of truth for paths:

* Any number of `HttpApi` definitions, docs pages, and plain routes can be merged onto the one router.
* Requests that match no Effect route receive the Effect router's 404 response.
* Plain Convex routes still work: add them to the returned router with `.route(...)`. Convex matches exact paths first and longer path prefixes before the catch-all, so they take precedence over the Effect router.

```ts confect/http.ts (continued) theme={null}
import { httpActionGeneric } from "convex/server";

const http = ConfectHttpRouter.make(/* ... */);

http.route({
  path: "/plain-convex",
  method: "GET",
  handler: httpActionGeneric(() => Promise.resolve(new Response("ok"))),
});

export default http;
```
