Platform API
platform-api is the main BFF. It owns everything that isn’t AI or document processing: resumes and their versions, sharing & collaboration, users & tokens, notifications, stats, analytics ingest, Clerk/Svix webhooks, billing & subscription, and the account-pool admin console (managing relay keys, channels, and dynamic config). It is also the single Prisma migration writer.
- Base URL (dev):
http://localhost:3111 - Global prefix:
/api - Swagger UI:
/api/docs - Auth:
ClerkAuthGuardper controller — send a PAT or Clerk JWT
Sections
The response envelope
Every successful response is wrapped by a global interceptor:
{ "code": 200, "data": <your payload>, "message": "success", "timestamp": "2026-06-19T08:00:00.000Z" }Always read your payload from data. Errors are shaped by a global exception filter — see Errors. Full details in Response envelope.
Validation
Request bodies are validated with class-validator and a global ValidationPipe({ transform: true, whitelist: true }):
- Unknown fields are stripped — sending extra keys won’t error, they just won’t persist.
- Type mismatches and missing required fields return
400with a list of messages.
Resume content is a serialized JSON string, not a nested object. The backend treats it opaquely — your app owns the resume schema. Send content: JSON.stringify(resumeDoc) and parse it back on read.
Rate limits
A global ThrottlerGuard is active, and some routes tighten it further with @Throttle. Notable per-route limits:
| Route | Limit |
|---|---|
PATCH /api/resumes/:id | 60 / min |
POST /api/resumes/:id/versions | 60 / min |
POST /api/resumes/:id/duplicate | 10 / min |
POST /api/users/feedback | 3 / min |
POST /api/analytics/events | 120 / min |
Exceeding a limit returns 429 Too Many Requests. Back off and retry.
Internal architecture
Every platform-api module shares one flat feature-module shape: <name>.controller.ts + <name>.service.ts + <name>.module.ts (+ dto/). Services inject PrismaService directly — there are zero repositories / ports / adapters in the codebase. Complexity only adds files, never changes the structure.
Extra files appear only when a real signal calls for them:
- Domain invariants (permissions / state machine / optimistic lock) →
<name>.entity.ts(a pure domain class) +<name>.errors.ts. - Domain events →
<name>.event.ts, with subscribers wiring up in a service’sonModuleInit.
resumes is the codebase’s one “golden template” carrying entity / errors / event:
- Optimistic locking:
Resumehas arevisionfield. An update carryingbaseRevisionruns a conditionalupdateMany; a 0-row match throwsResumeConflictError→409. - Field-level encryption: resume
contentis stored AES-256-GCM-encrypted (ENCRYPTION_SECRET_KEY) and decrypted on read. - Domain events: adding a comment emits
CommentAddedEvent, whichnotificationssubscribes to and fans out — decoupled from the request path.
platform-api owns the resume/user/notification/analytics/observability models — plus the billing & subscription (Plan, Subscription, Order, CreditWallet, CreditTransaction, BillingOperation, …), AI-quota, reconciliation, Clerk-mirror, and dynamic-config (AppConfig) models. For the full grouped list and cross-service conventions see Architecture & conventions.
Endpoint map
| Area | Endpoints |
|---|---|
| Resumes | /api/resumes, /api/resumes/mine, /api/resumes/:id, /api/resumes/:id/versions, /api/resumes/:id/duplicate |
| Sharing | /api/resumes/shared/:shareId, /api/resumes/shared/:shareId/comments |
| Collaboration | /api/resumes/:id/collaborators, /api/resumes/:id/comments |
| Users | /api/users, /api/users/:id, /api/users/sync, /api/users/feedback |
| Tokens | /api/users/me/personal-access-tokens |
| Billing | /api/billing/plans, /api/billing/subscriptions, /api/billing/subscription, /api/billing/wallet, /api/billing/ai-entitlement |
| Admin billing | /api/admin/plans, /api/admin/subscriptions, /api/admin/customers, /api/admin/wallets/:userId/adjust, /api/admin/orders |
| Notifications | /api/notifications, /api/notifications/:id/read |
| Stats | /api/stats, /api/stats/advanced, /api/stats/logs |
| Analytics | /api/analytics/events |
| Webhooks | /api/webhooks/clerk (Svix-signed; not for direct calls) |