Architecture
Three independent services, one shared identity layer, one billing rail.
System diagram
What lives where
| Concern | Repo | Notes |
|---|---|---|
| Marketplace UI (catalog, pricing, dashboard, admin) | fleapo-marketplace | React/Vite, Tailwind, shadcn, Framer Motion. Mock data in src/data/agents.ts is being replaced by the generated API client in src/api/ (Orval from openapi.json). |
| Marketplace backend (catalog, auth, billing) | marketplace-fleapoai-service | NestJS, Drizzle ORM, Postgres, better-auth (OIDC provider + admin plugin), Stripe SDK, posthog-node for audit logs. |
| Reference agent (Track A) | agent-poc (VoxaAI) | Express, MongoDB Atlas, better-auth (genericOAuth plugin as RP), Puppeteer, ElevenLabs, LiveAvatar. |
| Generated API client | fleapo-marketplace/src/api/ | Produced by orval from openapi.json. The same openapi.json feeds this docs site. |
| Docs site | marketplace-docs | Docusaurus 3, OpenAPI plugin, Mermaid, local search. |
Boundaries and what crosses them
Browser ↔ Marketplace
The marketplace SPA talks to the backend over fetch with credentials: 'include'. Sessions are cookies issued by better-auth. CORS is locked to FRONTEND_URL (comma-separated allow-list), credentials: true, so wildcard origins are not allowed.
Browser ↔ Agent
Each agent runs its own frontend at its own domain (voxaai.com, etc.) talking to its own backend. The user signs into that frontend by being redirected to the marketplace's /api/auth/oauth2/authorize endpoint.
Agent backend ↔ Marketplace backend
Server-to-server. Two purposes:
- OAuth token endpoints — code exchange at
/api/auth/oauth2/token, userinfo at/api/auth/oauth2/userinfo, JWKS at/api/auth/jwks. Agents use thegenericOAuthplugin in better-auth (or any OIDC library). - Subscription + usage —
GET /me/subscriptions/:agentIdto read tier,GET /me/agents/:agentId/usageto inspect quotas,POST /me/agents/:agentId/usage/:metricSlug/consumebefore a chargeable action,POST .../releaseto refund.
Marketplace ↔ Stripe
- The marketplace creates Stripe Products and Prices when an admin creates a paid pricing tier (
POST /agents/:agentId/pricing). - The marketplace redirects users to Stripe Checkout when they select a paid tier (
POST /agents/:agentId/pricing/:tierId/checkoutreturns a Stripe URL). - Stripe calls the marketplace back at
POST /webhooks/stripeforcheckout.session.completed,customer.subscription.{created,updated,deleted},invoice.payment_succeeded,invoice.payment_failed. - All inbound events are deduped via
stripe_events.stripeEventId(unique).
Marketplace ↔ PostHog
Every request is enqueued by AuditLogMiddleware, then AuditLoggerService flushes batches to POST ${POSTHOG_HOST}/batch/. PostHog is only for audit telemetry — no product analytics ride on this path.
What does NOT exist in the architecture
- No message queue between marketplace and agents.
- No marketplace → agent webhook. The marketplace never initiates a call to an agent service.
- No marketplace-side agent runtime, GPU pool, inference router, sandbox, or job scheduler.
- No shared Postgres between the marketplace and an agent. The marketplace database is private to the marketplace.
Request lifecycle (marketplace backend)
The order is load-bearing. AuthMiddleware must run before guards because guards read req.user. LoggingInterceptor must run first because the others read requestId. ApiResponseInterceptor before ErrorHandlingInterceptor because the latter uses catchError, which only fires on thrown exceptions.
Continue to the Glossary to lock down the vocabulary, or jump straight to Add a New Agent.