# ============================================================================== # FLAWLESS ENT — OFFLINE FLUTTER DEV AGENT API SETTINGS & PIPELINE BRIDGE # Target Platform: Flutter 3.x / Dart 3.x # Protocol: REST / JSON HTTP Gateway + Supabase Sync Engine # Spec Version: 2.1.0-PRODUCTION # Generated: 2026-09-09 # ============================================================================== # ------------------------------------------------------------------------------ # 1. ENVIRONMENT BASE URL MATRIX # ------------------------------------------------------------------------------ # Choose the appropriate base URL according to your execution environment: [ENV_PRODUCTION] WEB_BASE_URL=https://flawlessent.net REST_API_BASE=https://flawlessent.net/api SUPABASE_URL=https://flawlessent.net/supabase STATIC_ASSET_BASE=https://flawlessent.net/ [ENV_ANDROID_EMULATOR] # 10.0.2.2 is the Android emulator's loopback alias for the host workstation WEB_BASE_URL=http://10.0.2.2:3003 REST_API_BASE=http://10.0.2.2:3003/api SUPABASE_URL=http://10.0.2.2:54321 STATIC_ASSET_BASE=http://10.0.2.2:3003/ [ENV_IOS_SIMULATOR] WEB_BASE_URL=http://localhost:3003 REST_API_BASE=http://localhost:3003/api SUPABASE_URL=http://127.0.0.1:54321 STATIC_ASSET_BASE=http://localhost:3003/ [ENV_LOCAL_DEVICE_LAN] # Replace with your local development machine IP on WiFi (e.g. 192.168.1.150) WEB_BASE_URL=http://:3003 REST_API_BASE=http://:3003/api SUPABASE_URL=http://:54321 STATIC_ASSET_BASE=http://:3003/ # ------------------------------------------------------------------------------ # 2. HTTP HEADERS & AUTHENTICATION SPECIFICATION # ------------------------------------------------------------------------------ # All REST requests must include Content-Type and Accept headers. # Authenticated endpoints require the Bearer JWT session token. Content-Type: application/json Accept: application/json Authorization: Bearer Token Format: - Prefix: flawless_jwt_ - Length: 77 characters (hex-encoded random entropy) - Storage Key: flawless_session_token (Store in flutter_secure_storage) - TTL: 30 days rolling session # ------------------------------------------------------------------------------ # 3. COMPLETE API ENDPOINT CATALOG # ------------------------------------------------------------------------------ # --- AUTHENTICATION --- POST /api/auth/login Body: { "email": "member@flawlessent.net", "password": "VIPMember2027!" } Returns: { "success": true, "user": { ... }, "token": "flawless_jwt_..." } POST /api/auth/signup Body: { "email": "user@example.com", "password": "Password123!", "fullName": "Alex Smith", "birthday": "1998-05-20", "ageConfirm": true } Returns: { "success": true, "user": { ... }, "token": "flawless_jwt_..." } Note: Strict 18+ verification enforced on birthday. GET /api/auth/me Header: Authorization: Bearer Returns: { "success": true, "user": { "id": "...", "email": "...", "accountLevel": 3, "assignedAssets": ["pkg_2027_01_core", "pkg_2027_01_vip"] } } GET /api/auth/credentials Returns: Seed test accounts for automated QA and sandbox testing. # --- MONTHLY EXPERIENCES & CALENDARS --- GET /api/content/experiences Query: None Returns: { "success": true, "experiences": [ ... 12 months ... ] } Note: Unpublished draft months are sealed for standard members with null hero artwork and sealed taglines. # --- MEDIA VAULT --- GET /api/content/media?experienceId={id} Header: Authorization: Bearer (Recommended for VIP unlock) Returns: { "success": true, "media": [ ... choreography videos, audio drops, galleries ... ] } Note: Non-VIP members receive isGated: true and null mediaUrl on VIP exclusive items. # --- BUYABLE ASSET INVENTORY & MONTHLY BUNDLES --- GET /api/inventory/packages Query: ?month={1-12}&year={2027}&tier={standard|vip|deluxe|collector} Returns: { "success": true, "packages": [ ... published buyable bundles ... ] } GET /api/user/assets Header: Authorization: Bearer Returns: { "success": true, "assets": [ ... full package models owned by user ... ], "assetIds": [ ... ] } # --- ADMIN REST OPERATIONS (Levels 0 and 1) --- GET /api/admin/users POST /api/admin/users POST /api/admin/users/edit (Full account edit, password reset, assignedAssets assignment) POST /api/admin/users/suspend DELETE /api/admin/users GET /api/admin/packages POST /api/admin/packages POST /api/admin/packages/publish DELETE /api/admin/packages GET /api/admin/stats GET /api/admin/audit POST /api/admin/upload Header: Authorization: Bearer , Content-Type: multipart/form-data Form Fields: - files: File (Binary, single or multiple; audio/video/image) - category: "sound" | "video" | "picture" | "cover" | "auto" - month: 1-12 - createMediaRecord: true | false Returns: { "success": true, "message": "...", "files": [ { "id": "...", "fileName": "...", "url": "uploads/...", "type": "sound", "format": "mp3", "title": "..." } ] } # ------------------------------------------------------------------------------ # 4. TYPED DART DATA CONTRACTS # ------------------------------------------------------------------------------ # Copy these model definitions into your Flutter app: lib/models/flawless_models.dart /* class PackageBundleModel { final String id; final String experienceId; final int month; final int year; final String monthName; final String title; final String sku; final String tier; // standard | vip | deluxe | collector final double priceUsd; final int priceTokens; final int inventoryCount; // -1 = unlimited final bool isPublished; final String? publishedAt; final String coverArtworkUrl; final String badge; final String description; final List sounds; final List videos; final List pictures; final List features; PackageBundleModel({ required this.id, required this.experienceId, required this.month, required this.year, required this.monthName, required this.title, required this.sku, required this.tier, required this.priceUsd, required this.priceTokens, required this.inventoryCount, required this.isPublished, this.publishedAt, required this.coverArtworkUrl, required this.badge, required this.description, required this.sounds, required this.videos, required this.pictures, required this.features, }); factory PackageBundleModel.fromJson(Map json) { return PackageBundleModel( id: json['id'] ?? '', experienceId: json['experienceId'] ?? '', month: json['month'] ?? 1, year: json['year'] ?? 2027, monthName: json['monthName'] ?? '', title: json['title'] ?? '', sku: json['sku'] ?? '', tier: json['tier'] ?? 'standard', priceUsd: (json['priceUsd'] as num?)?.toDouble() ?? 0.0, priceTokens: json['priceTokens'] ?? 0, inventoryCount: json['inventoryCount'] ?? -1, isPublished: json['isPublished'] ?? false, publishedAt: json['publishedAt'], coverArtworkUrl: json['coverArtworkUrl'] ?? 'assets/january-hero.png', badge: json['badge'] ?? '', description: json['description'] ?? '', sounds: (json['sounds'] as List?) ?.map((s) => SoundAssetModel.fromJson(s)) .toList() ?? [], videos: (json['videos'] as List?) ?.map((v) => VideoAssetModel.fromJson(v)) .toList() ?? [], pictures: (json['pictures'] as List?) ?.map((p) => PictureAssetModel.fromJson(p)) .toList() ?? [], features: (json['features'] as List?) ?.map((f) => f.toString()) .toList() ?? [], ); } Map toJson() => { 'id': id, 'experienceId': experienceId, 'month': month, 'year': year, 'monthName': monthName, 'title': title, 'sku': sku, 'tier': tier, 'priceUsd': priceUsd, 'priceTokens': priceTokens, 'inventoryCount': inventoryCount, 'isPublished': isPublished, 'publishedAt': publishedAt, 'coverArtworkUrl': coverArtworkUrl, 'badge': badge, 'description': description, 'sounds': sounds.map((s) => s.toJson()).toList(), 'videos': videos.map((v) => v.toJson()).toList(), 'pictures': pictures.map((p) => p.toJson()).toList(), 'features': features, }; } class SoundAssetModel { final String id; final String title; final String url; final int durationSeconds; final String format; SoundAssetModel({ required this.id, required this.title, required this.url, required this.durationSeconds, required this.format, }); factory SoundAssetModel.fromJson(Map json) => SoundAssetModel( id: json['id'] ?? '', title: json['title'] ?? '', url: json['url'] ?? '', durationSeconds: json['durationSeconds'] ?? 0, format: json['format'] ?? 'mp3', ); Map toJson() => { 'id': id, 'title': title, 'url': url, 'durationSeconds': durationSeconds, 'format': format, }; } class VideoAssetModel { final String id; final String title; final String url; final int durationSeconds; final String resolution; VideoAssetModel({ required this.id, required this.title, required this.url, required this.durationSeconds, required this.resolution, }); factory VideoAssetModel.fromJson(Map json) => VideoAssetModel( id: json['id'] ?? '', title: json['title'] ?? '', url: json['url'] ?? '', durationSeconds: json['durationSeconds'] ?? 0, resolution: json['resolution'] ?? '1080p', ); Map toJson() => { 'id': id, 'title': title, 'url': url, 'durationSeconds': durationSeconds, 'resolution': resolution, }; } class PictureAssetModel { final String id; final String title; final String url; final String resolution; PictureAssetModel({ required this.id, required this.title, required this.url, required this.resolution, }); factory PictureAssetModel.fromJson(Map json) => PictureAssetModel( id: json['id'] ?? '', title: json['title'] ?? '', url: json['url'] ?? '', resolution: json['resolution'] ?? '3840x2160', ); Map toJson() => { 'id': id, 'title': title, 'url': url, 'resolution': resolution, }; } class FlawlessUserModel { final String id; final String email; final String fullName; final String dateOfBirth; final int accountLevel; // 0=SysAdmin, 1=OpsAdmin, 2=Curator, 3=VIP, 4=Standard, 5=Suspended final String role; final bool isVip; final bool suspended; final List assignedAssets; final String avatarInitial; FlawlessUserModel({ required this.id, required this.email, required this.fullName, required this.dateOfBirth, required this.accountLevel, required this.role, required this.isVip, required this.suspended, required this.assignedAssets, required this.avatarInitial, }); bool ownsPackage(String packageId) { if (accountLevel == 0) return true; // Level 0 owns all assets return assignedAssets.contains(packageId); } factory FlawlessUserModel.fromJson(Map json) => FlawlessUserModel( id: json['id'] ?? '', email: json['email'] ?? '', fullName: json['fullName'] ?? '', dateOfBirth: json['dateOfBirth'] ?? '', accountLevel: json['accountLevel'] ?? 4, role: json['role'] ?? 'user', isVip: json['isVip'] ?? false, suspended: json['suspended'] ?? false, assignedAssets: (json['assignedAssets'] as List?) ?.map((a) => a.toString()) .toList() ?? [], avatarInitial: json['avatarInitial'] ?? 'M', ); } */ # ------------------------------------------------------------------------------ # 5. OFFLINE MOCK DATASET FOR FLUTTER DEV AGENTS # ------------------------------------------------------------------------------ # Use this JSON mock dataset in your offline mock repository # (e.g. MockFlawlessRepository or MockPackageService) to run the full app # without network connectivity: OFFLINE_MOCK_PACKAGES=[ { "id": "pkg_2027_01_core", "experienceId": "exp_2027_01", "month": 1, "year": 2027, "monthName": "January", "title": "January 2027: The Ice Samurai — Digital Starter Bundle", "sku": "FLW-2027-01-CORE", "tier": "standard", "priceUsd": 14.99, "priceTokens": 150, "inventoryCount": -1, "isPublished": true, "publishedAt": "2027-01-01T00:00:00.000Z", "coverArtworkUrl": "assets/january-hero.png", "badge": "Essential Drop", "description": "Official entry pack for January 2027: The Ice Samurai. Includes curator audio commentary, high-resolution digital stills, and mobile wallpapers.", "sounds": [ { "id": "snd_01_01", "title": "Ice Samurai: Executive Audio Drop & Commentary", "url": "assets/commentary-january.mp3", "durationSeconds": 112, "format": "mp3" } ], "videos": [], "pictures": [ { "id": "pic_01_01", "title": "The Ice Samurai: Master Hero Artwork (4K Still)", "url": "assets/january-hero.png", "resolution": "3840x2160" }, { "id": "pic_01_02", "title": "Winter Glaze Editorial Portrait", "url": "assets/welcome-hero.png", "resolution": "2048x2048" } ], "features": [ "Full Curator Audio Commentary Track (Lossless Audio)", "High-Resolution 4K Editorial Digital Wallpapers", "Digital Collector Certificate of Authenticity" ] }, { "id": "pkg_2027_01_vip", "experienceId": "exp_2027_01", "month": 1, "year": 2027, "monthName": "January", "title": "January 2027: Ice Samurai — VIP 4K Visualizer & Choreography Pack", "sku": "FLW-2027-01-VIP", "tier": "vip", "priceUsd": 29.99, "priceTokens": 300, "inventoryCount": 1000, "isPublished": true, "publishedAt": "2027-01-01T00:00:00.000Z", "coverArtworkUrl": "assets/january-hero.png", "badge": "👑 VIP Choice", "description": "Complete high-octane visual experience. Unlocks full 4K Neo-Tokyo choreography video performance, audio drops, and executive fashion gallery.", "sounds": [ { "id": "snd_01_01", "title": "Ice Samurai: Executive Audio Drop & Commentary", "url": "assets/commentary-january.mp3", "durationSeconds": 112, "format": "mp3" }, { "id": "snd_01_02", "title": "Ice Samurai: Synthwave Battle Anthem (Original Mix)", "url": "assets/commentary-january.mp3", "durationSeconds": 210, "format": "mp3" } ], "videos": [ { "id": "vid_01_01", "title": "Ice Samurai: Master Choreography Visualizer (4K Director's Cut)", "url": "https://flawlessent.net/media/ice-samurai-choreography-4k.mp4", "durationSeconds": 240, "resolution": "4K UHD" } ], "pictures": [ { "id": "pic_01_01", "title": "The Ice Samurai: Master Hero Artwork (4K Still)", "url": "assets/january-hero.png", "resolution": "3840x2160" }, { "id": "pic_01_03", "title": "Behind-the-Scenes Production Stills & Wardrobe", "url": "assets/welcome-hero.png", "resolution": "3840x2160" } ], "features": [ "4K UHD Director's Cut Choreography Performance Video", "Multi-Track Audio Bundle (Commentary + Anthem)", "VIP Access Tier & Exclusive Digital Collectible Token", "High-Resolution Neo-Tokyo Studio Gallery" ] }, { "id": "pkg_2027_01_deluxe", "experienceId": "exp_2027_01", "month": 1, "year": 2027, "monthName": "January", "title": "January 2027: Ice Samurai — Master Collector Vault Box", "sku": "FLW-2027-01-DLX", "tier": "deluxe", "priceUsd": 49.99, "priceTokens": 500, "inventoryCount": 250, "isPublished": true, "publishedAt": "2027-01-01T00:00:00.000Z", "coverArtworkUrl": "assets/january-hero.png", "badge": "Limited Collector (250 Units)", "description": "The definitive January artifact box. Includes all 4K performance videos, master commentary, isolated stems, photo gallery, and priority pass for 2027 live events.", "sounds": [ { "id": "snd_01_01", "title": "Ice Samurai: Executive Audio Drop & Commentary", "url": "assets/commentary-january.mp3", "durationSeconds": 112, "format": "mp3" }, { "id": "snd_01_02", "title": "Ice Samurai: Synthwave Battle Anthem (Lossless WAV)", "url": "assets/commentary-january.mp3", "durationSeconds": 210, "format": "wav" } ], "videos": [ { "id": "vid_01_01", "title": "Ice Samurai: Master Choreography Visualizer (4K Director's Cut)", "url": "https://flawlessent.net/media/ice-samurai-choreography-4k.mp4", "durationSeconds": 240, "resolution": "4K UHD" }, { "id": "vid_01_02", "title": "Neo-Tokyo Soundstage Rehearsal & Raw Takes", "url": "https://flawlessent.net/media/ice-samurai-choreography-4k.mp4", "durationSeconds": 180, "resolution": "1080p HD" } ], "pictures": [ { "id": "pic_01_01", "title": "The Ice Samurai: Master Hero Artwork (4K Still)", "url": "assets/january-hero.png", "resolution": "3840x2160" }, { "id": "pic_01_02", "title": "Winter Glaze Editorial Portrait", "url": "assets/welcome-hero.png", "resolution": "2048x2048" }, { "id": "pic_01_03", "title": "Behind-the-Scenes Production Stills & Wardrobe", "url": "assets/welcome-hero.png", "resolution": "3840x2160" } ], "features": [ "Complete 4K Director Cut + Rehearsal Footage", "Lossless 24-Bit Studio Master Audio Tracks", "Signed Digital Lithograph Artwork", "Numbered Collectible Serial Pass" ] } ] OFFLINE_MOCK_USER={ "id": "usr_vipmember_004", "email": "member@flawlessent.net", "fullName": "Marcus Cross", "dateOfBirth": "1995-04-18", "accountLevel": 3, "role": "vip", "isVip": true, "suspended": false, "assignedAssets": [ "pkg_2027_01_core", "pkg_2027_01_vip" ], "avatarInitial": "M" } # ------------------------------------------------------------------------------ # 6. OFFLINE-TO-LIVE FLUTTER BRIDGING INSTRUCTIONS # ------------------------------------------------------------------------------ # 1. Store this configuration in your Flutter project under assets/config/api-settings.txt # or load it dynamically in debug builds. # 2. Use a FlawlessEnvironment configuration class with a boolean flag: # const bool kIsOfflineDev = bool.fromEnvironment('OFFLINE_MODE', defaultValue: false); # 3. If kIsOfflineDev is true, serve data from OFFLINE_MOCK_PACKAGES. # 4. If kIsOfflineDev is false, perform http.get() to REST_API_BASE + '/inventory/packages'. # 5. When resolving image, video, and audio URLs, check if the URL is relative: # String resolveUrl(String url) { # if (url.startsWith('http://') || url.startsWith('https://')) return url; # return '$STATIC_ASSET_BASE${url.startsWith('/') ? url.substring(1) : url}'; # } # 6. Run offline with: # flutter run --dart-define=OFFLINE_MODE=true # 7. Run live emulator with: # flutter run --dart-define=FLAWLESS_API_URL=http://10.0.2.2:3003/api # ==============================================================================