Prood
Architecture

API-Centric Architecture

Why Prood routes all commerce operations through apps/api and how callers authenticate.

Prood uses an API-centric architecture: all commerce reads and writes go through apps/api, and client applications use @prood/api-client.

Why an API layer?

ConcernDirect import (@prood/commerce)API-centric (apps/api)
AuthEach app implements tenant resolutionSingle resolveCaller() with JWT, API key, session, Host
ContractImplicit function signaturesOpenAPI 3.1 spec → typed client, MCP tools, Agent Auth
AgentsNot possible without custom wiringEvery operationId → agent capability
CachingPer-app cache tag managementAPI controls cache invalidation via revalidateTag
DeploymentApps need DB accessOnly API needs DATABASE_URL for commerce
TestingMock @prood/commerce in each appMock HTTP or test API routes directly

The storefront and dashboard are thin clients. They render UI and call HTTP endpoints. Business logic lives in @prood/commerce, invoked only by the API.

Request flow

Drag to pan · Scroll to zoom

When the storefront uses a BFF

Not every storefront request goes directly to the API. Local BFF routes exist where browser UX requires them:

BFF routeWhy not direct API?
/api/commerce/cart/*Cart ID stored in HTTP-only cookie; BFF reads cookie and forwards to API
/api/commerce/searchClient-side search palette needs a lightweight JSON endpoint
/api/commerce/countriesStatic-ish data for address forms; avoids exposing API URL to client
/api/auth/[...all] (storefront only)Better Auth handler must run on the storefront origin (*.prood.app / custom domains)

Dashboard does not run Better Auth handlers directly. Browser auth calls go to a same-origin BFF at /api/auth/*, which proxies to apps/api so session cookies work when the dashboard and API are on different hostnames (e.g. separate *.vercel.app deploys). With custom domains, AUTH_COOKIE_DOMAIN=.prood.com on the API project also shares cookies across dashboard.prood.com and api.prood.com.

Everything else (products, categories, store info, orders) is fetched server-side in React Server Components via @prood/api-client.

API client configuration

Both storefront and dashboard create an API client that forwards authentication context:

// apps/storefront/lib/commerce-api.ts
import { createCommerceApiClient } from '@prood/api-client'

export function getCommerceApi() {
  return createCommerceApiClient({
    baseUrl: process.env.COMMERCE_API_URL!,
    headers: async () => {
      const h = await headers()
      return {
        cookie: h.get('cookie') ?? '',
        host: h.get('host') ?? '',
      }
    },
  })
}

The API uses the Host header for tenant resolution on storefront-scoped routes and the session cookie for dashboard admin routes.

Scopes

The API enforces two scopes:

ScopeAccess
storefrontCatalog reads, cart CRUD, place order, customer orders
adminFull CRUD — products, orders, customers, store settings, inventory, dashboard stats

Route handlers call requireCaller('storefront') or requireCaller('admin') to enforce scope.

OpenAPI as the contract

The API generates OpenAPI 3.1 from Zod schemas in apps/api/lib/schemas.ts:

Drag to pan · Scroll to zoom

The docs site syncs this spec at build time (pnpm sync:openapi) and renders it via Fumadocs OpenAPI at /docs/api/*.

@prood/api-client is generated from the same spec, ensuring type safety between server and client.

MCP and Agent Auth

The API also exposes:

SurfacePathPurpose
MCP server/mcpModel Context Protocol tools mirroring REST endpoints
Agent Auth/api/auth/* + /.well-known/agent-configurationAI agent registration, capability grants, JWT access
API keysx-api-key headerProgrammatic access with org + scope metadata

See Agent Auth and MCP server.

Dashboard exception

The dashboard uses @prood/commerce directly for two operations that do not go through the REST API:

  1. Integration config — encrypt/decrypt provider credentials in integration_config
  2. Domain management — CRUD on tenant_domain table

All product, order, and customer management goes through admin-api.ts → API /v1/admin/*.

Using commerce from apps

PatternRecommended approach
import { getProducts } from '@prood/commerce' in an appgetCommerceApi().GET('/products')
Client-side composable for catalog dataServer component + commerce-data.ts fetchers
createCommerce({ adapter })Only in apps/api/lib/commerce-service.ts
Custom commerce endpointsExplicit routes in apps/api/app/v1/

On this page