Files
carmanagement/docs/project-design/api-routes.md
T
root c77d0a8e89
Build & Push / Build & Push Docker Image (push) Failing after 10m39s
Rename legacy storefront app and references to carplace
Replace storefront naming across source, tests, docs, config, and production scripts. Rename the legacy top-level app directory and Carplace component files, remove duplicate storefront startup scripts, and refresh the lockfile.
2026-07-04 18:10:08 -04:00

15 KiB

API Architecture and Route Design

This document explains how the API is structured today, how requests move through the stack, and what each route group is responsible for.

Source of truth for the runtime wiring:

  • apps/api/src/app.ts
  • apps/api/src/modules/*
  • apps/api/src/middleware/*
  • apps/api/src/swagger/openapi.ts

Runtime Overview

The API is an Express application mounted under /api/v1 with a few non-versioned utility endpoints:

  • GET /health returns process health.
  • GET /docs serves Swagger UI.
  • GET /api/v1/openapi.json returns the generated OpenAPI document.
  • /storage/* serves uploaded assets such as logos, hero images, vehicle photos, and reservation/customer documents.

The app boot sequence in apps/api/src/app.ts is intentionally ordered:

  1. CORS is applied first.
  2. storage guards are applied before static serving so private customer license files are never anonymously retrievable.
  3. Swagger UI is mounted before Helmet so the UI assets are not blocked by CSP.
  4. webhook routes that require raw payload handling are mounted before express.json().
  5. Helmet, request logging, JSON parsing, and the module routers are mounted.
  6. the centralized error middleware converts validation, Prisma, and application errors into consistent JSON responses.

Request Lifecycle

Most internal company routes follow the same pattern:

  1. Express router receives the request.
  2. Zod schemas validate req.params, req.query, and req.body through parseParams, parseQuery, and parseBody.
  3. auth middleware resolves the caller identity.
  4. tenant middleware resolves the company record.
  5. subscription middleware blocks suspended or pending companies where required.
  6. role middleware enforces employee or admin permissions.
  7. the route handler calls a service function.
  8. the service applies business rules and calls a repo or Prisma directly.
  9. a presenter may normalize the payload for frontend consumption.
  10. ok() or created() wraps successful responses in { "data": ... }.

Common exceptions:

  • GET /health and GET /api/v1/docs return raw JSON, not the { data } envelope.
  • webhook endpoints return minimal receipt payloads.
  • some public endpoints return graceful fallback responses if the database is unavailable.

Security and Access Model

There are four main access patterns.

Employee JWT

requireCompanyAuth validates a Bearer token with type === "employee", loads the employee, and attaches:

  • req.employee
  • req.company
  • req.companyId

This is the default for dashboard/company management routes.

Tenant Resolution

requireTenant reloads the company from req.companyId and guarantees the tenant context exists. Company-scoped modules then query by companyId.

Subscription Guard

requireSubscription blocks company routes when the company status is PENDING or SUSPENDED. Public site and carplace routes are intentionally not behind this guard.

Role-Based Access Control

Employee roles are hierarchical:

  • OWNER
  • MANAGER
  • AGENT

Admin roles are hierarchical:

  • SUPER_ADMIN
  • ADMIN
  • SUPPORT
  • FINANCE
  • VIEWER

Renter JWT

requireRenterAuth validates a Bearer token with type === "renter" and attaches req.renterId.

optionalRenterAuth is used on carplace routes so public reads still work without a token.

API Key

requireApiKey accepts a company public API key in x-api-key. This is the low-friction integration path for public company-site style calls.

Implementation Pattern

The API codebase is organized per module. The most common pattern is:

  • *.routes.ts: route definitions and middleware composition
  • *.schemas.ts: Zod input validation
  • *.service.ts: business rules and orchestration
  • *.repo.ts: Prisma access helpers
  • *.presenter.ts: response shaping

The vehicles module is representative:

  • vehicle.routes.ts mounts auth, tenant, and subscription guards once for the router.
  • route handlers validate input and call service functions.
  • vehicle.service.ts contains business rules such as publish-state normalization and availability calculation.
  • vehicle.repo.ts owns the Prisma queries and enforces tenant filters in reads.
  • vehicle.presenter.ts shapes the response returned to clients.

Some modules split specialized workflows into additional services instead of one large file. The reservations module is the clearest example:

  • reservation.service.ts handles CRUD and list behavior
  • reservation.lifecycle.service.ts handles confirm/checkin/checkout/close/extend/cancel transitions
  • reservation.inspection.service.ts handles pickup/dropoff inspections
  • reservation.additional-driver.service.ts handles approval workflows
  • reservation.photo.service.ts handles pickup/dropoff photo uploads
  • reservation.document.service.ts generates contract and billing payloads

Route Groups

The table below describes the current route groups mounted in app.ts.

Base path Access Purpose Notes
/health Public Process health Non-versioned utility endpoint
/docs Public Swagger UI Backed by openapi.ts
/api/v1/openapi.json Public OpenAPI JSON Generated from Zod schemas and manual path entries
/api/v1/auth/company Public Company signup complete-signup and verify-email now return disabled errors
/api/v1/auth/employee Public plus employee JWT Employee login, password reset, profile, language Dashboard/staff authentication path
/api/v1/auth/renter Renter JWT for profile routes Renter profile and push token signup and login are currently disabled in this codebase
/api/v1/admin Admin JWT Platform operations Includes company admin, billing, subscriptions, pricing, audit, and admin-user management
/api/v1/carplace Public, optional renter JWT Carplace discovery and reservation intake Designed for discovery and lead capture across companies
/api/v1/site Public White-label company site APIs Drives each company-branded booking site
/api/v1/subscriptions Mixed Plan listing, webhooks, subscription lifecycle Public, webhook, and authenticated sub-routers share the same prefix
/api/v1/vehicles Employee JWT + tenant + subscription Fleet management Includes photos, status, calendar blocks, maintenance, and availability
/api/v1/reservations Employee JWT + tenant + subscription Reservation operations Central booking lifecycle for internal staff
/api/v1/team Employee JWT + tenant + subscription Employee/team management Owner-controlled invite, role, activation, removal
/api/v1/customers Employee JWT + tenant + subscription Company CRM and license handling Includes protected license-image retrieval and approval flows
/api/v1/offers Employee JWT + tenant + subscription Promotional offers Company marketing and promo codes
/api/v1/analytics Employee JWT + tenant + subscription Dashboard metrics and reports Includes summary, dashboard, source, and report routes
/api/v1/notifications Employee or renter JWT depending on route Notification inbox and preferences Separate company and renter surfaces
/api/v1/companies Employee JWT + tenant + subscription Company profile and settings Brand, domains, contract settings, insurance policies, pricing rules, accounting, API key
/api/v1/payments Mixed Rental payment webhooks and company payment actions Webhooks are public; operational routes are company-authenticated
/api/v1/reviews Employee JWT + tenant + subscription Review moderation Company-side review visibility and replies
/api/v1/complaints Employee JWT + tenant + subscription Complaint handling Internal complaint tracking
/api/v1/webhooks Public External webhook receivers Currently includes /clerk placeholder

Functional Breakdown by Module

Company and employee onboarding

  • POST /api/v1/auth/company/signup creates the company tenant, owner employee, default subscription state, and related baseline records in one transaction.
  • The signup flow generates a unique company slug, hashes the owner password, starts a 30-day trial window, and sends account-created notifications.
  • Employee auth supports login, forgot-password, reset-password, profile read, and language updates.

Company profile and business configuration

The companies module owns tenant-level settings:

  • company identity
  • brand settings and asset upload
  • subdomain/custom-domain configuration
  • contract settings used by rental documents
  • insurance policies
  • pricing rules
  • accounting settings
  • public API key generation/regeneration

This module is where most long-lived company configuration lives.

Fleet management

The vehicles module handles:

  • CRUD for vehicles
  • publish/unpublish
  • explicit status updates
  • photo upload and deletion
  • availability checks
  • monthly vehicle calendar events
  • manual calendar blocks
  • maintenance logs

Uploads are persisted through the storage service, then surfaced under /storage/*.

Reservations and rental workflow

The reservation aggregate is the center of the operational API.

Important transitions:

  1. create draft reservation
  2. confirm reservation after license checks
  3. check in and move the reservation to ACTIVE
  4. check out and mark the reservation COMPLETED
  5. close the reservation operationally after post-rental tasks are done

The lifecycle service also handles:

  • extensions with conflict checks
  • cancellations
  • vehicle status synchronization
  • review token generation
  • review request email dispatch on close

The reservations module also owns:

  • contract and billing payload generation
  • pickup/dropoff inspections
  • additional-driver approval
  • pickup/dropoff photo upload

Customer CRM and license compliance

Customers are company-scoped CRM records, even when a renter identity also exists. The customers module supports:

  • customer CRUD
  • flag/unflag flows
  • license image upload and protected retrieval
  • license validation and manager approval

This is intentionally separate from renter identity because one renter may interact with multiple companies while each company still needs its own operational customer record.

Carplace and public site

The public surface is split in two modules on purpose.

carplace is the cross-company discovery layer:

  • featured/public offers
  • carplace cities
  • listed companies
  • vehicle search
  • carplace reservation intake
  • review submission by token
  • company public pages under /:slug

site is the white-label company booking API:

  • company brand payload
  • published vehicle catalog
  • booking options
  • date-range availability
  • promo validation
  • booking creation
  • payment initialization
  • booking lookup
  • company contact form

Both modules deliberately include graceful degradation paths when the database is unavailable so public pages can fail softer than internal operations.

Payments

Rental payments are company-side operational payments, distinct from SaaS subscription billing.

The payments module handles:

  • AmanPay and PayPal rental webhooks
  • listing payments by company or reservation
  • initializing online charges
  • capturing PayPal orders
  • recording manual payments
  • refunding successful online payments

Payment success updates both the payment record and the reservation paid amount.

Subscriptions and SaaS billing

The subscriptions module owns the platform billing lifecycle:

  • public plan/provider/feature listing
  • company subscription read endpoints
  • trial start
  • checkout
  • plan changes
  • cancellation/resume/reactivation
  • provider webhooks
  • admin overrides

It also exposes background-job style functions for:

  • trial expiration
  • payment-pending timeout
  • past-due timeout
  • suspension timeout
  • period-end cancellation

Notifications

Notifications are multi-channel and multi-audience:

  • company inbox
  • renter inbox
  • unread counts
  • preferences per type and channel
  • notification history

Templates and preference records live in the database; delivery orchestration lives in services/notificationService.ts.

Admin surface

The admin router is broad because it covers platform operations across multiple domains:

  • admin authentication and 2FA
  • company listing, inspection, status changes, deletion, impersonation
  • renter blocking/unblocking
  • platform metrics
  • notification inspection
  • audit log access
  • admin-user and permission management
  • billing account and billing invoice operations
  • pricing config, plan features, and promotions
  • subscription overrides and extensions
  • carplace homepage configuration

This is the only route group allowed to work across tenants.

Important Workflow Examples

1. Company signup

POST /api/v1/auth/company/signup

  • validates the payload with Zod
  • checks for duplicate company and owner email addresses
  • generates a unique slug
  • hashes the owner password
  • creates the tenant, owner employee, and initial subscription state in one transaction
  • sends localized account-created notifications

2. Public booking from a company site

POST /api/v1/site/:slug/book

  • resolves the company from the slug
  • verifies vehicle availability for the requested date range
  • upserts the company-scoped customer record
  • computes total days and base rental price
  • applies promo code discount if present
  • applies pricing rules
  • stores a draft reservation
  • attaches reservation insurance snapshots and additional-driver snapshots
  • triggers license validation flags

Payment is then started with POST /api/v1/site/:slug/booking/:id/pay.

3. Reservation lifecycle after booking

  • reservation starts in DRAFT
  • staff confirms it after license compliance checks
  • vehicle status moves to RESERVED
  • checkin moves reservation to ACTIVE and vehicle to RENTED
  • checkout moves reservation to COMPLETED, records mileage, and generates a review token
  • close marks the operational workflow complete and can send the review request email

Error Handling and Response Shape

The API standardizes error handling in http/errors/errorMiddleware.ts.

Special handling exists for:

  • Zod validation errors -> 400 validation_error
  • Prisma P2025 -> 404 not_found
  • Prisma P2002 -> 409 conflict
  • custom AppError subclasses -> explicit status and machine-readable code

Successful route handlers usually return:

{
  "data": {}
}

Common deviations:

  • /health
  • /api/v1/docs
  • webhook receipt payloads
  • file downloads such as admin invoice PDFs

Documentation Artifacts

There are two documentation layers in the codebase:

  1. runtime API docs from Swagger/OpenAPI at /docs and /api/v1/openapi.json
  2. this markdown document, which explains design decisions, route grouping, and request flow

The OpenAPI file is useful for request/response contracts, but it is not yet a perfect mirror of every newer route. This document should be updated together with app.ts, route files, and the OpenAPI generator whenever route groups change.