SILICON ZOMBIES · API V1
Web, Flutter, and billing integration
The server is authoritative for identity, role, Human/Zombie VIP gating, and billing. Send a bearer session token only from secure storage; do not infer VIP access from a completed client payment.
Response and error handlers
Successful JSON responses use { data: ... }. Errors use { error: { code, message, details?, requestId? } }; retain requestId for support, but show only the safe message. JSON mutations require Content-Type: application/json. Invalid fields return 422, missing auth 401, forbidden actions 403, conflicts 409, and temporary integrations 503.
class ApiException implements Exception {
ApiException(this.status, this.code, this.message, this.requestId);
final int status;
final String code;
final String message;
final String? requestId;
}
class ZombiesApi {
ZombiesApi(this.baseUrl, this.token);
final String baseUrl;
final String token; // Read from flutter_secure_storage.
Future<dynamic> request(String method, String path, {Object? body}) async {
try {
final response = await http.Request(method, Uri.parse('$baseUrl/api/v1$path'))
..headers.addAll({
'Accept': 'application/json',
'Authorization': 'Bearer $token',
if (body != null) 'Content-Type': 'application/json',
})
..body = body == null ? '' : jsonEncode(body);
final streamed = await response.send().timeout(const Duration(seconds: 20));
final raw = await streamed.stream.bytesToString();
final json = raw.isEmpty ? <String, dynamic>{} : jsonDecode(raw) as Map<String, dynamic>;
if (streamed.statusCode >= 400) {
final error = json['error'] as Map<String, dynamic>? ?? const {};
throw ApiException(streamed.statusCode, error['code'] as String? ?? 'HTTP_ERROR',
error['message'] as String? ?? 'Request failed.', error['requestId'] as String?);
}
return json['data'];
} on TimeoutException {
throw ApiException(0, 'NETWORK_TIMEOUT', 'Please try again.', null);
} on SocketException {
throw ApiException(0, 'NETWORK_OFFLINE', 'Check your connection.', null);
}
}
}VIP_REQUIRED: open the membership CTA using server upsell details; never retry the protected action automatically.VIP_APPROVAL_REQUIRED: show the application status, not a payment button.IOS_EXTERNAL_BILLING_DISABLED: open hosted web checkout or the approved native store path.422: render field errors;429/503: offer a bounded retry;401: clear the stored token and sign in.
Hosted web checkout
After approval, call POST /billing/checkout with { "plan": "monthly" | "annual" }, then navigate to the returned checkoutUrl. Stripe-hosted Checkout shows eligible wallets, including Apple Pay and Google Pay, from the Stripe Dashboard configuration. On the success URL, refresh GET /billing/status; it can remain pending until the signed webhook records a paid subscription.
Flutter PaymentSheet: Android and eligible iOS
Initialize flutter_stripe once with the publishable key and, on iOS, the configured merchant identifier. The endpoint returns only short-lived PaymentSheet material after authentication and VIP approval. The iOS route is disabled by default because digital subscriptions commonly require Apple in-app purchase; enable it only after confirming current App Store eligibility for the storefront.
Future<void> startVipPayment(ZombiesApi api, String plan, TargetPlatform platform) async {
final platformName = platform == TargetPlatform.iOS ? 'ios' : 'android';
final data = await api.request('POST', '/billing/mobile-payment-sheet', body: {
'plan': plan, 'platform': platformName,
}) as Map<String, dynamic>;
await Stripe.instance.initPaymentSheet(
paymentSheetParameters: SetupPaymentSheetParameters(
merchantDisplayName: 'Silicon Zombies',
paymentIntentClientSecret: data['paymentIntentClientSecret'] as String,
customerId: data['customerId'] as String,
customerEphemeralKeySecret: data['ephemeralKeySecret'] as String,
returnURL: 'siliconzombies://stripe-redirect',
style: ThemeMode.dark,
applePay: const PaymentSheetApplePay(merchantCountryCode: 'US'),
googlePay: PaymentSheetGooglePay(
merchantCountryCode: 'US',
currencyCode: 'USD',
testEnv: kDebugMode,
),
),
);
await Stripe.instance.presentPaymentSheet();
// Never grant access here. The verified Stripe webhook changes the tier.
await api.request('GET', '/billing/status');
}For Apple Pay, register the merchant ID and verified domain in Stripe and enable the Apple Pay capability in Xcode. For Google Pay, enable it in Stripe and use a physical/device-supported wallet during testing. PaymentSheet can omit a wallet when the device, country, currency, or Dashboard settings are ineligible.
Resource handlers
- Content: list locked items normally; request
/content/:id/streamonly immediately before playback. Do not cache signed URLs. - Events: handle
403 RSVP_NOT_OPENas a VIP-first/waitlist state. Treat the server RSVP status as final. - Community: Humans can read permitted spaces. On a 403 when posting, open the upgrade flow; do not render a local optimistic message.
- Notifications: register an Expo token after permission, delete it on logout, and mark a deep link read only after navigation succeeds.
Endpoint contract
GET /api/v1/health— Service healthPOST /api/v1/auth/session— Rotate the current opaque sessionGET /api/v1/me— Get the current member profile and membership stateGET /api/v1/content— List published content; VIP records remain visible as lockedGET /api/v1/content/{id}/stream— Get a short-lived audio URLPOST /api/v1/vip/apply— Submit a Zombie VIP applicationGET /api/v1/events— List events with tier-aware booking availabilityPOST /api/v1/events/{id}/rsvp— Create an RSVP or waitlist entryGET /api/v1/spaces— List community spaces, including VIP locksGET /api/v1/spaces/{id}/messages— List messages in a spaceGET /api/v1/live— Get current live-stream statePOST /api/v1/live/{id}/chat-ticket— Create a five-minute VIP ticket for the API Gateway WebSocket chatGET /api/v1/notifications— List the current member's notificationsPOST /api/v1/notifications/{id}/read— Mark one notification readPOST /api/v1/push-tokens— Register an Expo device tokenGET /api/v1/billing/status— Get server-authoritative subscription statusPOST /api/v1/billing/checkout— Create a hosted Stripe Checkout session after VIP approvalPOST /api/v1/billing/mobile-payment-sheet— Create an Android or eligible iOS PaymentSheet subscription flowPOST /api/v1/billing/portal— Create a Stripe customer-portal sessionPOST /api/v1/admin/content— Create or update contentGET /api/v1/admin/applications— List VIP applicationsPOST /api/v1/admin/applications/{id}/approve— Approve an application and create Stripe CheckoutPOST /api/v1/admin/applications/{id}/reject— Reject an applicationPOST /api/v1/admin/events/{id}/checkin— Check in an attendee or promote a waitlisted RSVPDELETE /api/v1/admin/messages/{id}— Remove a messageGET /api/v1/admin/reports— List moderation reportsPOST /api/v1/admin/live/start— Mark a YouTube stream livePOST /api/v1/admin/live/end— End a live streamPOST /api/v1/admin/live/{id}/sync— Synchronize live status and viewers from YouTubeGET /api/v1/admin/metrics— Get revenue, subscription, and retention metricsGET /api/v1/admin/integrations— Get secret-free external integration readinessPOST /api/v1/webhooks/stripe— Receive verified Stripe eventsPOST /api/v1/webhooks/luma— Receive Luma events after configured signature verification
Machine-readable OpenAPI is available at /openapi.json.