Skip to main content

Architecture

Three independent services, one shared identity layer, one billing rail.

System diagram

What lives where

ConcernRepoNotes
Marketplace UI (catalog, pricing, dashboard, admin)fleapo-marketplaceReact/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-serviceNestJS, 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 clientfleapo-marketplace/src/api/Produced by orval from openapi.json. The same openapi.json feeds this docs site.
Docs sitemarketplace-docsDocusaurus 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:

  1. OAuth token endpoints — code exchange at /api/auth/oauth2/token, userinfo at /api/auth/oauth2/userinfo, JWKS at /api/auth/jwks. Agents use the genericOAuth plugin in better-auth (or any OIDC library).
  2. Subscription + usageGET /me/subscriptions/:agentId to read tier, GET /me/agents/:agentId/usage to inspect quotas, POST /me/agents/:agentId/usage/:metricSlug/consume before a chargeable action, POST .../release to 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/checkout returns a Stripe URL).
  • Stripe calls the marketplace back at POST /webhooks/stripe for checkout.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.