Appearance
Authority Doc / Public V1 REST
The REST API Contract Standard defines how CoachMe apps talk to the backend without drifting apart.
This page is the source of truth for Public V1 REST route shape, DTOs, response envelopes, error envelopes, pagination, idempotency, timestamps, upload intents, OpenAPI generation, compatibility, and contract tests. Feature pages can define local endpoints, but they must follow this contract standard.
REST + OpenAPIGenerated ClientsContract TestsBackend Authority
Note
This page covers REST API contracts only. Domain events, outbox processing, queue payloads, and mobile offline sync are owned by adjacent authority docs: event-and-outbox-standard and offline-sync-protocol.
REST Scope
Use this page for HTTP API consistency. Do not let it become the event, queue, or offline-sync protocol.
This Standard Owns
- URL and route naming under
/api/v1. - HTTP method semantics and status codes.
- Request DTOs, response DTOs, response envelopes, and the bounded client-diagnostic DTO.
- HTTP request-id validation and authoritative workflow-correlation issuance or resolution.
- Error and validation envelopes.
- Pagination, filtering, sorting, and sparse field rules.
- Idempotency, retry handling, and optimistic concurrency.
- Timestamp, locale, timezone, and unit conventions.
- Upload intent and media download contract patterns.
- OpenAPI generation, generated clients, compatibility, and REST contract tests.
This Standard Does Not Own
- Domain event envelope rules.
- Outbox transaction, retry, ordering, and dead-letter rules.
- Queue payloads and worker job payloads.
- Mobile local database schema.
- Offline operation replay protocol.
- Provider-specific API details except through normalized CoachMe REST boundaries.
- Telemetry event names, metric conversion, dashboards, alerts, and retention, which remain with Observability and Privacy.
Note
REST contracts should be boring and predictable. When a feature needs richer background processing or offline reconciliation, the REST endpoint should return a stable receipt and hand off the deeper behavior to the event/outbox or offline-sync authority.
Route Shape
Routes identify context and resources. They should not hide authorization, lifecycle, or state-machine decisions in vague verbs.
Route Rules
- All public app REST routes live under
/api/v1. - Use plural nouns for resource collections.
- Use kebab-case route segments.
- Use path IDs only for the resource being addressed.
- Use query parameters for filters, pagination, sorting, includes, and sparse fields.
- Use command subresources only when the action is a meaningful business transition.
- Never expose database table names, ORM names, provider-specific object names, or raw storage keys in route design.
HTTP Method Semantics
GETreads and never mutates product state.POSTcreates resources or executes command-style transitions.PATCHapplies partial updates to a known resource.PUTreplaces a known resource only when full replacement is explicitly safe.DELETEremoves, cancels, revokes, or starts deletion according to the resource lifecycle.
| Route Family | Purpose | Examples |
|---|---|---|
/api/v1/public/... | Safe unauthenticated flows. | GET /api/v1/public/client-invitations/{token}/preview |
/api/v1/me/... | Current user, active session, and account preferences. | GET /api/v1/me/session-context |
/api/v1/coach-workspaces/{workspaceId}/... | Coach workspace setup, templates, clients, and coach-owned resources. | GET /api/v1/coach-workspaces/{workspaceId}/clients |
/api/v1/coach-client-relationships/{relationshipId}/... | Relationship-scoped coaching, client execution, review, messages, and progress. | POST /api/v1/coach-client-relationships/{relationshipId}/forms/{formId}/submit |
/api/v1/personal-workspaces/{personalWorkspaceId}/... | Personal and self-coaching resources. | GET /api/v1/personal-workspaces/{personalWorkspaceId}/today |
/api/v1/client/... | Client-app installation and client-owned device surfaces, scoped by authenticated user before a workspace is selected. | POST /api/v1/client/devices |
/api/v1/support/... | Permissioned, reason-coded support/admin APIs. | GET /api/v1/support/cases/{caseId}/resources |
/api/v1/internal/... | Service-authenticated worker or internal HTTP boundaries. | POST /api/v1/internal/daily-execution/materialize |
Note
Prefer POST /publish, POST /cancel, POST /complete, or POST /revoke only when the command maps to a named lifecycle transition. Generic verbs such as /process, /do, and /update-status are not contract-quality route names.
/sync is the one sanctioned transport subresource: POST pushes an operation batch and GET pulls the scoped snapshot for the same context. Its envelope, receipts, and per-operation statuses belong to offline-sync-protocol; this page only fixes its placement under the owning context route.
Authentication And Context
The request must prove the actor, selected operating context, and target resource scope before the application use case runs.
Required Context
- Authenticated app calls use
Authorization: Bearer <token>. - Public endpoints must return only safe preview data and must not leak private existence signals.
- The selected operating context must be clear from route, claims, or explicit context header.
- Workspace, relationship, personal workspace, and support/admin scopes must never merge.
- Every request receives an API-validated request id. The API issues or resolves an authoritative
correlationIdonly for a bounded product workflow.
Backend Authorization Is Final
Generated clients, UI route guards, and mobile cache checks improve experience, but they do not grant authority. Backend guards and application use cases verify permissions on every read, mutation, upload, playback, export, delete, and internal action.
Permission behavior follows roles-and-permissions. Lifecycle behavior follows workflow-state-map.
| Header Or Field | Required For | Rule |
|---|---|---|
Authorization | Authenticated app and support calls. | Bearer token must resolve to a CoachMe user/session and accepted auth provider identity. |
X-Request-Id | All calls. | The API validates any supplied value, replaces an unacceptable value, generates one when absent, and always returns and logs the authoritative request id. |
X-Correlation-Id | Bounded multi-step product workflows. | Public clients cannot author the authoritative value. The API creates it at workflow start and resolves continuation only from trusted workflow state or a prior API-issued receipt. Validated internal calls propagate it; arbitrary public values are rejected or replaced. |
Idempotency-Key | Retryable commands. | Required for commands listed in the mutation section that do not carry a clientOperationId. |
If-Match or expectedRevision | Contested edits. | Required when another actor or device can change the same resource. |
X-Support-Reason-Code | Support/admin sensitive access. | Required with target scope and audit event before sensitive data is read or changed. |
DTO Rules
REST contracts expose product language, not persistence internals.
JSON Shape
- Use
application/json; charset=utf-8. - Use camelCase property names.
- Use stable DTOs, not raw database rows.
- Use
idfor the represented resource and explicit names such asworkspaceId,relationshipId, ortaskIdfor references. - Use arrays for lists, even when empty.
- Use strings for identifiers, enums, ISO timestamps, local dates, and decimal-safe values when precision matters.
Null, Optional, And Redacted Values
- Use
nullonly when null carries product meaning. - Omit optional fields that were not requested, not calculated, or not available.
- Use explicit redaction metadata when a field exists but the actor lacks permission to see it.
- Never return secrets, provider tokens, raw password state, raw audit payloads, internal notes, or private diagnostics through normal app DTOs.
| Contract Choice | Rule | Why |
|---|---|---|
| Enums | Use uppercase snake case string values such as ACTIVE and PAUSED. | Matches existing workflow-state vocabulary and keeps generated clients stable. |
| Money and precise decimals | Use strings plus currency/unit metadata. | Avoids floating-point drift in web and mobile clients. |
| Measurement values | Use JSON numbers with the canonical unit named in the field, such as weightKg. Use strings plus unit metadata only for money and for user-entered display-unit snapshots. | Keeps workout and body metrics arithmetic-ready on device while money stays exact. |
| Booleans | Name positively, such as isComplete or canPublish. | Avoids double-negative client logic. |
| Derived values | Include source timestamp or revision when stale derived data could mislead. | Lets clients explain snapshot freshness. |
| Permission-limited fields | Prefer omission or explicit redaction over empty fake values. | Prevents clients from confusing unavailable data with real blank data. |
Response Envelopes
Clients should be able to parse success, list, command, and error responses with one predictable pattern.
| Response Type | Shape | Use When |
|---|---|---|
| Single resource | { "data": { ... }, "meta": { ... } } | Reading or returning one canonical resource. |
| Collection | { "data": [ ... ], "page": { ... }, "meta": { ... } } | Returning cursor-paged lists. |
| Paged snapshot | { "data": { ... }, "page": { ... }, "meta": { ... } } | Returning one snapshot object whose primary sub-collection is cursor-paged. |
| Command result | { "data": { "id": "...", "status": "...", "revision": 3 }, "meta": { ... } } | Mutations where the client needs a receipt, revision, or next action. |
| Empty success | 204 No Content | Only when the client does not need an updated revision, receipt, or redirect target. |
| Error | { "error": { ... } } | Any rejected request. |
Meta Fields
requestId: returned on every response when practical.generatedAt: UTC instant for generated snapshots or projections.serverTime: UTC server clock at response time. It is the offline-sync name for the same instant and replacesgeneratedAton sync transport and daily-execution responses.revision: resource revision when optimistic concurrency applies.warnings: non-blocking machine-readable warnings when the client needs to display or log them.
Command Result Fields
id: created or affected resource id.status: resulting resource or command status.revision: new accepted revision when applicable.receiptId: stable receipt for retry, sync, audit, or support diagnosis.nextAction: optional machine-readable hint when user action is required.
Error Envelope
Errors must be readable by people, stable for clients, and useful for support without leaking private data.
Canonical Error Shape
json
{
"error": {
"code": "VALIDATION_FAILED",
"message": "The request could not be accepted.",
"status": 422,
"requestId": "req_01HX...",
"fields": [
{
"path": "profile.birthDate",
"code": "DATE_IN_FUTURE",
"message": "Birth date cannot be in the future."
}
]
}
}| Status | Use For | Contract Rule |
|---|---|---|
400 | Malformed request shape. | Use when the server cannot parse the request or route parameters are structurally invalid. |
401 | Missing or invalid authentication. | Do not reveal whether the target resource exists. |
403 | Authenticated actor lacks authority. | Use when the actor is known but the selected context, role, surface, or data rule blocks access. |
404 | Resource not found or intentionally hidden. | Use when the resource is absent or existence must not be disclosed. |
409 | Business conflict. | Use for lifecycle, duplicate active relationship, incompatible state, or stale domain conflict. |
410 | Gone. | Use for expired, consumed, revoked, purged, or no-longer-available public resources when safe to disclose. |
412 | Precondition failed. | Use for failed If-Match, stale expectedRevision, or checksum mismatch preconditions. |
413 | Payload too large. | Use for body or upload metadata over the documented endpoint limit. |
415 | Unsupported media type. | Use when content type is not allowed. |
422 | Validation failed. | Use for field-level business validation where the JSON shape is parseable. |
429 | Rate limited. | Return retry metadata when safe and avoid account enumeration. |
5xx | Server or dependency failure. | Return a safe generic message plus request id. Log the detailed cause internally. |
Note
Error code values are stable API contract values. Display copy can change by locale, but clients must key behavior from machine-readable codes and field paths.
Fallback Codes
Endpoints with no more specific code fall back to VALIDATION_FAILED for parseable field-level failures, REQUEST_REJECTED for other rejected requests, and INTERNAL_SERVER_ERROR for unhandled failures. Field-level code values derive from the validation constraint name in upper snake case, such as IS_EMAIL or MAX_LENGTH.
Sync Receipt Exception
Sync-receipt commands are the one exception to the error envelope. When a rejected outcome is itself a durable receipt the client must reconcile, the endpoint returns the command-result envelope with the mapped status code — 409 for a conflicted receipt, 422 for a rejected business payload, 404 for an unresolved target — and carries the machine-readable reason in data.rejectionCode or data.conflictCode. The { "error": { ... } } envelope still applies to transport, auth, permission, and request-shape failures on the same endpoint.
Lists, Pagination, Filtering, And Sorting
List endpoints must stay stable as workspaces grow and must not leak data across context boundaries.
Cursor Pagination
- Use cursor pagination for growing collections.
- Use opaque cursor values.
- Document the default and maximum page size per endpoint.
- Return
nextCursoronly when another page is available. - Keep sort order stable across pages.
Filters And Includes
- Allowlist filters per endpoint.
- Reject unknown filters with
422 VALIDATION_FAILED. - Allowlist
includeexpansions and sparse fields. - Permission-check every included relation as if it were loaded by its own endpoint.
- Never return global counts when a count can leak hidden resources.
Collection Response Shape
json
{
"data": [
{ "id": "task_123", "status": "PENDING", "title": "Submit check-in" }
],
"page": {
"limit": 25,
"nextCursor": "cur_01HX...",
"sort": "-dueAt,id"
},
"meta": {
"requestId": "req_01HX...",
"generatedAt": "2026-07-08T06:30:00Z"
}
}Note
Offset pagination is allowed only for fixed, small, explicitly bounded collections such as a short enum-backed option list. Client, task, message, progress, food, workout, and audit-style lists use cursors.
Mutations, Idempotency, And Concurrency
Mutating requests must survive mobile retries, double taps, network failures, and parallel edits.
| Mechanism | Required For | Rule |
|---|---|---|
Idempotency-Key | Create, submit, complete, publish, resend, consume, upload-intent, and other retryable commands that do not already carry a clientOperationId. | Duplicate accepted requests return the same result or receipt when the original result is still available. |
expectedRevision | JSON command bodies where contested edits are expected. | Reject stale edits with 409 or 412 and return conflict details. |
If-Match | Resource updates that can use HTTP preconditions cleanly. | Reject missing or stale preconditions when the endpoint requires them. |
clientOperationId | Mobile-originated operations that may later sync or reconcile. | Stable per device and operation. A command that carries clientOperationId uses it as its idempotency key and does not also require Idempotency-Key. Offline acceptance rules belong to offline-sync-protocol. |
receiptId | Accepted mutations needing support or sync diagnosis. | Return a receipt in the command result and log it with request id and actor context. |
Conflict Payload
json
{
"error": {
"code": "REVISION_CONFLICT",
"message": "This resource changed before your update was saved.",
"status": 409,
"requestId": "req_01HX...",
"conflict": {
"expectedRevision": 7,
"actualRevision": 9,
"resourceId": "program_123"
}
}
}Mutation Defaults
- Return the updated representation or command receipt when clients need reconciliation.
- Return
201for a first accepted create and200with the original receipt for a replay of the same accepted command. A batch command returns200for the batch itself and reports each operation's outcome inside the result list. - Validate authorization and lifecycle state inside the same transaction when the mutation changes ownership or state.
- Never accept a mutation only because it was locally allowed earlier on mobile.
- Emit domain events through the Event And Outbox Standard, not through ad hoc REST side effects.
Time, Locale, Timezone, And Units
CoachMe operates across daily routines, workouts, meals, reminders, travel, Ramadan, and progress history. Time rules cannot be implicit.
| Concept | REST Contract Rule |
|---|---|
| Instants | Store and return UTC instants as ISO 8601 strings with Z, such as 2026-07-08T06:30:00Z. |
| Local dates | Use YYYY-MM-DD strings for calendar concepts such as Today, due date, workout day, meal day, and check-in date. |
| Timezone source | Any local date interpretation must use an explicit timezone, workspace timezone, relationship timezone, or user preference named by the endpoint. |
| Locale | Locale affects display text, sorting labels, and formatting. Locale must not change canonical enum values, IDs, or numeric storage values. |
| Units | API boundaries normalize to canonical units while preserving display unit snapshots where feature docs require it. |
| Durations | Use seconds for canonical duration values unless a feature doc defines a more specific unit. |
| Measurements | Use canonical units for storage and explicit unit metadata for display, history, imports, and user-entered snapshots. |
Uploads And Media
Media APIs wrap object storage with product ownership, permission, policy, and audit checks.
1. Request Upload Intent
Client sends owner context, purpose, filename, byte size, MIME type, checksum, and optional dimensions or duration.
Validate
2. Issue Signed Upload Target
API validates permission, lifecycle, type, size, and quota, then returns a short-lived upload URL or form fields.
Short TTL
3. Upload To Storage
Client uploads bytes directly to object storage using the signed target.
Storage
4. Complete Upload
Client confirms checksum and storage metadata. API creates the media record only after final policy checks pass.
Record
Media Rules
- Signed URLs are short-lived and do not grant permanent access.
- Playback and download endpoints re-check permission before returning a signed URL.
- Relationship end, workspace suspension, deletion, redaction, or support restriction can invalidate future URL issuance.
- Upload limits are enforced before storage acceptance whenever possible.
- Checksums are required for accepted media records.
Media Errors
413 PAYLOAD_TOO_LARGEfor body or file size over limit.415 UNSUPPORTED_MEDIA_TYPEfor blocked MIME type.412 CHECKSUM_MISMATCHfor completion checksum mismatch.403 MEDIA_ACCESS_DENIEDwhen ownership, lifecycle, or permission blocks access.
Client Diagnostic Summary
The API accepts one bounded, best-effort app-health summary without creating a product, audit, or analytics record.
Transport
- Authenticated, rate-limited
POST /api/v1/me/client-diagnostics. - Accepted summaries return
204 No Content. - Submission or telemetry-export failure cannot fail another accepted product or sync operation.
- The API hands accepted bounded values to the Observability-owned aggregation and emission contract.
Safety
- No free text, URLs, payloads, filenames, business values, user content, or authenticated session id.
- No new durable product record; rate-limit evidence follows existing security/audit authority.
- Fixed enums and registered builds only; unknown or spoofed values map to bounded fallbacks or a typed safe rejection.
Client diagnostic summary fields
| Field | Rule |
|---|---|
appSurface, appVersion, appBuild, platform | Registered release and bounded platform context. |
connectivityState, networkFailureCategory | Bounded transport outcome; never raw network text or URL. |
operationFamily, localQueueDepth, oldestQueuedOperationAgeSeconds | Offline Sync-owned local health facts, optional where surface-inapplicable. |
restartRecoveryOutcome, cachePurgeOutcome | Bounded recovery results without local payload detail. |
OpenAPI And Generated Clients
OpenAPI is the shared contract between the NestJS backend, Next.js coach web, and both Expo apps.
Source Of Truth
- Backend contracts generate the OpenAPI artifact.
- DTOs, envelopes, error schemas, auth schemes, headers, path params, query params, and status codes must appear in OpenAPI.
- Feature docs may propose contracts, but implementation must make the generated OpenAPI artifact match the approved backend behavior.
- Manual client DTO drift blocks merge.
Client Generation Gate
- Coach web, coach mobile, and client mobile compile against generated types or generated clients.
- PR checks diff the OpenAPI artifact against the previous accepted contract.
- Breaking diffs require explicit review, migration notes, and release coordination.
- Generated files policy belongs in toolchain-contract, but this page defines the contract expectation.
Note
The OpenAPI artifact is not a documentation afterthought. It is the build-time evidence that app surfaces and backend behavior still agree.
Compatibility And Deprecation
Public V1 clients will ship on app stores. Contract changes must assume older mobile builds may remain active.
| Change Type | Compatibility Rule |
|---|---|
| Add optional response field | Compatible when clients tolerate unknown fields. |
| Add optional request field | Compatible when old clients still work without it. |
| Add enum value | Potentially breaking for exhaustive clients; requires client review and fallback behavior. |
| Remove field | Breaking unless the field was formally deprecated and no supported client depends on it. |
| Rename field or enum | Breaking. |
| Change type, format, or meaning | Breaking. |
| Make optional field required | Breaking for request DTOs and potentially breaking for response DTOs. |
| Change pagination, sorting, or default filter | Potentially breaking; requires contract review. |
Versioning
Public V1 routes live under /api/v1. Breaking API changes require a new route version, a compatibility adapter, or a coordinated release window that keeps old mobile builds working until they are unsupported.
Deprecation Record
Every deprecated endpoint, field, enum value, or behavior needs an owner, reason, replacement, first deprecated date, supported-client impact, and removal date or review date.
Supported Mobile Build Window
Coach mobile and client mobile are tracked separately in a supported-build registry containing native version, build number, runtime version, latest compatible OTA update, release date, support-until date, and minimum accepted build. Each surface supports its two most recent native releases or 90 days after replacement, whichever is longer. A critical security exception may shorten the window only through a coordinated release plan.
Contract Test Gate
Every feature API must prove the happy path and the contract failures that keep apps predictable.
| Required Evidence | What Must Pass |
|---|---|
| OpenAPI validity | Generated schema is valid and includes auth, headers, path params, query params, status codes, DTOs, envelopes, and errors. |
| Generated clients | Coach web, coach mobile, and client mobile compile against generated contract types or clients. |
| Success envelopes | Single, collection, command, and empty-success responses match this standard. |
| Error envelopes | Validation, permission, not-found, conflict, rate-limit, and server errors return canonical envelopes. |
| Permission failures | Wrong workspace, relationship, personal workspace, support grant, and app surface fail without data leakage. |
| Pagination | Cursor pagination is stable across pages and respects filters, sort, and permissions. |
| Idempotency | Retrying an accepted idempotent command returns the same accepted result or receipt. |
| Concurrency | Stale expectedRevision or If-Match conflicts reject cleanly. |
| Uploads | Invalid type, too-large file, checksum mismatch, and unauthorized media access fail before unsafe acceptance. |
Note
The required-check cadence, thresholds, and merge policy belong in the Test And Release Gate. This page defines what REST contract behavior those checks must prove.
Related Docs
REST contracts sit between product authority, backend implementation, app clients, and release gates.
Workflow State Map
Defines lifecycle states and transitions that REST commands must respect.
Roles And Permissions
Defines authorization context and data access rules enforced behind every endpoint.
Event And Outbox Standard
Authority for event envelopes, transaction boundaries, retries, ordering, and dead letters.
Offline Sync Protocol
Authority for mobile operation queues, receipts, replay, conflict handling, and pruning.
Toolchain Contract
Authority for codegen scripts, generated files policy, lint, typecheck, and build commands.
Observability Plan
Owns safe telemetry use of API identifiers, client-diagnostic aggregation, logs, metrics, errors, and alerts.
Test And Release Gate
Defines required-check cadence, contract-test blocking, security checks, mobile E2E, and release smoke tests.