AI Commerce & Procurement Platform for every service vertical, built to the
OKX "Revenue Rocket" brief. A customer (human or AI agent) describes what
they need in one sentence, the platform searches vetted providers, verifies
availability, quotes a price with a 10 % platform fee, and issues a real
hosted checkout. Payment webhooks flip the booking to
AWAITING_PROVIDER_CONFIRMATION; the provider accepts and the money settles.
The whole loop is drivable by an AI agent through three REST tools exposed
under /api/agent/*, or by a human via the in-app Vercel-AI-SDK copilot.
The winning story: an AI agent executes real commerce end-to-end and the platform captures a 10 % take.
The same engine books photographers, caterers, consultants, agencies,
venues, tradespeople, event vendors, and creative or performing talent —
every category is a first-class provider vertical. See
spec/10_generic_refactor.md for the
generic-terminology reference.
Revenue Rocket is a generic AI-native commerce & procurement platform.
Providers (photographers, chefs, consultants, agencies, venues, vendors,
creative talent, …) onboard into workspaces owned by an organization; each
workspace gets its own catalog, branding, coupons, integrations, API keys,
and AI agent. The full product vision lives in
spec/07_platform_prd.md; this section is the
implementation quick-start. The generic booking flow above is unchanged —
the platform layer is purely additive.
Concepts
- Organization — the tenant root (a brand, agency, marketplace, or enterprise). Owns one or more workspaces and carries branding.
- Workspace — the tenant boundary. Owns its providers, services, categories, requests/bookings, coupons, API keys, integrations, and a branded AI agent.
- Membership — user↔workspace RBAC:
OWNER · ADMIN · FINANCE · MANAGER · MEMBER · PROVIDER · GUEST. - API keys — per-workspace credentials (
LIVE | TEST | SANDBOX) minted from the org dashboard; verified by prefix lookup + constant-time hash. - Coupons — workspace-scoped
PERCENT/FLATdiscounts, applied to gross before the platform fee. - Integrations — Stripe · Razorpay · Dodo · OKX · Slack · Discord · Google Calendar · Zapier · HubSpot · Salesforce · Webhooks — enabled per-workspace.
- Per-org AI agent — same booking engine, per-workspace prompt and branding, so every tenant gets its own "book any service" copilot.
Endpoints — /api/org/*
Every route accepts either an authenticated session or a workspace API
key via the x-workspace-key: rr_live_… (or rr_test_…) header. Machine
callers (external agents, integrations, OKX.AI) use the header.
| Path | Purpose |
|---|---|
/api/org/workspaces |
List / create workspaces for the org |
/api/org/members |
Invite, list, and role-update memberships |
/api/org/providers |
Workspace-scoped provider CRUD |
/api/org/services |
Bookable service offerings per provider |
/api/org/categories |
Category taxonomy (Photography, Catering, …) |
/api/org/requests |
Requests → bookings for procurement flows |
/api/org/coupons |
Mint / list / redeem coupons |
/api/org/api-keys |
Mint (returns plaintext once), list, revoke |
/api/org/integrations |
Enable Stripe / OKX / Slack / … per workspace |
/api/org/agent |
Per-workspace branded copilot endpoint |
Domain code lives under lib/platform/* (workspace context, API-key
verification, RBAC, coupons, categories, integrations); it wraps the same
lib/booking + lib/payments engines every provider vertical uses.
Seed the demo org
The main seed populates a mixed roster of providers and demo bookers; layer
the platform seed on top to create categories, link providers to their
categories, and mint a demo organization (boAt, workspace boat) with an
OWNER member, one TEST + one LIVE API key, a WELCOME20 coupon, and
Stripe + OKX integrations. Idempotent — safe to re-run.
npm run db:seed
npx dotenv -e .env.local -- npx tsx prisma/seed-platform.tsLog in as boat-owner@demo.com, jump into /org, and drive the multi-tenant
story: create a coupon, mint an API key, hit /api/org/providers from
curl, watch the same 10 % take-rate settle inside that workspace.
For the full PRD (positioning, entity naming, dashboards, migration
recommendation), read spec/07_platform_prd.md.
| Layer | Technology |
|---|---|
| App framework | Next.js 16 (App Router, Server Components, async request APIs) |
| Language / typing | TypeScript 5 (strict) + Zod v4 validators |
| Data | Prisma 6 + Postgres 16 |
| Search | Meilisearch v1.10 (fuzzy match on providers, filtered by city / budget) |
| Auth | Auth.js v5 (next-auth@5.0.0-beta) with Google OAuth |
| Payments (pluggable) | Stripe · Razorpay · Dodo Payments — swap via PAYMENT_PROVIDER |
| AI agent surface | Vercel AI SDK v6 (streamText + tools) — same tools exposed as REST |
| Styling | Tailwind CSS v4 (@theme inline tokens) + shadcn/ui + tw-animate-css |
| Testing | Vitest 3 (unit + API), @playwright/test (E2E smoke) |
Single Next.js app — no monorepo. Domain code lives under lib/ and is
shared between the OKX agent endpoints and the in-app copilot so validation,
availability, fee math, and DB writes are guaranteed to behave identically.
app/
(marketing)/ Landing pages
(auth)/ Split-screen /login, /signup, /forgot-password, /reset-password,
/verify-email, /join, /dev-login (legacy /sign-in,/sign-up redirect)
(provider)/dashboard Provider dashboard (profile, availability, bookings, earnings)
(booker)/ Discover + provider detail
discover/ Copilot + search results
providers/[id]/ Canonical provider detail page
(org)/org/ Organization dashboard (workspace-scoped)
(portal)/p/ Branded per-workspace public booking page
bookings/[id]/ Booking detail (post-checkout)
checkout/[id]/ Fallback in-app checkout (dev without provider keys)
api/
agent/ OKX agent tool endpoints (secured by x-okx-agent-key)
org/ Workspace-scoped platform APIs (session OR x-workspace-key)
webhooks/[p]/ Multi-provider payment webhook (stripe|razorpay|dodo)
chat/ In-app copilot streaming endpoint
providers/ ProviderProfile CRUD (session-auth)
availability/ bookings/ analytics/ CRUD + revenue dashboards
lib/
booking/ Shared engine — discover, checkAvailability, initiate
payments/ Provider adapter interface + fees.ts + registry
meilisearch/ Index + search helpers
validators/ Zod schemas (single source of truth for wire contracts)
analytics/ Revenue event ledger
auth/ Auth.js v5 config + prisma adapter
platform/ Workspace context, API keys, RBAC, coupons, integrations
prisma/
schema.prisma Booking lifecycle, fees, webhook ledger, analytics.
First-class names throughout: `ProviderProfile`,
`Role.PROVIDER`, `BookingStatus.AWAITING_PROVIDER_CONFIRMATION`.
DB tables, columns, and enum values were physically
renamed in migration `20260705173629_provider_full_rename`
(RENAME + UPDATE only — data preserved).
seed.ts 14+ providers across several verticals, availability, 2 demo bookers
spec/ Product + engineering specs (source of truth)
tests/
unit/ Fee math, state machine, validators
api/ Agent contract tests + webhook idempotency + race
e2e/ Playwright smoke pack
Prerequisites: Node 20+, Docker (for Postgres + Meilisearch), and a POSIX shell.
# 1) Install deps (Next 16 / React 19 peers require the flag).
npm install --legacy-peer-deps
# 2) Start Postgres + Meilisearch.
docker compose up -d
# ...or point DATABASE_URL / MEILISEARCH_HOST at your own instances.
# 3) Copy env and fill secrets.
cp .env.example .env.local
# Minimum for a local demo:
# DATABASE_URL, AUTH_SECRET, PAYMENT_PROVIDER=stripe,
# MEILISEARCH_HOST, MEILISEARCH_API_KEY, OKX_AGENT_API_KEY
# 4) Migrate the DB and seed demo data.
npx dotenv -e .env.local -- npx prisma migrate dev
npm run db:seed
# 5) Sync the provider search index.
npm run search:sync
# 6) Boot the app.
npm run dev
# → http://localhost:3000| Variable | Purpose |
|---|---|
DATABASE_URL |
Postgres connection string |
AUTH_SECRET |
Auth.js JWT secret (openssl rand -base64 32) |
NEXTAUTH_URL |
App base URL (http://localhost:3000 in dev) |
AUTH_GOOGLE_ID / _SECRET |
Google OAuth client (optional in dev) |
NEXT_PUBLIC_APP_URL |
Public origin used in checkout success/cancel URLs |
PAYMENT_PROVIDER |
stripe | razorpay | dodo |
PLATFORM_FEE_BPS |
Fee in basis points (default 1000 = 10 %) |
DEFAULT_CURRENCY |
usd | inr |
STRIPE_SECRET_KEY etc. |
Stripe / Razorpay / Dodo credentials |
MEILISEARCH_HOST / _API_KEY |
Meilisearch instance |
MEILISEARCH_INDEX_PROVIDERS |
Provider search index name (defaults to providers) |
AI_PROVIDER |
openai or anthropic (in-app copilot only) |
OPENAI_API_KEY / ANTHROPIC_ |
LLM keys for the copilot |
OKX_AGENT_API_KEY |
Shared secret required on every /api/agent/* call |
OKX_AI_API_KEY, _BASE_URL |
(optional) OKX-hosted model endpoint |
.env.example in the repo lists every variable with its canonical name.
The platform Role enum has three values (BOOKER, PROVIDER, ADMIN)
stored under those exact names in the DB:
BOOKER— customer discovering + booking services (/discover).PROVIDER— manages profile, availability, bookings, earnings (/dashboard).ADMIN— platform-level admin (fees / moderation, schema-only MVP).
Organizations are represented via workspace membership
(WorkspaceRole: OWNER · ADMIN · FINANCE · MANAGER · MEMBER · PROVIDER · GUEST) on the platform-tenant layer, not a top-level Role value.
Sign-in / sign-up — the split-screen /login page is a single linear
form for existing users; /signup carries the intent picker for new
accounts and routes each intent to the matching dashboard:
Intent (on /signup) |
Role provisioned | Lands on |
|---|---|---|
| Buy / book | Role.BOOKER |
/discover |
| Offer services | Role.PROVIDER |
/dashboard |
| Set up an organization | Org + Workspace + OWNER | /org |
Join a team (via /join) |
Role.BOOKER + Membership |
/org |
Legacy /sign-in and /sign-up URLs redirect to /login and /signup
respectively. See spec/10_generic_refactor.md
for the full role / route mapping.
Revenue Rocket runs Auth.js v5 with three sign-in surfaces layered on top
of each other — a full email/password Credentials provider (id: "password"),
Google OAuth, and a dev-only passwordless shortcut for local demos. The
complete plan lives in
spec/13_authentication.md; this section is the
operator quick-start.
Every user-facing auth surface is a clean split-screen page under
app/(auth)/ — brand on the left, form on the right. Legacy /sign-in and
/sign-up URLs redirect to /login and /signup.
| Page | Purpose |
|---|---|
/login |
Linear log-in form (email + password, Google, "forgot?" link). |
/signup |
Intent-picker sign-up (buy / offer / setup / join). |
/forgot-password |
Request a password-reset email. |
/reset-password |
Consume a reset token and set a new password. |
/verify-email |
Consume an email-verification token; offers resend on expiry. |
/join |
Accept a workspace invite by code (signup or login inline). |
/dev-login |
Passwordless shortcut for seeded users; disabled in production. |
Password rows sit in User.passwordHash (bcryptjs cost 12) alongside the
existing OAuth Account rows, so a single user can hold both a Google
identity and a password. All flows below run in the Node runtime, validate
with Zod (lib/validators/auth.ts), and are non-enumerating: register /
forgot return generic success regardless of whether the email is on file.
| Route | Body | Behaviour |
|---|---|---|
POST /api/auth/register |
{ email, password, name, intent, organizationName? } |
Creates the user (+ Organization/Workspace/OWNER Membership when intent="organization"), issues an EmailVerificationToken. Returns 201 { ok, needsVerification, devVerifyUrl? }. |
POST /api/auth/verify-email |
{ token } |
Consumes an unexpired verification token, stamps User.emailVerified = now(), deletes sibling tokens. |
POST /api/auth/resend-verification |
{ email } |
Idempotent — mints a fresh verification link for an unverified user. Generic 200. |
POST /api/auth/forgot-password |
{ email } |
Issues a PasswordResetToken (1h TTL). Generic 200; returns devResetUrl in non-production. |
POST /api/auth/reset-password |
{ token, password } |
Rotates User.passwordHash, marks the token usedAt, wipes sibling reset tokens, verifies email if it was still unverified. |
POST /api/auth/register accepts an intent selector that fans out into the
sign-up path spec/13 §Flows describes:
buyer(default) — plainRole.BOOKERuser; lands on/discoverafter verification.business—Role.PROVIDERuser; lands on/dashboardto complete the provider profile.organization— user +Organization+Workspace+OWNER Membershipcreated in a single transaction; lands on/org.organizationNameis required in this branch.member— plain user record;Membershipis attached separately when the user opens/join?code=…and accepts the invite.
Login uses Auth.js directly (signIn("password", { email, password })) —
there is no custom login endpoint. The provider enforces the verified-email
gate by throwing "email_not_verified" so the UI can offer a resend link.
Google OAuth stays wired up whenever AUTH_GOOGLE_ID / AUTH_GOOGLE_SECRET
are set — pick "Continue with Google" on /login. Registering with the
same email as an existing OAuth-only user links credentials onto that
row rather than creating a duplicate.
For quick local demos without any OAuth keys, open /dev-login and
sign in as any seeded user. Enabled unless ENABLE_DEV_LOGIN=false or
NODE_ENV === "production" — see Dev login.
No SMTP / Resend provider is wired up yet. Until one is:
- Verification and password-reset URLs are
console.loged from the route handler (search for[auth]/[api/auth/…]in the dev server output). - In non-production the same URLs come back inside the JSON response as
devVerifyUrl/devResetUrl, so/signupand/reset-password(and the Vitest suite intests/api/auth-flow.test.ts) can complete the flow without a mailbox.
Wire a real provider before shipping — the TODO markers in
app/api/auth/{register,forgot-password,resend-verification}/route.ts mark
the hand-off point.
Three tools, all under /api/agent/*. Every request MUST carry
x-okx-agent-key: $OKX_AGENT_API_KEY or the server returns 401.
The exact wire contract is documented in
spec/04_okx_agent_tools.md; this section is
the quick-start.
Naming note. The OKX-facing tool names (
search_providers,check_availability,initiate_booking) and their snake_case request fields (provider_id,booker_email, …) are the wire contract; they read fromProviderProfile/User.role = PROVIDERunder the hood (seespec/10_generic_refactor.md).
Fuzzy match against the provider index, filtered by city, budget, and availability on a target date.
curl -s http://localhost:3000/api/agent/search \
-H "Content-Type: application/json" \
-H "x-okx-agent-key: $OKX_AGENT_API_KEY" \
-d '{
"query": "jazz saxophonist",
"location": "Bengaluru",
"max_budget": 1500000,
"target_date": "2026-07-20"
}'Returns at most three top-ranked results in an LLM-friendly shape:
{
"results": [
{
"provider_id": "clx...",
"name": "Aarav Rao",
"specialty": "Jazz Saxophonist",
"city": "Bengaluru",
"base_rate": 1200000,
"currency": "inr",
"rating": 4.8,
"available": true,
"quick_action": "initiate_booking with provider_id"
}
],
"count": 1
}curl -s http://localhost:3000/api/agent/availability \
-H "Content-Type: application/json" \
-H "x-okx-agent-key: $OKX_AGENT_API_KEY" \
-d '{ "provider_id": "clx...", "target_date": "2026-07-20" }'Creates a PENDING_PAYMENT booking and a hosted checkout link. The
transactional re-check inside lib/booking/engine.ts is the double-booking
lock; concurrent calls on the same (provider, date) produce exactly one
success.
curl -s http://localhost:3000/api/agent/book \
-H "Content-Type: application/json" \
-H "x-okx-agent-key: $OKX_AGENT_API_KEY" \
-d '{
"provider_id": "clx...",
"event_date": "2026-07-20",
"event_location": "Bengaluru",
"offer_amount": 1400000,
"booker_email": "demo@example.com",
"event_type": "Wedding",
"duration_mins": 120
}'Response:
{
"booking_id": "clx...",
"checkout_url": "https://checkout.stripe.com/...",
"amount": 1400000,
"platform_fee": 140000,
"currency": "inr",
"status": "PENDING_PAYMENT"
}Error responses are stable and machine-readable:
| Status | Body | When |
|---|---|---|
| 400 | { "error": "invalid_input", "issues" } |
Zod validation failure |
| 401 | { "error": "unauthorized" } |
Missing / wrong x-okx-agent-key |
| 404 | { "error": "provider_not_found" } |
Unknown provider_id |
| 409 | { "error": "date_unavailable" } |
Date blocked, booked, or race lost |
| 422 | { "error": "below_minimum", "minimum" } |
offer_amount < provider.baseRate |
npm run dev(ordocker compose up -d && npm run dev).- Open http://localhost:3000 — pitch the landing (Revenue Story section spells out the 10 % take).
- Hit
/discover, drop the canonical prompt into the copilot:"Jazz saxophonist in Bengaluru under ₹15,000 for 20 July 2026" The copilot calls
search_providers, presents three matches, theninitiate_bookingwith the top provider. - Follow the checkout link. In dev without Stripe keys the app falls back
to the in-app
/checkout/[id]page which lets you simulate payment — this posts a fakecheckout.session.completedback into the webhook. - Booking flips to
AWAITING_PROVIDER_CONFIRMATION. Sign in as the provider, accept from/dashboard/bookings, watch the earnings tile move. - Open
/dashboard/earnings(or/api/analytics/revenue) — the 10 % platform fee is captured against theplatform_fee_capturedanalytics event.
The same three tools are hittable from an external OKX agent by pointing it
at the curl examples above — the copilot flow and the agent flow share
one lib/booking/engine.ts.
PAYMENT_PROVIDER selects the default provider used by
getPaymentProvider(); adapters live in lib/payments/providers/*.ts and
all implement a common interface (createCheckout, verifyAndParse,
refund — see lib/payments/types.ts).
| Provider | Enable by setting |
|---|---|
| Stripe | PAYMENT_PROVIDER=stripe + STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET |
| Razorpay | PAYMENT_PROVIDER=razorpay + RAZORPAY_KEY_ID, _SECRET, _WEBHOOK_SECRET |
| Dodo | PAYMENT_PROVIDER=dodo + DODO_API_KEY, DODO_WEBHOOK_SECRET |
The webhook handler at app/api/webhooks/[provider]/route.ts dispatches by
URL segment, so all three can be enabled simultaneously — point each
provider's webhook at /api/webhooks/stripe, /api/webhooks/razorpay,
/api/webhooks/dodo respectively.
Idempotency: the WebhookEvent ledger (unique on eventId) short-
circuits replays before they touch the state machine or the
PaymentTransaction table.
# Unit + API contract + DB-guarded (Vitest 3).
npx dotenv -e .env.local -- npm test
# E2E smoke (Playwright — boots `next dev` if nothing is on :3000).
npx dotenv -e .env.local -- npm run test:e2eWhat's covered:
tests/unit/fees.test.ts— 10 % split, rounding, edge cases.tests/unit/state-machine.test.ts— every legal + illegal transition.tests/unit/validators.test.ts— Zod schemas for the agent tools.tests/api/agent-search.test.ts— auth, validation, happy path (mocked engine).tests/api/agent-book.test.ts— 401 / 400 / 404 / 409 / 422 / 200.tests/api/webhook-idempotency.test.ts— replays don't double-writePaymentTransaction(DB-backed; skipped when Postgres is unreachable).tests/api/double-booking.test.ts— concurrentinitiate_bookingon the same(provider, date)produces exactly one winner (DB-backed).tests/e2e/smoke.spec.ts— landing renders,/discoverrenders,/dashboardredirects to/loginwhen signed out.
The spec/ directory holds the deterministic source of truth used to
coordinate parallel agent work — read the files in numeric order:
00_STACK.md— every pinned dependency and its version-specific rules.01_product_requirements.md— roles, flows, lifecycle, pricing.02_system_architecture.md— layers, data flow, tenancy model.03_database_schema.md— Prisma schema (ProviderProfile,Role.PROVIDER, …).04_okx_agent_tools.md— the authoritative agent wire contract.05_test_suite.md— the coverage matrix this test suite fulfils.06_design_and_ux.md— design tokens, layout, and screen-by-screen UX.07_platform_prd.md— multi-tenant platform vision + terminology mapping.08_enterprise_platform.md— full enterprise PRD (modules, MVP scope, roadmap).09_procure_to_pay_prd.md— procure-to-pay module PRD.10_generic_refactor.md— generic-terminology rename map (authoritative).11_positioning_and_copy.md— AI Commerce & Procurement positioning + copy.13_authentication.md— email/password + Google + dev-login auth surface.
Beyond the generic provider marketplace, Revenue Rocket ships an AI-Native Commerce & Service Orchestration Platform layer. It is fully additive — the marketplace workflow above is unchanged.
- Workspaces / Organizations — every business gets a tenant workspace with
RBAC (
OWNER, ADMIN, FINANCE, MANAGER, MEMBER, PROVIDER, GUEST). - Organization dashboard
/org/[workspaceId]— Overview, Providers, Services, Requests, Members, Teams, Procurement (approvals), Campaigns, Affiliates & Referrals, Coupons, Invoices, API Keys, Integrations, AI Agent config, Branding, Audit Log, Check-in, Settings. - Branded portals — public per-workspace booking page at
/p/[slug](demo:/p/boat), themed byPortalConfig. - QR operations — bookings issue a
QRPass(/pass/[token]), staff check-in at/org/[workspaceId]/checkin, tracked asAttendance. - Growth — coupons/campaigns + affiliate/referral links (
/r/[code]→ attributed/discover). - Developer platform — per-workspace API keys (LIVE/TEST/SANDBOX),
webhooks,
x-workspace-keyauth on/api/org/*. - Security — workspace isolation, RBAC guards,
AuditLog, hashed + rotatable API keys.
Seed the demo org (boAt):
npx dotenv -e .env.local -- npx tsx prisma/seed-platform.ts then
... prisma/seed-enterprise.ts.
For local demos, open /dev-login to sign in as any seeded user
(provider → /dashboard, org owner → /org, booker → /discover) via a
dev-only passwordless Credentials provider. Disabled in production; set
ENABLE_DEV_LOGIN=false to force off. Configure Google OAuth for real auth.