# API Usage Map

> **Verified accurate:** 2026-05-02 — boundary architecture, two call patterns (`defineResource` / `defineRoute`), `_call` seam, MOCK_MODE flag, hooks (`useApi`, `useApiMutation`, `useMutation`), and the canonical envelope `{ data, meta?, error? }` all hold against `front/common/api.js` (426 lines). **Counts have drifted:** see corrected numbers below.
> **Status:** active source-of-truth doc; the canonical contract for the API boundary. Per-endpoint per-batch surface lives in the SCREENS-INVENTORY-* docs and the BACKEND-MAPPING catalog.

**Audience:** integration engineers wiring the Dalseen front-end to a real backend.
**Companion docs:** `MODULE-MAP.md` (file/module geography), `DESIGN-SYSTEM.md` (token reference), `INTEGRATION-NOTES.md` (cut-over plan), `SCREENS-INVENTORY.md` & `USER-FLOWS.md` (consumer detail).

---

## TL;DR

The front-end already talks to a single boundary — `window.API` — that mimics a REST backend over `fetch`. In production today, that boundary is fronted by an in-process **mock router** (`front/common/api.js` + `front/common/api-seeds.js`) that resolves requests against `localStorage`-backed seed collections. Flipping `window.API.config.MOCK_MODE = false` (and pointing `API_BASE_URL` at a real host) is the one-line cut-over: every consumer goes through `_call(method, path, payload, opts)`, which already speaks the canonical envelope `{ data, meta?, error? }` and already serialises an `Idempotency-Key` header on writes.

There is **one** API surface and **two** call patterns:

1. **Auto-CRUD** via `defineResource(key, seed)` → exposes `list / get / create / update / patch / replace / remove` on `window.API.<ns>.<resource>`.
2. **Custom routes** via `defineRoute(method, path, fn)` → exposes a named action on `window.API.<ns>.<action>` (e.g. `API.owner.preview` for `POST /owner/payrollRuns/:id/preview`).

Consumers read with `window.useApi(() => API.x.y(), [deps])` (loading/error/refetch state) and write with `window.useMutation(fn)` (in-flight + last-error state). Both helpers are defined in `front/api/api-hooks.jsx`.

**Counts (verified 2026-05-02 against `front/common/api-seeds.js`, 757 lines):**
- 7 namespaces in `api-seeds.js` (`acct`, `dine`, `owner`, `pay`, `platform`, `retail`, `shared`) plus a small `auth/me` shim registered elsewhere
- **70** auto-CRUD resources via `D = defineResource` (each = 7 verbs ⇒ 490 implicit endpoints)
- **21** custom routes via `R = defineRoute` (named actions)
- **91** explicit registrations total in `front/common/api-seeds.js`

**Original write-up counts (60 / 31 / 91) are stale** — the resource:route mix shifted as some custom routes got promoted to full resources during batch work. Total registration count is unchanged.

---

## 1 · The boundary in one diagram

```
┌──────────────────── consumer screens ──────────────────────┐
│  React components                                          │
│   useApi(() => API.owner.dashboard())          ← reads     │
│   useMutation((b) => API.sales.create(b))      ← writes    │
└──────────────────────────┬─────────────────────────────────┘
                           │  thin façade — no business logic
                           ▼
┌─────────── window.API.<ns>.<resource | action>(...) ───────┐
│   Built by defineResource() + defineRoute()                │
│   All methods resolve to ↓                                 │
└──────────────────────────┬─────────────────────────────────┘
                           ▼
                  window.API._call(method, path, payload, opts)
                  ─────────────────────────────────────────────
                  ▲                                           ▲
                  │ MOCK_MODE = true (default today)          │ MOCK_MODE = false (cut-over)
                  ▼                                           ▼
        ┌────── mock router ──────┐               ┌──── fetch(API_BASE_URL+path) ────┐
        │  RESOURCES / CUSTOM /   │               │  Same headers, same envelope,   │
        │  CUSTOM_PATTERNS maps,  │               │  same idempotency-key contract. │
        │  delay() + maybeFail(), │               │  Real backend.                  │
        │  localStorage store.    │               └─────────────────────────────────┘
        └─────────────────────────┘
```

The vertical line through `_call` is the **single integration seam**. Nothing above it cares whether the bottom half is mock or real. Nothing below it is referenced by any consumer.

**Implementation pointers:**
- Façade & seam: `front/common/api.js` (lines 194–230 = `_call`, 324–360 = `defineResource`/`defineRoute`).
- Seed registry: `front/common/api-seeds.js` (757 lines, 91 calls).
- Hooks: `front/api/api-hooks.jsx` (`useApi`, `useApiMutation`, `useMutation`).
- Per-namespace hardening (auth header, session refresh): `front/api/api-session.js`, `front/api/api-fetch.js`, `front/api/api-foundation.js`, `front/api/api-namespace.js`.
- Standard UI states: `front/api/api-states.jsx` (`<EmptyState>`, `<LoadingState>`, `<ErrorState>` — five canonical phases idle/loading/empty/error/data).

---

## 2 · The wire contract

The consumer code already assumes this contract; the real backend must conform.

### 2.1 Request

```
<METHOD> <API_BASE_URL><path>
Headers:
  Content-Type:    application/json
  Accept:          application/json
  Authorization:   Bearer <session.token>           (added by buildHeaders)
  X-Tenant-Id:     <session.tenantId>               (multi-tenant routing)
  X-Locale:        en | ar
  X-Branch-Id:     <session.activeBranchId?>        (when scoped)
  Idempotency-Key: <opts.idempotencyKey>            (only for POST/PATCH/PUT/DELETE
                                                     when caller provided it; the
                                                     mock layer auto-generates one
                                                     per write if absent.)
Body: JSON for POST/PATCH/PUT/DELETE; query string for GET (URLSearchParams).
```

### 2.2 Response envelope (success)

```jsonc
// Single resource
{ "data": { "id": "...", "...": "..." } }

// Collection
{ "data": [ { "id": "..." }, ... ],
  "meta": { "total": 1234, "limit": 50, "offset": 0 } }
```

The mock returns the same shape today: `_call` strips and returns `data` (so consumers see the bare row/array). When a list is returned, the array carries a `meta` property (`Object.assign(rows, { meta })`) — both the explicit `data: [..]` envelope and the array-with-`.meta` shape must work after cut-over.

### 2.3 Response envelope (error)

```jsonc
{ "error": {
    "code":       "VALIDATION_FAILED",       // machine code, stable
    "message_en": "Salary must be positive",
    "message_ar": "يجب أن يكون الراتب أكبر من صفر",
    "fields":     { "salary": "min(1)" }     // optional, per-field detail
} }
```

`_call` throws an `ApiError` with `{ code, status, message_en, message_ar, fields }`. Consumers branch on `code` (never on the human message). Known codes used in the FE today:

| Code                      | HTTP | Where used                                                     |
|---------------------------|------|----------------------------------------------------------------|
| `VALIDATION_FAILED`       | 422  | All create/update — fields surface inline under inputs         |
| `NOT_FOUND`               | 404  | Auto-CRUD `get/patch/put/delete` of unknown id                 |
| `BAD_PATH`                | 400  | Mock-only sanity check; treat as 400                           |
| `NO_RESOURCE`             | 404  | Mock-only ("no resource registered"); treat as 404             |
| `METHOD_NOT_ALLOWED`      | 405  | Wrong verb on auto-CRUD                                        |
| `HTTP_ERROR`              | any  | Fallback when backend returns unparseable JSON                 |
| `INSUFFICIENT_FUNDS`      | 422  | `POST /owner/payrollRuns/:id/commit` (WPS pre-flight)          |
| `IDEMPOTENT_REPLAY`       | 409  | Server-side replay detection (consumers retry & accept)        |
| `PERMISSION_DENIED`       | 403  | Routes guarded by `session.hasPermission(...)`                 |

### 2.4 Idempotency

Every write **MUST** be idempotent on `Idempotency-Key`. The FE generates one of three ways:

1. Explicit, deterministic — collapses double-clicks: `\`sub-change-${tenantId}-${Date.now() >>> 16}\``.
2. Explicit, content-derived — for ticket replies: `\`tkt-reply-${id}-${body.slice(0,8)}-${Date.now() >>> 16}\``.
3. Auto-generated by `buildHeaders()` when caller passes nothing — ULID per call.

Servers must store `(tenantId, idempotencyKey) → response` for ≥24h and replay on collision (return the original 2xx body, not 409). The FE treats a 409 with code `IDEMPOTENT_REPLAY` as success.

### 2.5 Pagination

GET on a collection accepts `limit` + `offset` (int) and optional `q` (free-text), `branchId`, `tenantId`. Response includes `meta.total / meta.limit / meta.offset`. No cursor pagination today — keep offset/limit on day one, layer cursors later if needed.

### 2.6 Auth

`window.API.session.get()` returns `{ tenantId, userId, token, role, permissions[], activeBranchId, locale }`. `setSession(...)` is called from the sign-in flow. `hasPermission(perm)` is checked client-side for UI gating, but **every server route must re-check** — the FE permission check is for affordance only.

---

## 3 · Namespace catalogue

Every consumer call sits under `window.API.<ns>.<x>`. Below: each namespace, what it owns, and whether it's wired (✅ used in screens) or registered-but-unused (⚠ seeded so the mock has data, no consumer yet — fine to ship without).

| Namespace   | Purpose                                                                          | Status |
|-------------|----------------------------------------------------------------------------------|--------|
| `owner`     | Owner dashboard, approvals, HR (staff/leave/payroll/attendance/…), legal docs    | ✅      |
| `retail`    | POS, catalogue, customers, suppliers, branches, sales, PO/receiving/transfer, shifts, stocktakes | ✅ |
| `pay`       | Terminal fleet, payment methods, settlements, payouts, disputes, SoftPOS         | ✅      |
| `dine`      | Tables, KDS tickets, menu, recipes, reservations, deliveries, waiters, reports   | ✅      |
| `acct`      | Customers, vendors, invoices, bills, bank txns, COA, VAT periods, journals       | ✅      |
| `shared`    | Cross-module: AI threads/skills, BI reports/dashboards, CRM, manufacturing       | ✅      |
| `platform`  | Tenant management, subscriptions, platform-side invoices, plans, services        | ✅      |
| `auth/me`   | Sign-in / session refresh / current-user (defined in `front/api/api-session.js`) | ✅      |

> All eight namespaces share one `_call` and one envelope. There is no per-namespace HTTP layer to special-case.

---

## 4 · Endpoint catalogue

Below: the complete list as registered in `front/common/api-seeds.js`. **Auto-CRUD resources** show only the resource key — each yields the seven verbs from §5. **Custom routes** are listed verbatim.

### 4.1 `owner` — Owner & HR

**Auto-CRUD resources** (`D('owner.<x>', …)`):
- `transactions`, `scheduledPayments`, `banks`
- `invoices`, `paymentMethods` *(billing snapshot for owner-side display)*
- `tickets`, `notifications`, `tasks`
- `staff`, `candidates`, `courses`, `policies`, `expenses`, `loans`, `assets`, `contracts`, `goals`, `kudos`
- `timesheets`, `leaveRequests`, `leaveTypes`, `payrollRuns`, `attendance`

**Custom routes:**
| Method | Path                                                       | Purpose                                                |
|--------|------------------------------------------------------------|--------------------------------------------------------|
| GET    | `/owner/dashboard`                                         | Aggregated home/console payload (KPIs + lists)         |
| POST   | `/owner/leaveRequests/:id/approve`                         | HR leave approval                                      |
| POST   | `/owner/leaveRequests/:id/reject`                          | HR leave rejection (body: `{ reason }`)                |
| POST   | `/owner/staff`                                             | Hire — overrides auto-CRUD with validation             |
| POST   | `/owner/payrollRuns/:id/preview`                           | Compute payroll run (no commit)                        |
| POST   | `/owner/payrollRuns/:id/approve`                           | Approve a previewed run                                |
| POST   | `/owner/payrollRuns/:id/commit`                            | Submit WPS file; can throw `INSUFFICIENT_FUNDS`        |
| POST   | `/owner/payrollRuns/:id/_simulateInsufficientFunds`        | Test-only toggle (gate behind feature flag in prod)    |
| GET    | `/owner/subscription`                                      | Current plan + add-ons                                 |
| POST   | `/owner/subscription/change`                               | Plan change (idempotency-keyed)                        |
| GET    | `/owner/invoices/:id/pdf`                                  | Returns `{ url }` — signed PDF URL                     |
| POST   | `/owner/invoices/:id/pay`                                  | Pay an invoice (`{ paymentMethodId }`, idempotency-keyed) |
| GET    | `/owner/support/tier`                                      | Current support entitlement                            |
| GET    | `/owner/tickets`                                           | List tickets (with filters)                            |
| GET    | `/owner/tickets/:id`                                       | Ticket detail with thread                              |
| POST   | `/owner/tickets`                                           | Open new ticket (idempotency-keyed)                    |
| POST   | `/owner/tickets/:id/reply`                                 | Add reply (idempotency-keyed)                          |
| POST   | `/owner/tickets/:id/close`                                 | Close ticket (idempotency-keyed)                       |

**Consumers (top hits):**
- `front/owner/owner-live.jsx` — wraps `OwnerHome`, `OwnerConsole`, `OwnerApprovals`, `OwnerInvoices` around `useApi(API.owner.dashboard)` / `approvals.list`.
- `front/owner/hr-leave-v2.compiled.js` — leave list/approve/reject UI.
- `front/owner/hr-payroll-v2.compiled.js` — preview/approve/commit cycle, including the insufficient-funds simulation.
- `front/owner/hr-roster-v2.compiled.js` — staff hiring form via `API.owner.staff.create`.
- `front/owner/owner-flows-goals-legal.compiled.js` — goals & legal docs (currently fixture-backed; switch to `API.owner.goals.list` after cut-over per the inline TODO).

### 4.2 `retail` — POS, inventory, purchasing

**Auto-CRUD resources:**
- `products`, `customers`, `suppliers`, `orders`, `branches`
- `sales`, `purchaseOrders`, `receivings`, `transfers`, `stocktakes`, `shifts`

**Custom routes:**
| Method | Path                              | Purpose                                                  |
|--------|-----------------------------------|----------------------------------------------------------|
| GET    | `/retail/dashboard`               | Retail home aggregate (today's sales, low-stock, alerts) |
| GET    | `/retail/products/:id/stock`      | Per-branch stock breakdown for one product               |

> The `retail-hooks.js` layer presents a synchronous façade over `API.catalog.products.*` and friends so legacy in-memory consumers keep working. **All mutations are mirrored** through `API` so audit/history/idempotency work uniformly. The aliases `API.catalog.products.*` and `API.sales.create` referenced in inline JSDoc are the *target* names; in the registry today they live under `retail.products` and `retail.sales`. Either standardise on `retail.*` (preferred) or add `catalog`/`sales` namespaces as thin shims at cut-over.

**Consumers:** the entire POS surface (`retail-pos*`, `retail-checkout*`, `retail-shift*`, `retail-inventory*`, `retail-purchasing*`, `retail-stocktake*`, `retail-expiry*`, `retail-write-offs*`) reads/writes through `retail-hooks.js`.

### 4.3 `pay` — Payments

**Auto-CRUD resources:**
- `terminals`, `disputes`, `methods`, `payouts`, `settlements`, `softposDevices`

**Custom routes:**
| Method | Path                              | Purpose                                                  |
|--------|-----------------------------------|----------------------------------------------------------|
| POST   | `/pay/disputes/:id/resolve`       | Resolve dispute (`{ resolution: 'refunded'|'denied' }`)  |

**Consumers:** `front/pay/*.compiled.js` (terminal fleet, dispute queue, settlements ledger). Sale capture itself goes through `retail.sales.create` (the FE treats payment as a step inside the sale, not as its own write).

### 4.4 `dine` — Restaurant

**Auto-CRUD resources:**
- `tables`, `kdsTickets`, `menuItems`, `recipes`, `reservations`, `deliveries`, `waiters`, `dineReports`

**Custom routes:** none. The FE drives the table/seat/order/fire lifecycle through PATCH on the resource (e.g. `API.dine.tables.update(id, { status: 'occupied', orderId })`). If the backend prefers an action-oriented surface (`/dine/tables/:id/seat`, `/dine/tables/:id/release`), add them in cut-over and the consumer wrapper can map seat() → POST instead of PATCH with one line of glue per action.

**Consumers:** `front/dine/*.compiled.js` (FOH host, server pad, KDS, expediter, reservations, delivery queue).

### 4.5 `acct` — Saaed Books (accounting)

**Auto-CRUD resources:**
- `customers`, `vendors`, `invoices`, `bills`, `bankTxns`, `coa`, `vatPeriods`, `journals`

**Custom routes:** *(in `accounting-vat.compiled.js` — registered there, not in api-seeds)*
| Method | Path                                              | Purpose                                                |
|--------|---------------------------------------------------|--------------------------------------------------------|
| POST   | `/acct/vat-returns/:id/file`                      | Submit VAT return to ZATCA (idempotency-keyed)         |
| GET    | `/acct/vat-returns/:id/submission-status`         | Poll until `accepted`/`rejected`                       |

> These two are referenced as `API.accounting.vat.file()` / `API.accounting.vat.status()` in the consumer (note the alias `accounting` ↔ `acct`). Either rename the namespace at cut-over or add an alias. **Pick one and stick.**

**Consumers:** `front/accounting/*.compiled.js` (chart of accounts, AR/AP, bank reconciliation, journals, VAT returns, financial statements).

### 4.6 `shared` — Cross-module

**Auto-CRUD resources:**
- `aiThreads`, `aiSkills`
- `biReports`, `biDashboards`
- `crmContacts`, `crmDeals`, `crmCampaigns`
- `mfgWorkOrders`, `mfgBoms`

**Consumers:** the AI panel (always-on side rail), BI dashboards (Owner Console + Super Admin), CRM/MFG screens where present.

### 4.7 `platform` — Super Admin

**Auto-CRUD resources:**
- `tenants`, `subscriptions`, `platformInvoices`
- `plans`, `services`
- `health` *(referenced as `API.platform.health.list()` in `platform-live.jsx` — confirm it's seeded; if not, add a `D('platform.health', …)` line)*

**Custom routes** *(injected by `defineRoute` from inline files, not api-seeds):*
| Method | Path                                          | Purpose                                                  |
|--------|-----------------------------------------------|----------------------------------------------------------|
| GET    | `/platform/dashboard`                         | Super Admin home aggregate                               |
| POST   | `/platform/tenants/:id/suspend`               | Tenant suspension (`{ reason }`)                         |
| POST   | `/platform/tenants/:id/reactivate`            | Reactivate suspended tenant                              |
| POST   | `/platform/tenants/:id/changePlan`            | `{ plan, mrr }` — plan change initiated by ops           |
| POST   | `/platform/tenants/:id/message`               | `{ subject, body, channel }` — in-app/email broadcast    |
| POST   | `/platform/tenants/:id/impersonate`           | `{ reason }` — start an impersonation session            |

All five are idempotency-keyed on the modal's submit handler in `front/platform/platform-live.jsx`.

### 4.8 `auth/me` — session

Defined in `front/api/api-session.js`. The two consumer-visible methods are `API.session.get()` / `API.session.set(...)` and `API.session.hasPermission(perm)`. The wire endpoints expected at cut-over:
| Method | Path                | Purpose                                              |
|--------|---------------------|------------------------------------------------------|
| POST   | `/auth/sign-in`     | `{ email, password }` → `{ token, user, tenant }`    |
| POST   | `/auth/sign-out`    | Invalidate session                                   |
| POST   | `/auth/refresh`     | Refresh access token                                 |
| GET    | `/me`               | Current user, permissions, active branch             |
| PATCH  | `/me/branch`        | `{ branchId }` — switch active branch                |
| PATCH  | `/me/locale`        | `{ locale: 'en'|'ar' }`                              |

These are the conventional shapes; `api-session.js` is small (read it for exact field names before wiring).

---

## 5 · Auto-CRUD verb table

For every `D('<ns>.<resource>', …)` registration, you get the following on `window.API.<ns>.<resource>`:

| Verb        | Method | Path                                | Body                  | Returns               |
|-------------|--------|-------------------------------------|-----------------------|-----------------------|
| `list(p)`   | GET    | `/<ns>/<resource>?<query>`          | —                     | `Row[]` + `.meta`     |
| `get(id)`   | GET    | `/<ns>/<resource>/<id>`             | —                     | `Row` or `NOT_FOUND`  |
| `create(b)` | POST   | `/<ns>/<resource>`                  | `Row` (no id)         | `Row` (with new id)   |
| `update(id, b)` / `patch(id, b)` | PATCH | `/<ns>/<resource>/<id>` | partial `Row` | `Row` (merged)        |
| `replace(id, b)` | PUT  | `/<ns>/<resource>/<id>`           | full `Row`            | `Row` (replaced)      |
| `remove(id)`| DELETE | `/<ns>/<resource>/<id>`             | —                     | `Row` (removed)       |

**`list` query params understood by the mock today** (real backend SHOULD support the same):

- `limit`, `offset` — pagination.
- `q` — free-text; mock does substring on the JSON-stringified row. Backend should map this to a real index.
- `branchId` — scopes to rows whose `branchId === payload.branchId` OR `branches === '*'` OR `branches.includes(...)`.
- `tenantId` — scopes to a tenant. The session's `X-Tenant-Id` header is the source of truth; this param is for cross-tenant ops queries (Super Admin).

**Server-set fields** the mock injects on writes (the real backend SHOULD set them):
- `id` — ULID if caller didn't provide.
- `tenantId` — from session.
- `createdAt` — ms epoch on POST.
- `updatedAt` — ms epoch on PATCH/PUT.

---

## 6 · Consumer patterns

Three patterns cover ≥95% of call sites.

### 6.1 Read with hook

```jsx
// owner-console.compiled.js
const dashState = window.useApi(() => window.API.owner.dashboard(), []);
const O = dashState.data?.data || dashState.data || null;

if (dashState.loading) return <LoadingState />;
if (dashState.error)   return <ErrorState err={dashState.error} retry={dashState.refetch} />;
if (!O)                return <EmptyState />;

return <OwnerDashboard data={O} />;
```

`useApi` returns `{ data, loading, error, refetch }`. The `?.data || dashState.data` dance handles the `{ data: … }` envelope vs the auto-stripped form returned by the mock — converge to one shape at cut-over.

### 6.2 Write with hook

```jsx
// hr-payroll-v2.compiled.js
const mutPreview = window.useMutation(
  (id) => API._call('POST', `/owner/payrollRuns/${id}/preview`)
);
const mutApprove = window.useMutation(
  (id) => API._call('POST', `/owner/payrollRuns/${id}/approve`)
);

<button
  disabled={mutPreview.loading}
  onClick={() => mutPreview.mutate(run.id).then(() => runApi.refetch())}
>
  Preview
</button>
{mutPreview.error && <FieldError err={mutPreview.error} />}
```

`useMutation` returns `{ mutate, loading, error, lastResult }`. **Always refetch the list/detail query after a successful mutation** — the FE has no global cache invalidation, the screen re-reads explicitly.

> Many call sites use `API._call('POST', '/path/...')` directly instead of the namespaced façade. That's fine and goes through the same seam. At cut-over you can leave them as-is or migrate to `API.owner.payrollRuns.preview(id)` for symmetry. No behaviour change either way.

### 6.3 Idempotency on writes

```jsx
// dalseen-commercial-screens.compiled.js
const payMut = window.useMutation(
  ({ invoiceId, paymentMethodId, idempotencyKey }) =>
    window.API._call('POST', `/owner/invoices/${invoiceId}/pay`,
      { paymentMethodId },
      { idempotencyKey })  // 4th arg = opts; threaded into the Idempotency-Key header
);

// Caller computes a deterministic key per logical action:
const idemKey = `pay-${invoice.id}-${paymentMethod.id}`;
payMut.mutate({ invoiceId: invoice.id, paymentMethodId, idempotencyKey: idemKey });
```

When `opts.idempotencyKey` is omitted, `buildHeaders()` auto-generates a ULID. **Prefer explicit deterministic keys** for any user-initiated action where double-click protection matters (payments, approvals, ticket sends).

### 6.4 Live wrappers (`*-live.jsx`)

`platform-live.jsx` and `owner-live.jsx` follow a non-destructive pattern: the original pre-compiled component stays untouched (still reads `window.DALSEEN_PLATFORM` / `window.DALSEEN_OWNER` fixtures), and a thin "Live" wrapper around it does the `useApi(API.x.y)` call, then either passes the API data through or — when not needed — just renders the original. This is intentional so the cut-over can land **without recompiling** the inner components.

**At cut-over:** keep the wrappers, but you can safely delete the inner fixture-reading code path once you confirm every screen mounts data through the wrapper. Until then, the dual path is the safety net.

---

## 7 · Mock router behaviours the real backend MUST replicate

The mock implements a few behaviours that the consumer code depends on, often subtly. Replicate these on the real backend or screens will misbehave.

| Behaviour                                      | Where (mock)                       | Why it matters                                              |
|------------------------------------------------|------------------------------------|-------------------------------------------------------------|
| `data` envelope unwrap on success              | `_call` — returns `json.data` if present | Consumers assume a `Row` or `Row[]`, not the envelope.     |
| Throw `ApiError` on `!res.ok` OR `json.error`  | `_call` `if (!res.ok || json.error)`     | Lets backends signal soft errors with HTTP 200.            |
| `meta` on list arrays (`Object.assign`)        | `_fakeRoute` GET-no-id branch            | Pagination headers; consumers read `rows.meta.total`.      |
| `branchId` filter at list time                 | `_fakeRoute` GET-no-id branch            | Multi-branch screens trust the server to scope.            |
| `q` substring filter                           | `_fakeRoute` GET-no-id branch            | Search inputs throttle into `?q=` — real backend needs FTS.|
| Auto-stamp `createdAt`/`updatedAt`/`tenantId`  | `_fakeRoute` POST/PATCH/PUT branches     | Consumers display `r.createdAt` and assume server-set.     |
| `Idempotency-Key` collapse                     | `buildHeaders` + caller-supplied `opts.idempotencyKey` | Double-click protection; replays return original 2xx.|
| `delay()` 80–250ms simulated latency           | `_call` mock branch                      | Loading states are tested against this floor; real p50 should be ≤ this so spinners don't jank. |
| `maybeFail()` 1.5% random error                | `_call` mock branch                      | Error UI is exercised in dev; not a real-backend concern.   |
| Permission checks server-side                  | `defineRoute` `(payload, sess) => …`     | FE checks `hasPermission` for affordance; server is the gate. |

---

## 8 · Cut-over checklist

When wiring the real backend, do these in order:

1. **Set config:**
   ```js
   window.API.config.MOCK_MODE   = false;
   window.API.config.API_BASE_URL = 'https://api.dalseen.example';
   ```
   (Both live in `front/common/api.js` `CONFIG` object — make them env-driven via build-time replace or a `<meta name="api-base-url">` read on boot.)

2. **Implement `/auth/sign-in` first.** Without a real session the rest is moot. Confirm `API.session.get()` returns the right shape (`tenantId`, `token`, `permissions[]`, `activeBranchId`).

3. **Implement the eight namespaces in dependency order:**
   1. `auth/me` (blocks everything)
   2. `platform.tenants` (blocks Super Admin and onboarding)
   3. `owner.dashboard` + `owner.staff` + `owner.leaveRequests` + `owner.payrollRuns/*` (blocks Owner & HR)
   4. `retail.*` (blocks POS, inventory, purchasing — biggest surface)
   5. `pay.*` (blocks payments, terminal management)
   6. `acct.*` + VAT routes (blocks accounting; depends on retail+pay events flowing first)
   7. `dine.*` (parallel to retail; ship after retail patterns are battle-tested)
   8. `shared.*` (AI/BI/CRM/MFG — last because consumers tolerate empty data here)

4. **Verify the wire contract** with one route per namespace before scaling out: shape of envelope, `meta`, `Idempotency-Key` replay, error envelope. If those are right, the rest is mechanical.

5. **Decide on naming aliases:**
   - `accounting` ↔ `acct` (FE uses both — pick one).
   - `catalog` ↔ `retail.products` (FE inline aliases — pick one).
   - `sales.captureSale` ↔ `retail.sales.create` (FE JSDoc references both — pick one).

   Whichever you pick, add a one-line shim in `front/common/api.js` so old call sites keep working: `window.API.accounting = window.API.acct;`.

6. **Keep the mock alive.** Don't delete `api-seeds.js` — `MOCK_MODE = true` becomes a per-developer override and your offline demo mode. Treat it as the contract test for the real backend (every fixture row should be fetchable from the real backend with the same shape).

7. **Add real-backend-only concerns** the mock doesn't model:
   - Real auth refresh (handled in `api-session.js` — wire `/auth/refresh`).
   - 401 → re-auth redirect (centralise in `_call` so every consumer benefits).
   - Long-poll/WebSocket for KDS tickets, dispute updates, payroll-commit progress (today the FE polls; consider upgrading to push at cut-over).
   - File upload (logos, ZATCA cert, payroll WPS file) — none in the FE today; spec separately.

---

## 9 · What is **not** here (intentional gaps)

- **No GraphQL.** REST-only, one envelope. Don't introduce GraphQL at the boundary — the consumers aren't built for it.
- **No client-side cache layer.** Each `useApi` call hits `_call`; there's no SWR/React Query. If you add one at cut-over, the consumers will still work — `useApi` becomes a thin adapter.
- **No optimistic updates.** Every write awaits the response and explicitly refetches. Keep this — the screens are designed around it; bolting optimism on retroactively is a mess.
- **No streaming endpoints.** AI threads are request/response today. If you add SSE later, gate it behind a separate `API.shared.aiThreads.stream(threadId)` method to avoid breaking the request/response shape of the existing `.list()` / `.create()`.
- **No file upload.** Plan separately.
- **No analytics/observability.** No client telemetry today. If you add it, route through `_call` so every API call gets a span automatically.

---

## 10 · Pointer index

When in doubt, read these in order:

| File                                           | Why                                                       |
|------------------------------------------------|-----------------------------------------------------------|
| `front/common/api.js`                          | The seam: `_call`, `defineResource`, `defineRoute`        |
| `front/common/api-seeds.js`                    | The endpoint registry — single source of truth            |
| `front/api/api-hooks.jsx`                      | `useApi`, `useApiMutation`, `useMutation`                 |
| `front/api/api-session.js`                     | Session/auth shape, header building                       |
| `front/api/api-fetch.js`                       | `fetch` wrapper, header injection                         |
| `front/api/api-states.jsx`                     | The five canonical UI states (idle/loading/empty/error/data) |
| `front/owner/owner-live.jsx`                   | Reference live-wrapper pattern                            |
| `front/platform/platform-live.jsx`             | Reference idempotency-keyed mutation pattern              |
| `front/owner/hr-payroll-v2.compiled.js`        | Reference multi-step write workflow (preview→approve→commit) |
| `front/owner/hr-leave-v2.compiled.js`          | Reference list+approve/reject pattern                     |
| `front/accounting/accounting-vat.compiled.js`  | Reference long-running submission + polling pattern       |

---

**End of API-USAGE-MAP.md.**
