Litestore · Developer docs
API

oRPC

Call the oRPC endpoint at /api/rpc with a service key to manage webhooks.

Litestore mounts oRPC at /api/rpc, and outside services call it with a service key. Use it when your integration needs to manage webhooks or check that the store is up.

Request

GET/api/products?q=linen&limit=12
AccessNo key needed
NotesDescribed by /api/openapi.json
An integration calls /api/rpc with a scoped service key, while the admin and storefront use server actions.

oRPC offers a router for managing webhooks and a health ping. Admin and storefront writes don't go through oRPC.

Each kind of caller has one way in. An integration with a service key uses oRPC. The admin and the storefront use zsa server actions. The published contract in app/api/openapi.json describes the REST endpoints. When the same operation exists in two transports, the two versions can drift apart.

The comment at the top of the router file states this boundary:

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,
}

Procedures

webRouter in platform/api/storefront-router.ts has exactly one router today, webhooks from modules/integrations/webhooks/router.ts.

ProcedureScope requiredWhat it does
web.webhooks.listreadLists webhooks with id, url, events, isActive, name and createdAt
web.webhooks.getreadReturns one webhook by id, or null
web.webhooks.createwrite:webhooksCreates a webhook and returns its secret once
web.webhooks.updatewrite:webhooksUpdates the url, events and name
web.webhooks.deletewrite:webhooksRemoves the webhook
web.webhooks.rotateSecretwrite:webhooksIssues a new secret and returns it once
pingnoneReturns { status: "ok", timestamp }

Save the secret from the response

Create and rotate return the signing secret in the response body, because the secret isn't readable later. If your code throws away the create response, it loses the secret for good and has to rotate to get a new one.

Procedure builders

platform/api/orpc.ts stacks three procedures, and each one builds on the one before.

baseProcedure

It puts db and a revalidate helper on the context.

serviceProcedure

It looks up the service key and turns an AppError into an ORPCError.

serviceProcedureWithScope

It does nothing except check the scope:

platform/api/orpc.ts
export function serviceProcedureWithScope(scope: ApiScope) {
  return serviceProcedure.use(async ({ next, context }) => {
    if (!hasScope(context.apiKey.scopes, scope)) {
      throw new ORPCError("FORBIDDEN", {
        message: `Requires ${scope} scope`,
      })
    }
    return next()
  })
}

Because the layers stack, a procedure can't get a database handle until Litestore has resolved the key. Every procedure is authenticated, and none can opt out.

Admin access

oRPC has no session-auth or permission procedures and no admin router, so no admin write is reachable through it. The procedures that would accept an admin session don't exist in platform/api/orpc.ts.

One webhook lifecycle

The oRPC router and the admin's Settings → Developer screen both call the same webhook lifecycle in modules/integrations/webhooks/lifecycle.ts, so a webhook created over oRPC and one created in the admin go through the same code.

Transports

Use oRPC when an outside integration with a service key needs to manage webhooks or run a health check. Those are the only jobs it covers.

Before you add a procedure here, don't ask whether it would be convenient over RPC. Ask this instead: does this operation belong to a service key, and does the published contract not already cover it?

On this page