The contract is generated, not declared. You never write an interface for your endpoint.
mercurjs codegen reads the route’s handler and validators and emits the Routes type the client consumes — so the panel call site breaks at compile time the moment the backend changes. This loop is also what makes Mercur projects reliable targets for AI agents: see Building with AI.What you’ll build
AGET /vendor/sales-summary endpoint returning the seller’s order count, called from a custom vendor portal page via client.vendor.salesSummary.query() with inferred types.
Build the loop
Create the route
API routes follow Medusa’s file conventions inside your API package. The URL path mirrors the directory path:Routes under
packages/api/src/api/vendor/sales-summary/route.ts
src/api/vendor/* run behind the vendor authentication middleware, so req.auth_context identifies the calling seller. Use src/api/admin/* for operator endpoints and src/api/store/* for public storefront endpoints.Regenerate the route map
Routes type that your panel apps already import:apps/vendor/src/lib/client.ts
client.vendor.salesSummary simply exists, typed.Call it from a panel page
Drop a page into the vendor app and call the endpoint through the client. Route segments map to camelCase properties, and the HTTP method is chosen by the terminal call:
query (GET), mutate (POST), delete (DELETE).apps/vendor/src/routes/sales-summary/page.tsx
InferClientOutput extracts the response type straight from the client method — change the route’s response shape, rerun codegen, and this component stops compiling until you update it.Verify
- Start the project (
bun run dev) and log into the vendor portal. - Sales summary appears in the sidebar (the
configexport registered it); the page shows the order count. curl http://localhost:9000/vendor/sales-summarywithout a token returns an authentication error — the vendor middleware guards your route.- Change the route to return
{ count: ... }instead of{ order_count: ... }, rerun codegen, and confirm the page fails to type-check — that’s the generated contract doing its job. Revert after.
FAQ
How do path parameters work in the client?
How do path parameters work in the client?
Use
$-prefixed segments: a route at src/api/vendor/things/[id]/route.ts is called as client.vendor.things.$id.query({ $id: "thing_123" }). The $id key is threaded into the URL path; everything else in the object becomes query params (GET) or the JSON body (POST).How do I handle errors from the client?
How do I handle errors from the client?
Failed requests throw
ClientError from @mercurjs/client, carrying status, statusText, and the backend’s message. Wrap calls in try/catch or let TanStack Query surface the error. Full reference: API Client.Where do request validation schemas go?
Where do request validation schemas go?
Follow Medusa conventions: a
validators.ts next to the route with a Zod schema, wired through the route’s middleware. Codegen reads validators too, so the client’s input type reflects them.Next steps
API Client
Everything the typed client can do — inputs, outputs, errors, React Query.
Extend a workflow
Put multi-step business logic behind your endpoint with rollback support.