# API Surface — current reality

A reality-based inventory. Used to drive Phase-2 contract work; not a
specification (the spec is `openapi/v1/openapi.json`).

## v1 (public, contracted)

| Route | Methods | Auth | Tag |
|---|---|---|---|
| `/api/v1/health` | GET | anonymous | Health |
| `/api/v1/master-data` | GET | cookieAuth | Master Data |
| `/api/v1/suppliers` | GET, POST | cookieAuth | Suppliers |
| `/api/v1/projects` | GET, POST | cookieAuth + entitlement(`MODULE_PROJECTS`) + tenant | Projects |
| `/api/v1/projects/{id}` | GET, PATCH, DELETE | cookieAuth + entitlement + tenant | Projects |
| `/api/v1/oee` | GET, POST | cookieAuth + entitlement(`MODULE_OEE`) + tenant | OEE |
| `/api/v1/assignments` | GET | cookieAuth + entitlement(`MODULE_CALENDAR`) + tenant | Assignments |

## Internal (not contracted)

### Tenant admin UI (`/api/admin/*`)

| Route | Methods |
|---|---|
| `/api/admin/audit` | GET |
| `/api/admin/demo` | POST |
| `/api/admin/emails/retry` | POST |
| `/api/admin/views` | GET |
| `/api/admin/users` | GET |
| `/api/admin/users/lock` | POST |
| `/api/admin/users/unlock` | POST |
| `/api/admin/users/notify` | POST |
| `/api/admin/users/reset-password` | POST |
| `/api/admin/users/update-email` | POST |

### Owner / control-plane (`/api/owner/*`)

24 routes covering plans, plan features, tenants (CRUD + branding + health +
modules + suspend + archive + provision + invite-admin + convert-to-paid +
subscription + support-access + demo-packs seed/cleanup), provisioning jobs
(retry / status), and audit. All require `requireOwnerRole(...)` from
`@/lib/owner-audit` and call `logAuditEvent(...)` after every mutation.

### Demo / tooling (`/api/demo/*`)

| Route | Methods |
|---|---|
| `/api/demo/load` | POST |
| `/api/demo/reset` | POST |
| `/api/demo/status` | GET |

### First-party UI (mixed)

`/api/planning/consultants` (GET, [id] GET/PATCH/DELETE), `/api/planning/settings`
(GET, PUT), `/api/wertstrom` (GET/POST), `/api/wertstrom/{id}` (GET/PATCH/DELETE),
`/api/repository/search` (GET), `/api/qaf-template` (GET), `/api/projects/next-id`
(GET), `/api/holidays/seed` (POST).

These have a stable shape *per the UI that calls them* but no published contract.
Promote to `/api/v1/*` if they need to be externally consumed.

## Module surface inventory

Public-surface state of each major `lib/<module>/`:

| Module | Public surface | Notes |
|---|---|---|
| `lib/api` | ✅ barrel | response envelope helpers; required by all v1 routes |
| `lib/cache` | ✅ barrel | `getCacheAdapter()` selector |
| `lib/control-plane` | ✅ barrel | clients + cp_* types |
| `lib/customers/bmw` | ✅ barrel | branding only (Phase 9 adapters pending) |
| `lib/entitlement` | ✅ barrel | engine + types + client hook |
| `lib/oee` | ✅ barrel | calculation + interpretation |
| `lib/owner-audit` | ✅ barrel | role guard + audit log |
| `lib/provisioning` | ✅ barrel | dispatcher + runner |
| `lib/support` | ✅ barrel | session validators |
| `lib/tenant` | ✅ barrel | tenant + module guards |
| `lib/auth` | ⚠️ deep | `getUserSession` and friends imported via deep paths |
| `lib/branding` | ⚠️ deep | engine imported directly |
| `lib/duplicates` | ⚠️ deep | `merge.ts` imported by tests |
| `lib/master-data` | ⚠️ deep | one external import (`use-placeholders`) |
| `lib/planning`, `lib/holidays`, `lib/employees`, `lib/repositories`, `lib/views`, `lib/email`, `lib/export`, `lib/format`, `lib/i18n`, `lib/offline`, `lib/repository`, `lib/calendar`, `lib/owner`, `lib/demo` | ⚠️ deep / mixed | barrel may add value when next touched; not blocking |
| `lib/supabase` | ⚠️ deep but stable | `createClient` (server), `createBrowserClient` (client), `createAdmin` are the de facto barrel; consider promoting to a real `index.ts` if the layout ever becomes confusing |
| `lib/logger`, `lib/env`, `lib/utils`, `lib/german-holidays`, `lib/qaf-parser`, `lib/repository-types`, `lib/vsm-types`, `lib/assessment-*`, `lib/planning-*` | n/a | single file at the lib root, no internals to hide |

## What this inventory drives

- The v1 column above is what `openapi/v1/openapi.json` documents today.
- The "internal" sections are explicitly excluded from the v1 contract — see
  [`api-contract.md`](./api-contract.md).
- The "deep" rows in the module table are tolerated; they do not block the
  Phase 2 close. New external imports must use the barrels where they exist.

## Known schema-vs-route mismatches (v1)

These are documented here so readers don't take the OpenAPI shape as gospel
where the route handler itself is wrong. The handlers were preserved as-is in
the envelope migration (behavior preservation rule); the fixes are tracked in
[`follow-ups.md`](./follow-ups.md).

| Route | Mismatch | DB reality (source) | Documented in spec |
|---|---|---|---|
| `GET /api/v1/oee` (filter) and `OeeRecord.week_number` | Route filters/returns `week_number`. | Column is `calendar_week` (`MO-OEE/oee-phase1.sql`). | OpenAPI `OeeRecord.description` calls this out; schema kept loose. |
| `GET /api/v1/assignments` (filter) and `Assignment.start_date`/`end_date` | Route uses `start_date`/`end_date`. | Single `date` column (`MO-23/planning-tables.sql`). | OpenAPI `Assignment.description` calls this out; schema kept loose. |
| `Assignment.planning_projects` join | Route joins `planning_projects` via a non-existent `planning_project_id`. | No FK; join silently returns `null`. | OpenAPI `Assignment.description` calls this out. |

**Why preserve the bugs?** The envelope migration was a contract-shape change,
not a behavior change. Fixing the SQL columns belongs in the OEE / planning
modules' own follow-up work, where the right people can reason about
breaking-vs-additive impact on the iOS app and downstream consumers.
