Litestore · Developer docs
API

Overview

Litestore's server answers calls through server actions, oRPC and REST endpoints, and each kind of caller uses exactly one of them.

Litestore's server answers calls through REST endpoints, oRPC and server actions, and each kind of caller uses exactly one of them. Read the sections below before you add an endpoint, so your code uses the transport its caller already speaks.

Request

GET/api/products?q=linen&limit=12
AccessNo key needed
NotesDescribed by /api/openapi.json
The same store answers REST calls without a key, oRPC calls with a scoped service key and MCP calls from AI assistants.

Nothing in this repository calls oRPC to write admin data, and oRPC has no admin write procedures.

Transports

Each transport has its own place in the repository. Pick the tab for the caller you are serving.

Server actions sit in the module whose data they write. The admin screens and storefront components in this repository call them.

modules/<area>/admin/actions.ts
modules/<area>/storefront/actions.ts

Server actions

Every admin and storefront write is a zsa action. Admin actions are built on the permission procedures in platform/permissions/procedures.ts, and permissionProcedure runs four checks before your handler does.

Session

The procedure reads the signed-in user's session.

Permission context

It works out which permissions the user holds.

Admin role check

If the user has no admin role, the request stops here.

Ban check

It checks once more whether the account is banned, with a fresh database read through assertAccountNotBanned.

The fourth check looks redundant, because the admin layout already stops a banned account from navigating. It is still needed. A server action is a direct POST that never renders the layout, so the procedure has to enforce the ban itself.

modulePermission(module)(action) then narrows the check to one <module>:<action> permission string:

modules/promotions/admin/actions.ts
const couponPermission = modulePermission(AdminModule.coupons)

export const deleteCoupons = couponPermission("delete")
  .createServerAction()
  .input(deleteCouponsSchema)
  .handler(async ({ input, ctx }) => {
    await releaseCouponsForDeletion(input.ids)
    await couponCrud.delete(input.ids, { user: ctx.user })
  })

Storefront actions use the matching procedure, customerProcedure in platform/permissions/customer.ts. It finds the signed-in customer and creates the Customer row when a logged-in user doesn't have one yet. It then puts ctx.customerId and ctx.userId on the context.

Both sides are zsa procedures, so admin and storefront actions return the same [data, error] tuple. Your calling code can handle a failure the same way without checking which half of the app it called.

When a module extends BaseCRUD, Litestore generates three actions from its declaration. The module exports the ones it uses, and you write create, update and anything that does more than save data by hand in the same file.

deleteAction
restoreAction
permanentDeleteAction

oRPC

The comment at the top of the router explains how narrow it is and why:

platform/api/router.ts
/**
 * Scope is deliberately narrow: oRPC is the EXTERNAL service-key API surface
 * (AI agents / integrations managing webhooks), plus a health ping. Admin and
 * storefront writes are zsa server actions in `modules/<area>/{admin,storefront}/actions.ts`
 * — the one transport story per consumer. A parallel oRPC admin write surface
 * was deleted: it had no client in this repo and is not part of the published
 * OpenAPI contract (app/api/openapi.json describes the REST endpoints instead).
 */

export const appRouter = {
  ping,
  web: webRouter,
}

webRouter in platform/api/storefront-router.ts has one entry, webhooks (from modules/integrations/webhooks/router.ts), with six procedures. Each one is built with serviceProcedureWithScope from platform/api/orpc.ts, which authenticates a ServiceApiKey through requireServiceKey() and then checks the key's scope.

ProcedureScope it needs
listread
getread
createwrite:webhooks
updatewrite:webhooks
deletewrite:webhooks
rotateSecretwrite:webhooks

A key without the right scope gets an ORPCError with FORBIDDEN. The valid scopes are listed in modules/team/api-keys/scopes.ts:

read
read:cart
write:cart
write:orders
write:webhooks
admin:mcp

The list only contains scopes that some request path checks. A scope that no request path checks would let an operator grant a permission that nothing enforces.

platform/api/orpc.ts also defines baseProcedure, which adds db and a revalidate helper to the context. The file has no session-auth or permission procedures, so it can't serve admin writes.

The route handler wraps the router in an oRPC RPCHandler with the prefix /api/rpc. It logs failures through the api-rpc-route logger and answers GET, POST, PUT, PATCH and DELETE. When no procedure matches, it returns a 404.

REST endpoints

app/api/openapi.json/route.ts returns an OpenAPI 3.0.3 document that we write by hand. It takes its title from config.site.name, and Litestore serves it with Cache-Control: public, max-age=3600.

The document describes eight paths under five tags. Six of them need no authentication.

PathTagWho can call it
GET /.well-known/ai-commerceDiscoveryAnyone
GET /api/capabilitiesDiscoveryAdmin session
GET /api/productsProductsAnyone
GET /api/products/feedProductsAnyone
GET /api/products/{id}ProductsAnyone
GET /api/recommendationsRecommendationsAnyone
POST /api/trackTrackingAnyone
POST /ai/v1/suggest-categoriesAIAdmin session (no longer exists, see below)

The only security scheme the document declares is sessionAuth, which is an apiKey sent in the session cookie.

/api/products accepts six query parameters:

ParameterWhat it does
pagePicks the page of results.
limitSets how many products come back.
categoryFilters by category.
collectionFilters by collection.
qSearches by text.
formatChooses json or jsonld.

/api/products/feed returns the whole catalog as a Schema.org ProductCollection, which Google Merchant Center and agents can read.

Excluded routes

We picked the spec's contents by hand for agents, and it doesn't list every route in the app. Litestore has 40 route handlers under app/api and two under app/ai/v1 (admin-chat and fill-product). The document still lists /ai/v1/suggest-categories, which no longer exists, and names neither of the two that do.

The spec leaves out the service-key REST endpoints. app/api/webhooks/route.ts authenticates through authenticateWebhookRequest(request, scope), yet neither the spec nor the oRPC router's scope comment mentions it.

It leaves out the MCP endpoints too. app/api/mcp/route.ts serves shoppers, and app/api/mcp/admin/route.ts serves the operator tool kit behind the admin:mcp scope.

The spec is maintained by hand

Nothing generates app/api/openapi.json from the route handlers or their zod schemas. The TrackEventRequest schema has the comment "Mirrors trackEventSchema in app/api/track/route.ts — keep in sync", and that comment is the only thing keeping them aligned. If a route changes shape or disappears, no check will fail.

Adding endpoints

Choose by who calls the endpoint, not by the shape of the data. Throughout the codebase, we follow the rule written in the router comment: one transport story per consumer.

On this page