# API-centric architecture (https://prood.com/docs/architecture/api-centric)

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? [#why-an-api-layer]

| Concern | Direct import (`@prood/commerce`) | API-centric (`apps/api`) |
| --- | --- | --- |
| **Auth** | Each app implements tenant resolution | Single `resolveCaller()` with JWT, API key, session, Host |
| **Contract** | Implicit function signatures | OpenAPI 3.1 spec → typed client, MCP tools, Agent Auth |
| **Agents** | Not possible without custom wiring | Every `operationId` → agent capability |
| **Caching** | Per-app cache tag management | API controls cache invalidation via `revalidateTag` |
| **Deployment** | Apps need DB access | Only API needs `DATABASE_URL` for commerce |
| **Testing** | Mock `@prood/commerce` in each app | Mock 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 [#request-flow]

```mermaid
flowchart TB
  CLIENT["Browser / Agent"]

  BFF["Storefront BFF<br/>/api/commerce/cart/*"]
  DASH["Dashboard server action<br/>admin-api.ts"]
  DIRECT["Direct API call<br/>curl / fetch"]
  MCP["MCP / Agent Auth<br/>/mcp or Bearer JWT"]

  RESOLVE["resolve-caller.ts"]
  JWT["Bearer JWT → org from agent metadata"]
  KEY["x-api-key → org + scopes"]
  SESS["Session cookie → activeOrganizationId"]
  HOST["x-storefront-host → tenant lookup"]

  SVC["commerce-service.ts"]
  PLATFORM["@prood/commerce.withTenant(orgId)<br/>→ @prood/platform"]

  CLIENT --> BFF --> RESOLVE
  CLIENT --> DASH --> RESOLVE
  CLIENT --> DIRECT --> RESOLVE
  CLIENT --> MCP --> RESOLVE

  RESOLVE --> JWT
  RESOLVE --> KEY
  RESOLVE --> SESS
  RESOLVE --> HOST
  RESOLVE --> SVC --> PLATFORM
```

## When the storefront uses a BFF [#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 route | Why not direct API? |
| --- | --- |
| `/api/commerce/cart/*` | Cart ID stored in HTTP-only cookie; BFF reads cookie and forwards to API |
| `/api/commerce/search` | Client-side search palette needs a lightweight JSON endpoint |
| `/api/commerce/countries` | Static-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 `/dashboard/api/auth/*`, which proxies to **apps/api** from the server, so the session cookie is set for prood.com whichever Vercel project answered. The dashboard, the admin console and the API share that one origin, so one session is valid at all three. `AUTH_COOKIE_DOMAIN` stays unset.

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

## API client configuration [#api-client-configuration]

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

```ts
// 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 `x-storefront-host` header for tenant resolution on storefront-scoped routes and the session cookie for dashboard admin routes.

## Scopes [#scopes]

The API enforces three scopes:

| Scope | Access |
| --- | --- |
| `storefront` | Catalog reads, cart CRUD, place order, customer orders |
| `staff` | Read admin data, for every member of a store |
| `admin` | Reads and changes catalog, inventory, orders and store settings, and reads customers. Payment accounts, domains, team and keys stay in the dashboard. Starts on Grow. |

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

## OpenAPI as the contract [#openapi-as-the-contract]

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

```mermaid
flowchart LR
  SPEC["GET /v1/openapi.json<br/>full spec"]
  PROD["GET /v1/products<br/>listProducts"]
  ADMIN["POST /v1/admin/products<br/>adminCreateProduct"]
  SPEC --> PROD
  SPEC --> ADMIN
```

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 [#mcp-and-agent-auth]

The API also exposes:

| Surface | Path | Purpose |
| --- | --- | --- |
| **MCP server** | `/mcp` | Model Context Protocol tools mirroring REST endpoints |
| **Agent Auth** | `/api/auth/*` + `/.well-known/agent-configuration` | AI agent registration, capability grants, JWT access |
| **API keys** | `x-api-key` header | Programmatic access with org + scope metadata |

See [Agent Auth](/docs/apps/api/agent-auth) and [MCP server](/docs/apps/api/mcp).

## Dashboard exception [#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 [#using-commerce-from-apps]

| Pattern | Recommended approach |
| --- | --- |
| `import { getProducts } from '@prood/commerce'` in an app | `getCommerceApi().GET('/products')` |
| Client-side composable for catalog data | Server component + `commerce-data.ts` fetchers |
| `createCommerce({ adapter })` | Only in `apps/api/lib/commerce-service.ts` |
| Custom commerce endpoints | Explicit routes in `apps/api/app/v1/` |

## Related pages [#related-pages]

<Cards>
  <Card title="Commerce API" href="/docs/apps/api" description="REST endpoints, routes, and service layer." />
  <Card title="Authentication" href="/docs/apps/api/authentication" description="Caller resolution and scopes." />
  <Card title="@prood/api-client" href="/docs/packages/api-client" description="Typed HTTP client." />
  <Card title="OpenAPI reference" href="/docs/api" description="Interactive API documentation." />
</Cards>
