REST
Learn which REST routes under app/api are published in the OpenAPI contract, which are internal, and how each one is authenticated.
Route handlers under app/api are the part of Litestore that is not a server action and not an oRPC procedure. They exist for callers that cannot use either: a payment provider posting a webhook, a browser uploading a file, an AI agent reading the catalog.
A subset of those routes is described by a published OpenAPI document served from app/api/openapi.json. That document is aimed at AI agents and outside integrations, and it is deliberately smaller than the route tree.
The distinction matters. Living under app/api does not make a route public. The contract is the list of routes an outside caller is invited to depend on; everything else is internal plumbing that happens to be reachable over HTTP.
The published contract
app/api/openapi.json/route.ts builds the spec inside the GET handler rather than serving a static file.
That is so the servers entry carries the deployment's own base URL, read from env.NEXT_PUBLIC_SITE_URL. A static document would have to hardcode a host and would be wrong on every environment except one.
The document declares itself as the AI Commerce API:
const spec = {
openapi: "3.0.3",
info: {
title: `${config.site.name} AI Commerce API`,
version: "1.0.0",
},
servers: [{ url: baseUrl, description: "Production server" }],
tags: [
{ name: "Discovery", description: "API discovery and capabilities" },
{ name: "Products", description: "Product catalog operations" },
{ name: "Recommendations", description: "Product recommendations" },
{ name: "Tracking", description: "Event tracking" },
{ name: "AI", description: "AI-powered content generation" },
],
// …
}The five tags are the whole of the advertised surface:
- Discovery — API discovery and capabilities
- Products — product catalog operations
- Recommendations — product recommendations
- Tracking — event tracking
- AI — AI-powered content generation
The paths the document currently describes are:
/.well-known/ai-commerce
/api/capabilities
/api/products
/api/products/feed
/api/products/{id}
/api/recommendations
/api/track
/ai/v1/suggest-categoriesThe spec is hand-written
The document is a literal in the route handler, not generated from the handlers it describes. Its own comment asking that it be kept in sync is the only thing enforcing that, so a new endpoint does not appear here until someone adds it.
Discovery
Discovery is deliberate. An agent should be able to arrive at the domain and work out the surface for itself, rather than being handed a URL by a human.
/.well-known/ai-commerce advertises the endpoints, the capabilities and the auth requirements. It is the entry point an agent is expected to try first, and it points onward to the OpenAPI document.
/api/capabilities is the detailed version. It reports platform readiness, the configured AI providers, the enabled features and the currencies.
/api/capabilities requires an authenticated admin session. It is listed under Discovery in the spec, but it is the one discovery route that is not open — it describes how the store is configured, which is operator information rather than shopper information.
Route groups
The route tree divides into six groups. They differ mainly in who is allowed to call them and what proves it.
Commerce for agents
products
recommendations
cart
trackAuthenticated by a service key, or read publicly. These are the routes the OpenAPI document is written for.
Discovery
.well-known/ai-commerce
capabilities
openapi.jsonPublic, with the exception of capabilities, which needs an admin session.
MCP
mcp
mcp/adminAuthenticated by a Bearer service key. The admin route additionally requires the admin:mcp scope.
Inbound webhooks
webhooks/stripe
webhooks/resendAuthenticated by provider signature verification. There is no Litestore credential involved — the caller is Stripe or Resend, and the proof is the signature they sign the body with.
Platform
inngest
health
upload
media
locale
unsubscribeAuth varies per route, so check the individual handler rather than assuming a group rule. These are the routes that exist because the platform needs an HTTP endpoint, not because an integrator asked for one.
Admin and AI
admin
aiAuthenticated by an admin session.
Surfaces that sit outside the contract
The MCP routes and the inbound provider webhooks are in neither the OpenAPI contract nor the oRPC router.
That is intentional rather than an omission. Each of them is its own surface with its own auth, and folding them into a shared contract would misdescribe them.
A webhook route is not an API an integrator calls. It is an endpoint a specific provider posts to, verified by that provider's signature scheme, and its shape is dictated by the provider.
The MCP routes speak JSON-RPC 2.0 rather than REST, and an MCP client discovers them through tools/list rather than through an OpenAPI document. Describing them in an OpenAPI spec would produce a document no MCP client reads.
Where a new route belongs
Use a server action when the caller is Litestore's own admin or storefront UI.
Use an oRPC procedure when the caller is a service-key consumer that fits the existing router.
Use a REST route handler when the caller cannot use either — a provider posting a signed webhook, a browser doing a multipart upload, an agent reading the catalog.
If the new route is one that outside integrators are meant to depend on, adding the handler is only half the work. The hand-written spec does not notice it:
A route is part of the published contract when someone adds it to app/api/openapi.json/route.ts, and not before.
Related
Overview
Learn which of Litestore's three transports to use — zsa server actions for admin and storefront writes, oRPC at /api/rpc for service-key consumers, and the REST endpoints described by /api/openapi.json.
MCP
Learn how to connect AI assistants to your store over MCP — eight public shopper tools at /api/mcp, and the admin's gated tool kit at /api/mcp/admin behind an admin:mcp key.
Webhooks
Learn how outbound deliveries are signed and retried, when an endpoint is auto-disabled, and how the inbound Stripe and Resend routes differ.