Source document · API/docs/API_OVERVIEW.md
How the API works
This guide follows one request from the mobile/admin client through the Go server to PostgreSQL and any external service. It explains the system as it is currently wired in cmd/api/main.go and internal/platform/httpserver/router.go.
What runs in production
The API is one Go process organized into feature modules. It is a modular monolith, not a set of independently deployed services. A reverse proxy should terminate HTTPS and forward requests to the process on its private HTTP listener. PostgreSQL is the durable system of record. MailAfrica (or SMTP) sends account email; S3-compatible object storage holds uploaded media; LiveKit carries live audio/video; Firebase Cloud Messaging is an optional Android push adapter.
The API authorizes storage and LiveKit access, but large file bytes and real-time media travel directly between the client and those providers. Do not send an S3 secret, MailAfrica key, database URL, or LiveKit secret to a mobile app.
Request pipeline
The router wraps each request in these cross-cutting layers before dispatching to a handler:
- Request ID: accept or create a correlation ID and place it in the request context and response envelope.
- Security headers: add browser-facing response protections.
- Rate limiter: allow up to 120 requests per client IP per minute; rejected requests receive a rate-limit response. The limiter bounds its IP-key map and removes expired entries.
- Metrics: record request observations for the process metrics endpoint.
- Body limit: reject request bodies larger than 10 MiB.
- Access log and panic recovery: emit structured request logs and turn handler panics into an HTTP error rather than terminating the process.
- Route handler: decode JSON, read the authenticated actor from context, call a feature service, and encode a standard response.
The API process uses read-header, request, write, and idle timeouts of 5, 15, 30, and 60 seconds respectively. Configure the reverse proxy timeouts to accommodate these limits and preserve the request ID when forwarding and logging.
Module boundaries and dependency direction
Each feature lives under internal/app/<feature>. The HTTP handler owns transport concerns; the service owns the use case and business checks; a repository interface isolates persistence; PostgreSQL adapters implement that interface. External integrations are adapters behind module-owned interfaces.
| Module | Responsibility |
|---|---|
auth | User credentials, email OTP, signed access tokens, refresh rotation, revocation, and profile/account operations. |
catalog | Service/category lookup, provider profiles, listings, menu items, and public discovery. |
orders | Server-priced bookings, guest access, reads, state transitions, completion, and sale snapshots. |
payments | Order/listing payment attempts, manual submission/evidence/review, and subscription activation. |
providers | Provider dashboard aggregation and manual sales. |
chat | Participant-scoped conversations, persisted messages/read state, and WebSocket fan-out. |
media | Upload validation, asset metadata, object-storage signing, and private-read authorization. |
notifications | Device registration, in-app inbox, optional push delivery, and notification persistence. |
community | Moments, feed visibility, likes, views, comments, reports, counters, and blocks. |
lifestyle | Dating profile opt-in, browse, curated content, meetings, and LiveKit participant tokens. |
reporting | Role-scoped report summaries and administrative audit reads. |
admin | Administrative search/overview, status and role changes, approval, moderation, announcements, assignments, and CSV export. |
Handlers do not issue SQL. A feature repository may participate in the caller’s transaction so one business action can update several related tables atomically. Cross-feature reads are kept in explicit repositories/read models rather than allowing one module to reach into another module’s SQL code.
Durable state and startup composition
cmd/api/main.go creates development in-memory adapters first. If DATABASE_URL is set, it opens PostgreSQL and replaces those adapters with PostgreSQL implementations for auth, catalog, orders, payments, providers/sales, chat, media metadata, community, lifestyle, reporting, admin, and notifications. Staging and production require DATABASE_URL; they must not be started in memory-only mode.
The database pool defaults to 10 open connections, 5 idle connections, and a 30-minute maximum connection lifetime. Tune those against the database plan and expected replica count. Schema changes are embedded under migrations/ and applied by go run ./cmd/migrate; deploy migrations before starting a version that depends on them.
The environment is validated before serving traffic. Staging/production require PostgreSQL, an AUTH_TOKEN_SECRET of at least 32 bytes, MailAfrica or SMTP, HTTPS S3-compatible storage, LiveKit credentials and a wss:// URL, a positive listing-subscription plan, and payment instructions for both supported manual methods. See production operations for every variable and deployment checks.
Response and error behavior
Every JSON response uses the envelope below. data is the result, meta is route-specific metadata (often null), and request_id is the correlation value. There is no top-level timestamp in the implementation.
{
"success": true,
"data": { "id": "…" },
"meta": null,
"error": null,
"request_id": "…"
}Errors keep the same envelope shape and populate error.code and error.message; field-level violations may appear in error.fields. Clients should branch on HTTP status and stable error code, not parse the human-readable message. 400 usually means malformed JSON or query input, 401 means the request has no valid session/capability, 403 means an authenticated actor lacks permission, 404 hides or identifies a missing resource according to the route, 409 means an idempotency/version conflict, 422 means valid JSON with invalid domain input, 429 is throttling, and 5xx means a dependency or server failure.
Endpoint contract and actual behavior
openapi/openapi.yaml is the published HTTP contract. The docs build parses it, creates the searchable operation index, and compares all method/path pairs with the route registrations in router.go. This catches missing or stale routes; it does not prove that each OpenAPI body schema captures every handler validation or authorization rule. See the endpoint reference for the operation list and the contract guide for known specification gaps.
When documentation and OpenAPI disagree with implementation, the handler, service, and repository are the runtime truth. Update the source API docs and OpenAPI in the same code change so the next build can publish the corrected behavior.
Health, readiness and metrics
| Route | Meaning |
|---|---|
GET /health | Process liveness. It returns 200 if the HTTP process can answer; it does not prove PostgreSQL or external providers are healthy. |
GET /ready | Deployment readiness. It returns 200 when readiness is disabled or its configured check succeeds; in staging/production it pings PostgreSQL and returns 503 on failure. |
GET /metrics | Process metrics for scraping. Restrict this route at the proxy/network boundary. |
GET /api/v1 | API name and major version discovery. |
Use /ready to decide whether the instance can receive production traffic; use /health only for process liveness. Neither endpoint checks MailAfrica, object storage, FCM, or LiveKit end to end.