Domain · Specifications

Aphrodite Domain — Technical Specifications

Aphrodite is a very large domain.

14sections30 minread

On this page

Technical specification of the implemented Aphrodite surface: the adult live-streaming / creator-economy domain. This document is grounded in code — the Prisma data model, the shared event contracts, the core constants and types, the streaming and payment libraries, and the live Hono routes. Sections marked (planned) describe library directories that exist as feature code but are not yet wired into a deployed service. Where the spec describes types or endpoints, the cited file is the source of truth.

This document is the authoritative technical reference for engineers integrating with or extending Aphrodite. It covers the Prisma data model, shared event contracts, streaming and payment subsystem types, application API surface, device protocols, compliance subsystem, configuration, and the full library catalogue. The companion documents are features.md (product-level feature descriptions) and architecture.md (system topology and cross-domain dependencies).


Scope and Implementation Status#

Aphrodite is a very large domain. The repository contains:

  • libs/aphrodite/* — 190 library directories. Roughly 186 contain real, substantial TypeScript implementations under src/. Four are thin scaffolds (@aphrodite/chat, @aphrodite/payment, @aphrodite/safety, @aphrodite/sdk): each exports a real types.ts but its functional subdirectories contain only a version constant and the comment implementation will be added during service migration. Treat these four as scaffold — types are usable, behaviour is not.
  • apps/aphrodite/* — 17 applications, each a Hono service with real route source (see Application Surface).
  • services/aphrodite/ — does not exist. Aphrodite has no services/ tier; runnable services live under apps/aphrodite/.

Because the domain is so wide, this specification gives normative detail only for the verified cross-cutting surface — data model, events, streaming, payments, devices, compliance, configuration — and summarises the remaining library tree as a status-labelled catalogue in Library Catalogue. Every schema, enum, endpoint, and event below is traceable to a cited file.


Technology Stack#

The table below summarises the key technology choices at each layer. All API services are Hono (TypeScript); all validation uses Zod schemas; the database is PostgreSQL accessed through Prisma. This consistency across services reduces the cognitive overhead of moving between different Aphrodite apps.

Layer Technology
API framework Hono (TypeScript), with @hono/zod-validator for request validation
Validation Zod schemas (per-library types.ts, plus @aphrodite/database schemas)
Database PostgreSQL via Prisma (libs/aphrodite/database/prisma/schema.prisma)
DB connectivity @oshun/database (pooling, transactions, query builder) via @aphrodite/database
Cache Redis (@aphrodite/cache)
Event contracts @oshun/contracts (libs/contracts/src/events/aphrodite.ts)
Event publishing @aphrodite/event-publisher (publisher/subscriber over the shared bus)
Streaming protocols RTMP, WebRTC (WHIP/WHEP), HLS / LL-HLS, SRT — @aphrodite/streaming-core
Object storage S3 / MinIO-compatible (@aphrodite/storage)
Build / test tsup, Vitest

Core Data Model#

The authoritative persistence schema is libs/aphrodite/database/prisma/schema.prisma (PostgreSQL, provider = "postgresql", url = env("APHRODITE_DATABASE_URL")). It defines 20 models and 22 enums. Understanding this schema is the fastest way to understand Aphrodite's domain model: every core entity — broadcasters, streams, devices, tips, subscriptions — has a model here. Integer money/token fields are stored as PostgreSQL Int (token counts) or Int cents (price_usd_cents, amount_cents); Recording.fileSize is BigInt.

Models#

The 22 models fall into four natural groups: people (Broadcaster, Viewer), streaming (Stream, PrivateShow, PrivateShowRequest, PrivateShowParticipant, StreamSchedule, StreamGoal, Recording), financial (Tip, TipMenuItem, TokenPurchase, Payout, Subscription, SubscriptionTier), and social/device (Follow, Device, DeviceGroup, DeviceGroupMember, DeviceControl, ChatMessage, ViewerBlock).

Model Table Purpose
Broadcaster broadcasters Content creators; profile, verification, pricing, denormalised stats
Viewer viewers Watching users; token balance, verification, preferences
Stream streams Live streaming sessions; technical URLs, viewer/earnings stats
PrivateShow private_shows One-on-one / group / spy private sessions, per-minute billing
PrivateShowRequest private_show_requests Viewer request to join a private show
PrivateShowParticipant private_show_participants Viewer participation record within a private show
StreamSchedule stream_schedules Scheduled (optionally recurring) stream times
StreamGoal stream_goals Tip goals with target/current amount
Recording recordings Stream recordings / VOD assets
Tip tips Token tips viewer → broadcaster, with fee breakdown
TipMenuItem tip_menu_items Predefined tip amounts with optional device actions
TokenPurchase token_purchases Real-money → token conversions
Payout payouts Broadcaster earnings payouts, per period
Subscription subscriptions Viewer subscription to a broadcaster tier
SubscriptionTier subscription_tiers Broadcaster-defined subscription tiers
Follow follows Viewer follows broadcaster, with notification prefs
Device devices Interactive (teledildonic) devices owned by a broadcaster
DeviceGroup device_groups Named cluster of devices for synchronised control
DeviceGroupMember device_group_members Device membership in a group, with intensity multiplier
DeviceControl device_controls Viewer-issued device control events
ChatMessage chat_messages Stream chat messages, with moderation flags
ViewerBlock viewer_blocks Broadcaster block of a viewer (temporary or permanent)

Note: Broadcaster and Viewer each carry a unique userId referencing a platform user managed outside this schema. There is no users table in the Aphrodite Prisma schema.

Key Model Shapes#

The field lists below are verbatim from schema.prisma — use these as the authoritative field reference when writing queries or building API payloads. Note that Broadcaster and Viewer both carry denormalised counts (followerCount, totalEarnings, totalWatchTime, etc.) for performance on the hot read paths; these are updated asynchronously from event handlers, not via live aggregation.

Broadcasterid, userId, stageName, bio?, avatarUrl?, bannerUrl?, profileVideoUrl?, status (BroadcasterStatus), verificationStatus, verifiedAt?, ageVerified, idVerified, isOnline, isStreaming, lastSeenAt?, timezone, categories[], tags[], languages[], tokenPerMinute, privateShowPrice, tipMenuEnabled, followerCount, subscriberCount, totalStreams, totalStreamMinutes, totalEarnings, pendingPayout, lastPayoutAt?, createdAt, updatedAt.

Viewerid, userId, displayName, avatarUrl?, ageVerified, ageVerifiedAt?, status (ViewerStatus), isBanned, banReason?, bannedUntil?, tokenBalance, lifetimePurchased, lifetimeSpent, preferredCategories[], showExplicit, notificationsEnabled, totalWatchTime, totalTipped, createdAt, updatedAt.

Streamid, broadcasterId, title, description?, thumbnailUrl?, previewUrl?, status (StreamStatus), streamType (StreamType), streamKey (unique), rtmpUrl?, hlsUrl?, webrtcUrl?, isExplicit, categories[], tags[], ageRestricted, currentViewers, peakViewers, totalViewers, totalTips, totalTokens, startedAt?, endedAt?, duration?, devicesEnabled, deviceSensitivity, createdAt, updatedAt.

Tipid, viewerId, broadcasterId, streamId?, amount, message?, isAnonymous, tipMenuItemId?, deviceTriggered, deviceDuration?, deviceIntensity?, platformFee, broadcasterShare, createdAt.

TokenPurchaseid, viewerId, tokenAmount, priceUsd (price_usd_cents), currency, exchangeRate, paymentProvider, paymentMethodType?, externalPaymentId?, status (PaymentStatus), failureReason?, createdAt, completedAt?.

Payoutid, broadcasterId, amount (amount_cents), currency, payoutMethod (PayoutMethod), externalPayoutId?, payoutDetails? (Json), status (PayoutStatus), failureReason?, periodStart, periodEnd, createdAt, processedAt?, completedAt?.

Deviceid, broadcasterId, name, deviceType (DeviceType), manufacturer?, model?, connectionType (DeviceConnectionType), connectionStatus (DeviceConnectionStatus), deviceId?, lastConnectedAt?, isActive, allowViewerControl, maxIntensity, defaultIntensity, supportedPatterns[], createdAt, updatedAt.

DeviceControlid, deviceId, viewerId, controlType (DeviceControlType), intensity, duration?, pattern?, sourceType (DeviceControlSource), sourceTipId?, status (DeviceControlStatus), executedAt?, createdAt.

Enums#

The 22 Prisma enums define the valid state transitions and type classifications throughout the domain. When adding a new status value, update the Prisma enum and the corresponding Zod schema in @aphrodite/database together — both must be kept in sync, and the Prisma enum is authoritative for stored data.

Enum Values
BroadcasterStatus PENDING, ACTIVE, SUSPENDED, BANNED, DELETED
VerificationStatus UNVERIFIED, PENDING, VERIFIED, REJECTED
ViewerStatus ACTIVE, SUSPENDED, BANNED, DELETED
StreamStatus OFFLINE, STARTING, LIVE, PAUSED, ENDING, ENDED
StreamType PUBLIC, PRIVATE, TICKET, GROUP, FAN_CLUB
PrivateShowStatus PENDING, ACTIVE, ENDED, CANCELLED
PrivateShowType ONE_ON_ONE, GROUP, SPY
PrivateShowRequestStatus PENDING, ACCEPTED, REJECTED, EXPIRED
ScheduleStatus SCHEDULED, LIVE, COMPLETED, CANCELLED
GoalStatus ACTIVE, COMPLETED, EXPIRED, CANCELLED
PaymentStatus PENDING, PROCESSING, COMPLETED, FAILED, REFUNDED, CANCELLED
PayoutStatus PENDING, PROCESSING, COMPLETED, FAILED, CANCELLED
PayoutMethod BANK_TRANSFER, PAYPAL, CRYPTO, CHECK
SubscriptionStatus ACTIVE, PAUSED, CANCELLED, EXPIRED, PAST_DUE
BillingCycle MONTHLY, QUARTERLY, YEARLY
DeviceType VIBRATOR, ROTATOR, CONSTRICTION, STROKER, MULTI_FUNCTION, OTHER
DeviceConnectionType BLUETOOTH, WIFI, USB, NETWORK
DeviceConnectionStatus DISCONNECTED, CONNECTING, CONNECTED, ERROR
DeviceControlType VIBRATE, ROTATE, CONSTRICT, PATTERN, STOP
DeviceControlSource TIP, TIP_MENU, SUBSCRIPTION, MANUAL, GOAL
DeviceControlStatus PENDING, EXECUTING, COMPLETED, FAILED
ChatMessageType TEXT, TIP, SYSTEM, EMOTE, STICKER

Zod Validation Layer#

The Prisma schema defines the storage shape; the Zod schemas in @aphrodite/database provide runtime validation of the data flowing in and out of service endpoints. These are two separate but related artefacts — keep them in sync when adding new fields.

@aphrodite/database (libs/aphrodite/database/src/schemas/index.ts, SCHEMA_VERSION = '0.2.0') provides runtime-validation Zod schemas for database operations, covering users, streams, transactions, content items, photo sets, content purchases/licenses, download tracking, subscriptions, subscription perks, subscription billing, and fan-club posts/likes/comments. These schemas are runtime guards layered over the Prisma model — where a Zod enum and the Prisma enum differ, the Prisma schema is authoritative for stored data.

@aphrodite/database also exports a tenant-isolation layer (libs/aphrodite/database/src/isolation/index.ts): setTenantContext, withTenantIsolation, withTenantRead, withAdminBypass, validateBroadcasterOwnership, validateViewerOwnership, checkRecordAccess, enforceTenantFilter, plus PostgreSQL row-level-security policy generators (generateRLSPoliciesSQL, generateFullRLSSetupSQL) and DEFAULT_ISOLATION_POLICIES. A BaseRepository pattern (REPOSITORY_VERSION, createRepository) is provided for data access.


Domain Types and Constants (@aphrodite/core)#

@aphrodite/core (libs/aphrodite/core/src/) is the foundational library that every other Aphrodite library may depend on. It is the single source of truth for shared types, platform constants, domain business logic, and limits. Any change to a core type or constant can affect the entire domain — treat changes to this library with extra care. It is organised into types/, constants/, domain/, and utils/.

Core Type Unions (core/src/types/)#

These discriminated union types are used as literal type constraints throughout the codebase. They define the valid values for roles, statuses, platforms, and currencies. Using these types (rather than plain strings) ensures that mismatched values are caught at compile time.

  • UserRoleviewer, broadcaster, studio, admin, moderator
  • AccountStatusactive, suspended, banned, pending_verification, deactivated
  • BroadcasterVerificationLevelunverified, basic, verified, premium, exclusive
  • AgeVerificationStatuspending, verified, rejected, expired
  • ContentRatingsfw, suggestive, sensual (Aphrodite is capped at music-video-grade sensuality; the platform does not accept nsfw / explicit content. See libs/aphrodite/core/src/domain/content-policy.ts for the cap matrix.)
  • Platformweb, ios, android, vr, desktop
  • CurrencyCodeUSD, EUR, GBP, CAD, AUD, JPY
  • PayoutInfo.methodbank_transfer, paypal, crypto, paxum, cosmo

core/types/common.ts also defines the shared envelope types ApiResponse<T> ({ success, data?, error?, meta? }), PaginatedResponse<T>, PaginationParams, Money, and GeoLocation, each with a matching Zod schema.

Platform Constants (core/src/constants/platform.ts)#

These constants define the static configuration of the platform: the token exchange rate, supported video quality levels, streaming protocols, content categories, device brands, and VR platforms. Changing these constants changes platform-wide behaviour, so they are centralised here rather than duplicated across services.

  • TOKEN_EXCHANGE_RATE = 20 (20 tokens = $1); MINIMUM_AGE = 18
  • VIDEO_QUALITY_LEVELS480p (854×480, 1500 kbps), 720p (1280×720, 3000), 1080p (1920×1080, 6000), 4k (3840×2160, 15000)
  • STREAMING_PROTOCOLSrtmp, hls, webrtc, srt
  • CONTENT_CATEGORIESamateur, professional, couples, solo, group, fetish, roleplay, interactive, vr, gaming, asmr, fitness, art, music, talk
  • SUPPORTED_DEVICE_BRANDSlovense, kiiroo, ohmibod, we-vibe, handy, satisfyer, generic-buttplug
  • PAYMENT_METHODScredit_card, debit_card, paypal, crypto, gift_card, wire_transfer
  • PAYOUT_METHODSbank_transfer, paypal, paxum, cosmo_payment, crypto, check
  • VR_PLATFORMS — Oculus Quest / Quest 2 / Quest 3 / Rift, HTC Vive / Vive Pro, Valve Index, PSVR, PSVR2, Apple Vision Pro, Windows Mixed Reality
  • WS_EVENTS — canonical client WebSocket event-name map (see WebSocket Event Names)
  • API_VERSIONSv1, v2; CURRENT_API_VERSION = 'v1'

Platform Limits (core/src/constants/limits.ts)#

Limits are enforced in service middleware and business logic rather than left to each endpoint to implement independently. The table below lists the key limits. When implementing a new feature that accepts user input, check this table and apply the relevant limit — do not introduce ad hoc limits that diverge from these constants.

Selected machine-enforceable limits:

Group Limit Value
Stream MAX_STREAM_DURATION_HOURS 24
Stream MAX_BITRATE_KBPS 15000 (MIN_BITRATE_KBPS 500)
Stream STREAM_KEY_LENGTH 48; rotation every 90 days
Stream concurrent viewers 1000 basic / 10000 verified / 50000 premium
Chat MESSAGE_MAX_LENGTH 500; 30 msg/min, 3 msg/s
Transaction tips 1–100000 tokens, 10 tips/min
Transaction token purchase $4.99–$500, $2000/day cap
Transaction subscription price $2.99–$49.99
Transaction MIN_PAYOUT_USD 50; processing 5 days
Device per user 5 devices, 3 simultaneous
Device command 0–100 intensity, 100–30000 ms, 60 cmd/min
Goal amount 100–1000000 tokens, 3 active

Pricing Domain Logic (core/src/domain/pricing.ts)#

The pricing module contains the pure functions that calculate platform economics. These functions are intentionally side-effect-free so they can be unit tested in isolation — they take inputs and return outputs with no database calls. All earning calculations in the platform flow through this module to ensure consistent fee application.

DEFAULT_PLATFORM_FEESbaseFeePercent: 20, paymentProcessingPercent: 2.9, chargebackFeeFlat: 15, minimumPayout: 50, withdrawalFee: 0.

Pure functions: calculateBroadcasterEarnings, calculateTokenValue, calculateTipPayout (default 20 % platform cut), applyDiscount, calculatePrivateShowCost, getBestPackageForTokens, calculateVipLevel, convertCurrency (uses CURRENCY_RATES). DEFAULT_TOKEN_PACKAGES (5 tiers, Starter→Ultimate) and DEFAULT_SUBSCRIPTION_TIERS (Fan / Supporter / VIP) are provided as catalogue defaults.


Streaming Subsystem (@aphrodite/streaming-core)#

@aphrodite/streaming-core (libs/aphrodite/streaming-core/src/) provides the low-level streaming primitives that the streaming app builds on: protocol handlers for WebRTC, HLS, and RTMP, plus a stream manager. Its types.ts is the authoritative type source for all streaming-related types in the domain. If you are working on the streaming service, start here.

Protocols and Lifecycle#

The streaming lifecycle moves through a well-defined state machine. A stream is created (state: created), transitions through starting and connecting as the ingest connection is established, becomes live when the video feed is active, and transitions to ended when the session completes. The protocol enum determines which delivery path is active for a given stream session.

  • StreamProtocolwebrtc, hls, ll-hls, rtmp, srt, whip, whep
  • StreamStatecreated, idle, starting, connecting, publishing, live, playing, paused, stopping, stopped, ended, error, closed
  • VideoCodech264, h265, vp8, vp9, av1
  • AudioCodecaac, opus, mp3, pcm
  • ConnectionQualityexcellent, good, fair, poor, disconnected
  • RecordingFormatmp4, webm, mkv, flv, ts, hls

Quality Presets and Adaptive Bitrate#

The quality ladder defines the encoding targets for transcoding and the steps through which the ABR algorithm cycles in response to bandwidth changes. The QUALITY_PRESETS object is the single source of truth for resolution and bitrate targets — do not hardcode these values in service code.

QUALITY_PRESETS defines five ladder rungs: 1080p (6000 kbps), 720p (3000), 480p (1500), 360p (800), 240p (400). DEFAULT_BITRATE_TIERS defines six adaptive tiers — ultra (8000 kbps, 1080p60), high (4500, 1080p30), medium (2500, 720p30), low (1200, 480p30), mobile (600, 360p24), and audio-only (128 kbps audio, no video). createDefaultStreamConfig yields a 720p / h264 / opus / WebRTC low-latency config.

Signalling and Rooms#

WebRTC sessions require a signalling exchange before media can flow. The signalling message types below model the offer/answer/ICE negotiation sequence. The room model supports co-streaming: multiple broadcasters sharing a stream session, with configurable layouts, role assignments, and revenue share.

WebRTC signalling messages are OfferMessage, AnswerMessage, IceCandidateMessage, JoinMessage, LeaveMessage. Co-streaming rooms model Participant (ParticipantRole: host, co-host, guest, viewer, moderator), BroadcasterSlot, RoomLayout (grid, spotlight, sidebar, pip, custom), RoomState (waiting, live, paused, ended), and a RevenueShareConfig (equal / weighted / custom).

Streaming Errors#

StreamingError carries a typed StreamingErrorCode enum that categorises failures by subsystem. Use these codes in error responses rather than free-form messages so clients can programmatically react to specific failure modes (for example, retrying on CONNECTION_LOST but surfacing a permission dialog on MEDIA_ACCESS_DENIED).

StreamingError carries a StreamingErrorCode enum: connection (CONNECTION_FAILED, CONNECTION_TIMEOUT, CONNECTION_LOST), media (MEDIA_ACCESS_DENIED, MEDIA_NOT_SUPPORTED, MEDIA_TRACK_ENDED), WebRTC (WEBRTC_OFFER_FAILED, WEBRTC_ANSWER_FAILED, WEBRTC_ICE_FAILED, WEBRTC_NEGOTIATION_FAILED), room (ROOM_NOT_FOUND, ROOM_FULL, ROOM_CLOSED, NOT_AUTHORIZED), stream (STREAM_NOT_FOUND, STREAM_ALREADY_LIVE, STREAM_KEY_INVALID, INGEST_FAILED), and general (UNKNOWN_ERROR, INVALID_CONFIG, RATE_LIMITED).

The streaming-core library models DASH only as an optional PlaybackUrls.dash field; the implemented distribution path is HLS / LL-HLS / WebRTC.


Payment Subsystem (@aphrodite/payments)#

@aphrodite/payments (libs/aphrodite/payments/src/, VERSION = '1.0.0') is the fully-implemented payment library for the domain. It handles payment processing, the token economy, tips, subscriptions, earnings, payouts, fraud evaluation, and tax documents. It is organised into processors/, tokens/, tips/, subscriptions/, earnings/, fraud/, tax/, and webhooks/.

Note: This is distinct from the scaffold @aphrodite/payment library — that library exports types only. @aphrodite/payments (plural) is the real implementation. See Library Catalogue for the full list.

Payment Types (payments/src/types.ts)#

The payment type system models the full lifecycle of a transaction: the processor that handles it, the current status, the payment method, the webhook events that confirm completion, and the payout destination. Amounts are always in the smallest currency unit (cents) to avoid floating-point rounding errors in financial calculations.

  • PaymentProcessorNameccbill, epoch, segpay, stripe, custom
  • PaymentStatuspending, processing, completed, failed, refunded, cancelled
  • PaymentMethodTypecard, crypto, wire, ach, paypal, other
  • WebhookEventTypepayment.completed, payment.failed, payment.refunded, subscription.created, subscription.renewed, subscription.cancelled, subscription.expired, chargeback.initiated, chargeback.reversed
  • PayoutMethod / PayoutProvider — payout method details model ACH, wire, PayPal, and crypto payout destinations (AchPayoutDetails, WirePayoutDetails, PaypalPayoutDetails, CryptoPayoutDetails)

The IPaymentProcessor interface defines the processor contract: buildCheckoutUrl, verifyWebhookSignature, parseWebhookPayload, processRefund, getTransactionStatus. Amounts in CheckoutParams are expressed in the smallest currency unit (cents).

Token Ledger#

Tokens are tracked with an append-only ledger rather than a mutable balance field. This ensures auditability: every token movement is a ledger entry with a type and source, making it possible to reconstruct any balance at any point in time. Holds (reservations against a balance) allow the platform to reserve tokens for a pending private show without deducting them until the show completes.

Tokens are tracked with a ledger model — LedgerEntry, LedgerEntryType, LedgerSource, TokenBalance, and TokenHold (reservations against a balance). Tips model Tip, TipMenuItem, TipAction / TipActionType, TipGoal, and TipLeaderboardEntry. Earnings model EarningsEntry, EarningsSource, EarningsSummary, and a RevenueShareConfig.


Application Surface#

All 17 Aphrodite apps (apps/aphrodite/*) are Hono services. Each registers a /health route group and uses a standard Hono middleware stack: requestId, logger, timing, secureHeaders, cors (the viewer app additionally adds compress for response compression). API route groups are listed below as registered route prefixes — the live source for each is apps/aphrodite/<app>/src/routes/. Endpoint paths combine the prefix with the route declared in the corresponding route file.

The auth middleware on the streaming, viewer, and payment apps currently carries placeholder JWT and API-key validation (commented TODO). Real production authentication is not yet wired — treat these endpoints as not production-secured until that is resolved.

The table lists the route group prefixes registered in each app's app.ts. Individual endpoint paths (e.g., POST /api/streams/:streamId/start) are in the route files under src/routes/.

App Registered API route groups
streaming /api/streams, /api/ingest, /api/transcoding, /api/distribution, /api/recording, /api/thumbnails, /api/quality, /api/discovery, /metrics
viewer /api/streams, /api/chat, /api/tips, /api/following, /api/settings, /api/devices, /api/shows, /api/history, /api/notifications
broadcaster /v1/streams, /v1/profile, /v1/settings, /v1/analytics, /v1/devices, /v1/shows
payment /api/v1/tokens, /api/v1/tips, /api/v1/subscriptions, /api/v1/payouts, /api/v1/transactions, /api/v1/webhooks, /api/v1/earnings
chat /api/v1/rooms, /api/v1/messages, /api/v1/moderation, /api/v1/dm, /api/v1/settings
devices /api/v1/devices, /api/v1/patterns, /api/v1/triggers
auth /auth, /auth/2fa
notifications /api/v1/notifications
analytics /api/v1/events, /api/v1/dashboard
vr /api/v1/capture, /api/v1/playback
admin /settings, /admin
admin-bi-dashboard dashboard / BI route groups (src/routes/)
analytics-dashboard analytics route groups (src/routes/)
cdn CDN origin / segment route groups (src/routes/)
realtime-analytics real-time analytics pipeline (no HTTP route group; src/ worker)
mobile-broadcaster React Native broadcaster client (src/, no HTTP route group)
mobile-viewer React Native viewer client (src/, no HTTP route group)

Streaming App Endpoints#

The streaming app is the most complex service. It manages the full stream lifecycle (create → start → live → stop), RTMP ingest hooks, WebRTC signalling, recording, transcoding, and content discovery. The endpoints below are the currently registered routes in apps/aphrodite/streaming/src/routes/.

The /api/streams group exposes: POST / (create, CreateStreamSchema), GET /:streamId, PATCH /:streamId, DELETE /:streamId, POST /:streamId/state, POST /:streamId/start, POST /:streamId/stop, POST /:streamId/key, POST /:streamId/key/rotate, POST /:streamId/auth, GET /:streamId/stats, GET /:streamId/viewers.

/api/ingest exposes RTMP hooks (POST /rtmp/auth, POST /rtmp/on-publish, POST /rtmp/on-publish-done, GET /rtmp/endpoints) and WebRTC signalling (POST /webrtc/offer, POST /webrtc/candidate, GET /webrtc/:streamId/state, POST /webrtc/:streamId/disconnect, GET /webrtc/capabilities), plus ingest settings and health. /api/recording covers recording lifecycle, DVR config, segment access, and VOD export. /api/transcoding manages transcoding jobs, profiles, workers, and metrics. /api/discovery covers search, live, trending, featured, categories, tags, recommendations, and geo discovery.

Payment App Endpoints#

The payment app handles all financial operations: token purchases, tips, subscriptions, and payouts. Its endpoints follow a consistent /api/v1/<resource> structure and validate all input with Zod schemas. The source is at apps/aphrodite/payment/src/routes/.

/api/v1/tokens exposes token packages, balance and history, POST /purchase/initiate and /purchase/confirm, POST /transfer, token holds (POST /holds, DELETE /holds/:holdId, GET /holds/:userId), POST /refund, and GET /rate. /api/v1/tips exposes POST / (send tip), sent/received history, tip-menu CRUD, tip-goal CRUD, and stream / creator leaderboards. /api/v1/subscriptions exposes tier CRUD, tier perk CRUD, POST /subscribe, user/creator subscription listing, and billing retry. /api/v1/payouts exposes payout-method CRUD, schedule, payout requests, verification, and tax documents (GET /:userId/tax/1099).


Events#

The Aphrodite event contract defines every cross-service notification the domain emits. Other services (and external consumers) react to these events rather than polling Aphrodite endpoints directly. All events flow through the shared event bus via @aphrodite/event-publisher.

The authoritative event contract is libs/contracts/src/events/aphrodite.ts (@oshun/contracts). It defines 22 events across six categories — stream lifecycle, transactions, user lifecycle, device state, chat, moderation, and content publishing — each with a Zod payload schema wrapped by createEventSchema(<topic>, 'aphrodite', <payloadSchema>). @aphrodite/event-publisher publishes and subscribes to these topics over the shared event bus.

The event table below lists every topic and its key payload fields. Note that the event StreamType and StreamStatus enums use lowercase forms (e.g., public, private) rather than the uppercase Prisma enum values — this is a known distinction between the contract and the database model.

Event topic Payload (key fields)
aphrodite.stream.started streamId, broadcasterId, title, type (StreamType), categories[], tags[], isRecording, deviceConnected
aphrodite.stream.ended streamId, broadcasterId, duration, peakViewers, totalViewers, totalTips, newSubscribers, recordingUrl?
aphrodite.stream.viewer_count_updated streamId, broadcasterId, viewerCount, peakViewerCount, anonymousCount, registeredCount, premiumCount
aphrodite.stream.goal_reached streamId, broadcasterId, goalId, title, targetAmount, totalAmount, contributorCount
aphrodite.transaction.tip_received transactionId, streamId, senderId?, senderName, recipientId, amount, message?, isAnonymous, triggerDevice
aphrodite.transaction.tokens_purchased transactionId, userId, amount, packageId, paymentMethod, currency, price, bonusTokens?
aphrodite.transaction.subscription_created subscriptionId, subscriberId, broadcasterId, tierId, tierName, price, period, isRenewal, expiresAt
aphrodite.transaction.subscription_cancelled subscriptionId, subscriberId, broadcasterId, reason?, cancelledAt, expiresAt
aphrodite.transaction.payout_requested payoutId, broadcasterId, amount, currency, method, estimatedArrival?
aphrodite.transaction.payout_completed payoutId, broadcasterId, amount, currency, method, transactionRef, completedAt
aphrodite.user.registered userId, username, email, role, registrationMethod, referredBy?
aphrodite.user.verified userId, verificationType (email/age/identity/broadcaster), verifiedAt, verificationDetails?
aphrodite.user.followed followerId, followedId, notificationsEnabled
aphrodite.user.banned userId, bannedBy, reason, duration?, expiresAt?
aphrodite.device.connected deviceId, broadcasterId, deviceType, deviceName, protocol, firmwareVersion?, batteryLevel?
aphrodite.device.control_sent deviceId, broadcasterId, streamId, command (DeviceCommand), intensity, duration?, pattern?, triggeredBy?
aphrodite.device.state_updated deviceId, broadcasterId, connected, batteryLevel?, intensity, currentPattern?
aphrodite.chat.message_sent messageId, streamId, senderId, senderName, content, isModerator, isPremium, badges?
aphrodite.chat.user_muted streamId, userId, mutedBy, reason?, duration?, expiresAt?
aphrodite.moderation.content_flagged contentId, contentType, streamId?, reporterId?, reason, category, severity, autoDetected
aphrodite.moderation.content_reviewed contentId, contentType, reviewerId, decision, reason?, actionsTaken?, reviewedAt
aphrodite.content.recording_ended recordingId, streamId, broadcasterId, duration, fileSize, storageUrl, thumbnailUrl?
aphrodite.content.vod_published vodId, streamId, broadcasterId, title, description?, duration, thumbnailUrl, playbackUrl, visibility
aphrodite.content.clip_created clipId, streamId, creatorId, broadcasterId, title, startTime, endTime, duration, thumbnailUrl, playbackUrl

Supporting enums in the contract: StreamType (public, private, ticket, group, fan_club — lowercase form of the Prisma enum), StreamStatus (initializing, live, paused, ending, ended, error), DeviceCommand (vibrate, rotate, stop, pattern), DeviceTriggerType (tip, subscription, manual). content_flagged.category is one of illegal, underage, non_consensual, violence, spam, other.

WebSocket Event Names#

WebSocket events are client-facing — they travel between the server and the viewer or broadcaster client over a WebSocket connection, as opposed to the Kafka bus events above which travel between services. @aphrodite/core (core/src/constants/platform.ts) defines the WS_EVENTS map as the canonical name registry. Use these constants rather than string literals when sending or subscribing to WebSocket events. Names use a domain:action form:

  • Connection — connect, disconnect, error, reconnect
  • Stream — stream:start, stream:end, stream:update, viewer:join, viewer:leave, viewer:count
  • Chat — chat:message, chat:delete, chat:clear, chat:timeout, chat:ban
  • Transaction — tip:received, goal:progress, goal:complete
  • Device — device:connect, device:disconnect, device:command
  • Notification — notification
  • Private show — private:request, private:start, private:end

Interactive Devices#

Device integration connects physical haptic hardware to the platform's event system. The data model is grounded in the Prisma Device / DeviceGroup / DeviceControl models; real-time device state flows through the aphrodite.device.* events; and the protocol implementation lives in the device libraries. Implemented device libraries include @aphrodite/buttplug (Buttplug.io protocol client wrapper), @aphrodite/haptics, @aphrodite/device-protocols, @aphrodite/device-safety (intensity caps, safe-word kill switch), @aphrodite/device-reaction-engine, @aphrodite/audio-haptics, @aphrodite/motion-haptics, @aphrodite/bidirectional-haptics, @aphrodite/sensation-bus, @aphrodite/smart-home, and @aphrodite/wearables.

Verified rules from code:

  • A device's maxIntensity (default 100) bounds all viewer-issued intensity. DEVICE_LIMITS in @aphrodite/core caps intensity to 0–100, command duration to 100–30000 ms, and 60 commands/minute.
  • DeviceControl.sourceType records control provenance: TIP, TIP_MENU, SUBSCRIPTION, MANUAL, or GOAL; sourceTipId links a tip-triggered control to its Tip.
  • The devices app exposes POST /api/v1/devices/:deviceId/command, POST /api/v1/devices/stop-all (safe-word all-stop), and device-brand pairing routes for Lovense, Kiiroo, Handy, and Buttplug.

Compliance Subsystem#

The compliance subsystem is legally critical infrastructure. Failures in 2257 record-keeping carry criminal penalties; failures in age verification expose the platform to regulatory action; failures in consent tracking undermine the platform's liability protection. These libraries are not optional features — they are load-bearing legal infrastructure.

18 U.S.C. 2257 (@aphrodite/compliance-2257)#

@aphrodite/compliance-2257 (libs/aphrodite/compliance-2257/src/) provides a service-oriented implementation: performer-service, content-service, custodian-service, inspection-service, and a top-level compliance-service, each with its own types. It supports performer ID document handling, custodian of records designation, record indexing by content ID, and inspection-request workflows.

Age verification (@aphrodite/age-verification)#

@aphrodite/age-verification (libs/aphrodite/age-verification/src/) implements multi-provider age/identity verification. Implemented providers: Yoti, Veriff, Stripe Identity, plus a mock provider for testing, all behind a base provider abstraction and a provider registry. The AgeVerificationService covers verification start, session management, and audit logging, with a compliance sub-module addressing UK Online Safety Act, EU DSA, US state laws, and COPPA.

@aphrodite/consent-engine (libs/aphrodite/consent-engine/src/) implements a graduated traffic-light consent modelConsentLevel: green, yellow, red. LimitType is hard, soft, or enthusiastic_yes. ActivityCategory covers device_control, visual_effects, viewer_interaction, audio_effects, intensity_modulation, and more. A ConsentEvent enum (prefixed consent:) covers profile changes, level transitions, activity enable/disable, panic activation/clear, consent decay/expiry, check-ins, and state-machine violation events. The consent state machine blocks any system from exceeding declared limits and audit-logs every state change.

V2 Game-Platform Contracts#

Several Aphrodite compliance libraries expose stable, cross-domain contracts consumed by the separate V2 game-platform program. V2 is a mature-rated fighting-game platform, not an adult-content platform, so it reuses Aphrodite's compliance primitives under a narrowed profile rather than forking them. Each contract is implemented in an Aphrodite libs/aphrodite/* package and composed by a thin V2 service under apps/v2/aphrodite-*. Every contract is backend-only, hides the Aphrodite brand from players, and carries mayInfluenceRollback: false so age, consent, and likeness decisions can never perturb the deterministic match simulation. The subsections below document each contract as a producer/consumer pair: what V2 consumes, the real package and function names, the inputs/outputs, and the safety posture.

V2 Age-Gate Driver Contract#

The V2 Age-Gate Driver Contract lets @aphrodite/age-verification act as the backend driver for V2's content age gate, consumed by the @v2/aphrodite-age-gate service. The driver function buildV2AgeGateContentDecision (age-verification/src/v2-age-gate-driver.ts, driver id aphrodite.age-verification.v2-age-gate-driver) takes the player's account id, country/region code, verification status and level, an estimated age, the requested gore tier, requested adult-fatality flag, and the list of requested cinematic ids; it resolves the per-region rule and returns a decision that governs three controlled surfaces — the effective gore tier, the adult-fatality enablement flag, and the per-cinematic gate actions for region-conditional cinematics. The default region table encodes the launch markets (US, GB, DE, AU, NZ, KR, CN, and a GLOBAL fallback) with their minimum ages, required verification levels, maximum gore tier, whether adult fatalities are allowed, and the cinematic policy (full, softened, or removed). The decision sets replacesStandaloneV2AgeGate: true, ageVerificationBackendOnly: true, and mayInfluenceRollback: false. The V2 service publishes v2.age-gate.decision.created, v2.content.gore-tier.changed, and v2.cinematic.region-gated events from this output. Documented at V2/docs/integration/aphrodite-age-gate.md.

The V2 Per-Feature Consent Surface Contract lets @aphrodite/consent-engine own V2's per-feature privacy consent, consumed by the @v2/aphrodite-consent-surfaces service. buildV2FeatureConsentSurface (consent-engine/src/v2-feature-consent-surfaces.ts, surface id aphrodite.consent-engine.v2-feature-consent-surfaces) takes the subject id, region code, and the player's recorded consent grants, then returns a receipt- backed decision (enabled, limited, or disabled) for each of three controlled features: telemetry, voice processing, and behavioral profiling. Telemetry defaults to a limited, privacy-preserving fallback; voice processing and behavioral profiling require explicit opt-in and default to disabled. Each decision lists the allowed data classes, the data classes redacted by the privacy-preserving fallback, and the retention window, and references the consent receipt that authorised it. The surface sets consentReceiptsRequired: true, privacyPreservingByDefault: true, and mayInfluenceRollback: false, and records that the deprecated @aphrodite/consent package is absent. The V2 service emits v2.consent.surface.evaluated, v2.telemetry.consent.changed, v2.voice-processing.consent.changed, and v2.behavioral-profiling.consent.changed.

V2 Licensed-Fighter Likeness Safety + Revocation Contract#

The V2 Licensed-Fighter Likeness Safety + Revocation Contract is composed from two Aphrodite packages — @aphrodite/performer-sovereignty and @aphrodite/performer-autonomy — and consumed by the @v2/aphrodite-licensed-likeness-safety service. The safety gate buildAphroditeV2LicensedFighterLikenessSafetyGate (performer-sovereignty/src/v2-licensed-fighter-likeness-safety.ts) takes a fighter id, a requested likeness surface (playable character, cinematic cameo, cosmetic skin, voice-line bank, commentary mention, esports broadcast, or creator-suite upload), and a @themis/likeness ledger stamp; it allows the use only when the ledger status is active, the rights manifest matches, the performer has opted in via sovereignty, and the surface is on the performer-approved list. On any failure it returns blocksLikenessSurface and blocksBellonaCook, refusing the downstream Bellona cook. The revocation flow buildAphroditeV2LikenessRevocationFlow (performer-autonomy/src/v2-likeness-revocation-flow.ts) emits aphrodite.performer-autonomy.likeness.revoked, mirrors the upstream themis.license.revoked event, blocks the Bellona cook, and notifies the Themis likeness ledger, Kuan-yin performer protection, and the Aja consent/NIL ledger. Both contracts carry mayInfluenceRollback: false and record that the deprecated @aphrodite/performer-safety package is absent; the V2 service takes a workspace dependency on the two Aphrodite packages but never on the forthcoming @themis/likeness package directly. The V2 service emits v2.likeness.safety.evaluated, v2.likeness.revocation.requested, and v2.bellona.cook.likeness.blocked.

V2 Age-Gate Primitive-Fitness Audit#

The V2 Age-Gate Primitive-Fitness Audit is the design record explaining why V2 reuses the Aphrodite age primitive rather than promoting a new identity primitive. buildV2AgeGatePrimitiveFitnessAudit (age-verification/src/v2-age-gate-driver.ts, audit id aphrodite.age-verification.v2-primitive-fitness-audit) returns the verdict "fit with V2 mature-game profile" and the decision to parameterize the Aphrodite primitive under the mature-rated-fighting-game profile (ESRB Mature 17+ default with 18+ regional variants). It sets promoteOshunIdentityPrimitive: false — V2 deliberately does not promote a new shared primitive into @oshun/identity, because the existing Aphrodite primitive already covers provider selection, region rules, retry/lockout, and audit logging. The audit rejects the adult-content default profile (adultContentDefaultRejected: true) and asserts that 2257 record-keeping, performer-protection, and anti-CSAM performer workflows are not required for V2 game-content gating (record2257Required: false, performerProtectionWorkflowRequired: false, antiCsamPerformerWorkflowRequired: false), while parental-control hand-off to platform family controls is required (parentalControlHandOffRequired: true). Documented at V2/docs/integration/age-gate-fitness-audit.md.

V2 Regional Content Rules Inputs#

The V2 Regional Content Rules Inputs contract is the one cross-domain boundary that does not live in Aphrodite. The regional cook engine is owned by shared infrastructure as @oshun/region-rules (libs/shared/region-rules/src/v2-regional-content-rules.ts), not as a hypothetical @aphrodite/regional-content-rules package — that Aphrodite-owned alias was rejected during the owner decision so that region cook plans stay a neutral shared service rather than an Aphrodite-domain dependency. buildV2RegionalContentCookPlan resolves a per-country SKU cook profile for CN, DE, AU, NZ, and KR, applying cuts such as remove-blood, recolor-red-blood, soften-finisher-cinematics, and require-finisher-preclearance. It consumes the Aphrodite age-gate and consent outputs as inputs (requested gore tier, adult-fatality allowance, and whether consent permits adult content) but treats them as advisory; the engine always applies the China most-restrictive default when a region is unknown (missing-region-default-most-restrictive, defaultProfileIsMostRestrictive: true). Like the other contracts it carries mayInfluenceRollback: false. Documented at V2/docs/integration/region-rules.md.


Configuration Reference#

Aphrodite services are configured through environment variables. There is no central config file — operational defaults are expressed as code constants in @aphrodite/core (see Platform Constants and Platform Limits), and the only required external variable is the database connection string.

Configuration is sourced from environment variables. The single required database variable is APHRODITE_DATABASE_URL (consumed by schema.prisma and @aphrodite/database). App services additionally read ALLOWED_ORIGINS, NODE_ENV, and (streaming) INTERNAL_API_KEY from the environment. Operational defaults are expressed as code constants rather than a single config object:

  • Platform fees — DEFAULT_PLATFORM_FEES in @aphrodite/core (20 % base fee, 2.9 % processing, $50 minimum payout).
  • Token economy — TOKEN_EXCHANGE_RATE = 20 tokens/USD; DEFAULT_TOKEN_PACKAGES.
  • Stream / chat / device / transaction limits — the constant groups in core/src/constants/limits.ts (see Platform limits).
  • Streaming defaults — createDefaultStreamConfig, DEFAULT_BITRATE_TIERS, QUALITY_PRESETS in @aphrodite/streaming-core.
  • Rate limiting — each app's app.ts defines an in-memory limiter (RATE_LIMIT_MAX 100 for the viewer app, 1000 for the streaming service, over a 60-second window).

Library Catalogue#

The full set of 190 libs/aphrodite/* directories grouped by functional area. Unless flagged otherwise, each library has a substantial src/ implementation. Only the four scaffold libraries (chat, payment, safety, sdk) are not behaviourally implemented. This catalogue is the index — for detailed per-library API documentation, see the library's own src/ and README.md.

Area Libraries
Platform / infrastructure core, database, cache, storage, event-publisher, testing, platform-accessibility, platform-analytics
Scaffold (types.ts only) chat, payment, safety, sdk — functional subdirectories are placeholders pending service migration
Streaming streaming-core, multi-source, stream-upscaling, stream-watermarking, stream-content-analysis, cloud-recording, highlight-clipping, thumbnail-generation, vod-chaptering, spatial-audio
Devices / haptics buttplug, haptics, bidirectional-haptics, audio-haptics, motion-haptics, device-protocols, device-safety, device-reaction-engine, sensation-bus, smart-home, wearables
VR / AR / avatars vr-core, vr-haptics, vr-quest, eye-tracking, volumetric-capture, gaussian-splatting, ar-overlays, viewer-avatars, avatar-renderer, avatar-motion, avatar-physics, avatar-customization-ui, animation-blending
Chat / interaction chat-core, chat-moderation, chat-entertainment, tip-acknowledgment, tip-effects, tip-goals, virtual-gifts, viewer-interactions, crowd-control, interactables
Payments / monetisation payments, crypto-payments, privacy-coins, revenue-sharing, ppv, nft-integration, treasury, gamification, growth
Compliance / safety compliance, compliance-2257, age-verification, gdpr-compliance, content-takedown, drm, recording-prevention, e2e-encryption, consent-engine, performer-autonomy, performer-sovereignty, safety-intelligence, ethics, legal, regulatory, body-dignity, risk-crisis
AI / agents ai-assistant, recommendations, notification-intelligence, scouting-agents, research-agents, agent-platform, content-tagging, market-intelligence
Performer economy career, talent, property, partnerships, production, multi-performer, performer-presence, performer-camera, performer-compositor, remote-guest, audience-experience, technical-direction
Wellbeing aftercare, aftercare-engine, arousal-engine, wellness, meditation, reaction-enhancement, neuro-design
Music / audio advanced-music, ai-music, music-force, cinematic-music-engine, ai-orchestration, composition-engine
Cinematic production auteur-camera, cinematic-camera, lighting-director, scene-composer, neural-rendering, vfx-particles, vfx-post-processing, vfx-volumetric, video-effects, animation-triggers, animations, drone-integration
Immersive / cultural / sacred venue-management, immersive-theater, ceremony, ritual-engine, cultural-heritage, sacred-dance, sacred-architecture, art-traditions, goddess-traditions, archetype-engine, symbol-engine, literary-engine, procedural-art, abstract-scenes, scene-abstract, scene-cosmic, scene-fantasy, scene-goddess, scene-nature, scene-urban
Identity / movement / fashion face-identity, biometric-engine, movement-engine, body-art, fashion, digital-couture, vip-areas, virtual-rooms
Embedded games game-runtime, game-ecs, game-event-bus, game-physics, game-input, game-renderer, game-assets, game-audio, game-networking, game-monetization, game-performance, game-accessibility, game-testing, game-dev-tools; genre templates game-fps, game-rpg, game-fighting, game-platformer, game-puzzle, game-rhythm, game-horror, game-strategy, game-sports, game-racing, game-openworld, game-simulation
Domain bridges maya-bridge, hathor-game-bridge, nyx-game-bridge, isis-game-bridge, lilith-game-bridge, nike-game-bridge, oauth-providers

Library directories beyond the verified cross-cutting surface contain feature code but are not all wired into a deployed apps/aphrodite/* service. Service wiring status should be confirmed per library before integration.


Cross-Domain Integration Points#

The table below lists every verified cross-domain dependency. These are the boundaries where Aphrodite's data and events cross into other domains or consume other domains' services. As with the architecture doc, only dependencies confirmed in package.json files are listed here.

System Direction Purpose
@oshun/database aphrodite → shared PostgreSQL pooling, transactions, query builder (via @aphrodite/database)
@oshun/contracts aphrodite → contracts Event envelope schemas, the aphrodite.* event catalogue
Shared event bus aphrodite → shared Event publication via @aphrodite/event-publisher
V2 program aphrodite → V2 Age-gate, consent-surface, and licensed-likeness contracts consumed by apps/v2/aphrodite-*

Earlier draft documentation referenced @oshun/auth, @oshun/storage, @oshun/websocket, @oshun/metrics, @oshun/rate-limit, @aje/*, and @lilith/llm as Aphrodite dependencies. Those are not present in the verified package.json files of the apps and core libraries reviewed; the apps depend on @aphrodite/core, @aphrodite/database, domain-specific Aphrodite libraries, and hono / @hono/zod-validator. Treat additional cross-domain wiring as unverified until confirmed in a package.json.


Acceptance Criteria#

Before merging a change to the Aphrodite domain, verify all seven criteria below. These criteria are not a checklist to rush through — each one protects a real invariant (schema integrity, event contract compatibility, compliance gating) that is difficult to repair after a regression.

A change to the Aphrodite domain is acceptable when:

  1. Schema integrity — new persisted fields are added to libs/aphrodite/database/prisma/schema.prisma and reflected in the @aphrodite/database Zod schemas; enum changes preserve the Prisma enum as the authoritative form.
  2. Event integrity — any new cross-service event is added to libs/contracts/src/events/aphrodite.ts with a Zod payload schema and a createEventSchema(...) wrapper, and registered in AphroditeEventTypes.
  3. API validation — every new Hono route validates its request with a Zod schema via @hono/zod-validator, and returns the shared error envelope ({ error: { code, message } }).
  4. Limit enforcement — token, tip, stream, chat, and device operations respect the constants in core/src/constants/limits.ts; device intensity is bounded by Device.maxIntensity.
  5. Compliance gating — broadcasting and access to age-restricted content remain gated on age verification; 2257 and consent workflows are not bypassed.
  6. Tests — affected libraries pass their Vitest suites.
  7. No scaffold regressions — code does not depend on the behaviour (only the types) of the four scaffold libraries until they are implemented.