# Domain contracts One file per client `services/` domain — **22 files, 22 domains, one-to-one.** Each names every server endpoint that belongs to it, verdicted against the live swagger and the real client code. > Last verified: 2026-07-30 against commit `d3ec723` and > [`../openapi/swagger.v1.json`](../openapi/swagger.v1.json) (2026-07-29). Read [`../api-contract.md`](../api-contract.md) first — the envelope, casing, pagination, errors, auth, idempotency and money rules hold everywhere and are not restated per domain. --- ## How to read a domain file Every endpoint carries one verdict: | Verdict | Means | | --- | --- | | **wired** | In swagger **and** called by the domain's real `apis/clientApi.ts` | | **unwired** | In swagger, no real client caller. Server-only, admin-only, or superseded — the reason is given | | **phantom** | The client calls it; **the server has no such route.** It 404s. Always carries its REQ | `phantom` rows are the frontend's proposed routes, filed as REQs and mocked behind the domain seam meanwhile. They are not bugs in the client — they are the contract's open edge — but on a domain whose mock is **off** they are live 404s, and that is called out where it happens. ## The census 186 operations, each in exactly one file below. 184 belong to a domain; 2 (`ping`) are platform endpoints and live in [`../api-contract.md`](../api-contract.md#platform-endpoints-outside-every-domain). | Domain file | `client/src/services/` | Seam | Server ops | Phantom | | --- | --- | --- | --- | --- | | [addresses.md](addresses.md) | `addresses` | real | 5 | — | | [admin.md](admin.md) | `admin` | **mock** | 14 | 5 | | [auth.md](auth.md) | `auth` | real | 7 | — | | [bnpl.md](bnpl.md) | `bnpl` | **mock** | 9 | 3 | | [booking-requests.md](booking-requests.md) | `bookingRequests` | real | 7 | — | | [bookings.md](bookings.md) | `bookings` | real | 16 | — | | [catalog.md](catalog.md) | `catalog` | real | 14 | — | | [geography.md](geography.md) | `geography` | real | 13 | — | | [notifications.md](notifications.md) | `notifications` | real | 4 | — | | [nurse.md](nurse.md) | `nurse` | real | 4 | — | | [partner-center.md](partner-center.md) | `partnerCenter` | **mock** | 9 | 6 | | [patient-records.md](patient-records.md) | `patientRecords` | **mock** | 5 | — | | [patients.md](patients.md) | `patients` | real | 5 | — | | [payment.md](payment.md) | `payment` | real | 3 | 1 | | [payouts.md](payouts.md) | `payouts` | **mock** | 13 | 1 | | [profiles.md](profiles.md) | `profiles` | real | 7 | — | | [refunds.md](refunds.md) | `refunds` | **mock** | 8 | 4 | | [reviews.md](reviews.md) | `reviews` | real | 8 | — | | [search.md](search.md) | `search` | real | 2 | — | | [service-areas.md](service-areas.md) | `serviceAreas` | real | 3 | — | | [tickets.md](tickets.md) | `tickets` | real | 11 | 1 | | [verification.md](verification.md) | `verification` | **mock** | 17 | 3 | | | | | **184** | **24** | "Seam" is the domain's `constants.ts` flag (`USE__MOCK`): **15 real, 7 mock.** A mocked domain still has a complete real client — flipping the flag is one line in `apis/index.ts`. **Two phantoms sit on a domain whose mock is off**, so they are reachable and they 404: `GET /api/v1/bookings/payment_history` ([payment.md](payment.md), REQ-047) and `POST /api/v1/tickets/{id}/assign` ([tickets.md](tickets.md), REQ-063). Both are guarded in the client — the first renders an empty state, the second is behind a default-off capability flag. ## Route-shape exceptions The routing convention is snake_case segments generated from `[controller]`/`[action]` tokens. **Four controllers hardcode a route string instead**, and three of those introduce hyphens: | Route | Controller | Note | | --- | --- | --- | | `api/v1/admin/partner-centers` | `AdminPartnerCentersController` | hyphens **and** a nested `admin/` segment; children add `/set-active`, `/sponsor-nurse` | | `api/v1/admin/tickets` | `AdminTicketsController` | nested `admin/` segment | | `api/v1/admin/reviews/moderation_queue` | `AdminReviewsController` | nested `admin/`, then snake_case | | `api/v1/internal/bookings/{bookingId}/center` | `InternalCentersController` | an `internal/` namespace | Every other admin controller uses a flat `admin_*` prefix (`admin_geo`, `admin_catalog`, `admin_refunds`, …). The split is historical, not meaningful. Since the route also derives the dynamic-permission key, normalising it is a breaking change to permissions as well as URLs — it is recorded here, not fixed. ## Enum vocabularies Swagger declares **no** string enums (see [`../api-contract.md`](../api-contract.md#enums)), so each domain file carries its own vocabulary. Every one was cross-checked against the server's `Baya.Domain` code set **and** the client's string-literal union. All match except one, noted in [tickets.md](tickets.md). | Vocabulary | Domain file | Server source | | --- | --- | --- | | `BookingRequestStatus` · `RequiredCaregiverGender` | [booking-requests.md](booking-requests.md) | `Entities/Booking/BookingRequestStatus.cs` | | `BookingStatus` · `BookingSessionStatus` · `VisitVerificationStatus` | [bookings.md](bookings.md) | `Entities/Booking/*.cs` | | `PriceUnit` | [catalog.md](catalog.md) | catalog config rows | | `BnplStatus` · `BnplEligibilityStatus` · `ProviderCode` | [bnpl.md](bnpl.md) | `Entities/Bnpl/*.cs` | | `PaymentTransactionStatus` · `MoadianStatus` | [payment.md](payment.md) | `Entities/Payments/`, `Entities/Invoices/` | | `RefundStatus` · `RefundChannel` · `ClawbackStatus` · cancellation codes | [refunds.md](refunds.md) | `Entities/Refunds/*.cs` | | `PayoutStatus` · `PayoutBatchStatus` · `EarningsState` | [payouts.md](payouts.md) | `Entities/Payouts/*.cs` | | `VerificationStatus` · `VerificationStepStatus` · `StepTypeCode` | [verification.md](verification.md) | `Entities/Verification/*.cs` | | `ModerationStatus` · `ModerationAction` | [reviews.md](reviews.md) | `Entities/Reviews/ReviewModerationStatus.cs` | | `TicketStatus` · `TicketCategory` · `TicketAuthorRole` | [tickets.md](tickets.md) | `Entities/Messaging/TicketCodes.cs` | | `CenterOnboardingState` | [partner-center.md](partner-center.md) | `Entities/PartnerCenters/` | | `BankAccountStatus` | [nurse.md](nurse.md) | bank-account entity | | roles · `Gender` | [auth.md](auth.md) | identity seed | | config/audit/holiday/alert codes | [admin.md](admin.md) | `Entities/Configuration/`, `Audit/`, `Holidays/`, `SupportAlerts/` | ## What replaced what These files supersede [`archive/build-chain/contracts/domains/`](../../../archive/build-chain/contracts/domains/) — 17 hand-written files frozen 2026-07-13, plus the two `conventions/` files. Route-level content there held up well: an audit of every route those files name found **zero** that the live swagger lacks. What did not hold up: | Was | Now | | --- | --- | | `conventions/api-conventions.md`: body casing is "typically `snake_case` … derive from swagger" | **camelCase**, proven mechanically. [`../api-contract.md`](../api-contract.md#casing) | | `conventions/api-conventions.md`: server default `https://localhost:5002` | `http://localhost:5002` — plain HTTP (contradiction **C-3**) | | The envelope has 5 fields | It has **6** — `code` was added for machine-readable errors (REQ-003) | | Enum vocabularies spread across 17 files and the REQ ledger | One vocabulary block per domain file, cross-checked both ways | | `messaging.md` (851 B, headerless) silently amending `messaging-notifications-admin.md` | Merged: [tickets.md](tickets.md) + [notifications.md](notifications.md) + [admin.md](admin.md) (contradiction **C-8**) | | 17 files whose names matched *backend phases* | 22 files whose names match the **client's domains**, which is how the seam is actually consumed | | The REQ ledger as the change log you had to read to know the current shape | Each domain file states the current shape and lists only its **open** REQs |