# Gravix Cloud: integration guide for AI coding assistants > Add real-time voice, video and live streaming to a Flutter or web app. The app joins a room with a short-lived access token. The developer's own server signs that token with the App ID and App Certificate from the Gravix Cloud dashboard. Checked against gravix_rtc 0.4.13 (pub.dev) and gravix-rtc 0.6.6 (npm) on 8 October 2026. Use only the packages, classes, options and events in this file. If you need something that is not here, ask the developer instead of guessing. ## 1. Packages - Flutter (Android and iOS apps): `flutter pub add gravix_rtc`. One import: `import 'package:gravix_rtc/gravix_rtc.dart';`. Needs Flutter 3.38+ and Dart 3.10+. Android 7.0 (API 24)+, iOS 13+. - Web (any framework, including React): `npm install gravix-rtc` (or `yarn add gravix-rtc`, `pnpm add gravix-rtc`). TypeScript types included. Pages must be served over HTTPS (a local development server on your own machine also works). - Server, Node 18+, Deno, Bun or edge runtimes: `npm install gravix-rtc-token`, function `createToken(appId, appCertificate, options)`. - Server, Go 1.21+: `gravix-token-go` (package `gravixtoken`), builder `gravixtoken.New(appID, appCertificate).Room(..).Identity(..).Name(..).CanPublish(..).TTL(..).ToJWT()`. - Native Kotlin or Swift SDKs do not exist. Android and iOS apps use the Flutter SDK. ## 2. Token flow (do not change it) 1. The developer gets an App ID, an App Certificate and a region URL (wss://...) from the dashboard. 2. The backend exposes one endpoint, for example POST /rtc/token, behind the app's existing login. 3. The SDK POSTs JSON `{ "room": "...", "identity": "...", "name": "...", "can_publish": true|false }` to it. 4. The endpoint takes the identity from its own session (not from the body), decides whether the user may publish, signs a token and answers `{ "token": "", "url": "" }`. 5. The app joins with that token. No Gravix Cloud service is called to make a token. Rules: - The App Certificate is a secret. Keep it in the server environment (GRAVIX_APP_CERTIFICATE). Never put it in app code, a mobile build, a web bundle, a .env file that ships to the browser, or a git repository. - Tokens are short-lived: 6 hours by default, 24 hours at most (`ttlSeconds` in Node, `TTL` in Go). - `canPublish: false` makes a listener or viewer. Decide it on the server. - Pass the plain room name. The token library adds the App ID prefix itself. - The identity in the token is the identity the user has in the room. Use the app's own user ID. ## 3. Server: token endpoint (Node) ```ts // server.ts (your backend, Node 18+). This file never ships inside an app. import express from 'express'; import { createToken } from 'gravix-rtc-token'; const APP_ID = process.env.GRAVIX_APP_ID!; // from your dashboard const APP_CERTIFICATE = process.env.GRAVIX_APP_CERTIFICATE!; // server only, never in an app const REGION_URL = process.env.GRAVIX_URL!; // wss://YOUR-REGION-URL const app = express(); app.use(express.json()); // requireUser is your own login check: the identity comes from YOUR session, // never from the request body. app.post('/rtc/token', requireUser, async (req, res) => { const room = req.body?.room; if (typeof room !== 'string' || !/^[\w.-]{1,64}$/.test(room)) { return res.status(400).json({ error: 'invalid room' }); } const token = await createToken(APP_ID, APP_CERTIFICATE, { room, // the library adds your App ID prefix identity: req.user.id, name: req.user.name, canPublish: true, // false = listen only; decide it here, not in the app ttlSeconds: 60 * 60, // short-lived: 1 hour }); res.json({ token, url: REGION_URL }); }); app.listen(3000); ``` ## 4. Server: token endpoint (Go) ```go // token.go (your backend, Go 1.21+). gravixtoken is the gravix-token-go package. func tokenHandler(w http.ResponseWriter, r *http.Request) { user, ok := currentUser(r) // your own login check if !ok { http.Error(w, "sign in first", http.StatusUnauthorized) return } var body struct { Room string `json:"room"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.Room == "" { http.Error(w, "invalid room", http.StatusBadRequest) return } // The App Certificate stays in the server's environment. jwt, err := gravixtoken.New(os.Getenv("GRAVIX_APP_ID"), os.Getenv("GRAVIX_APP_CERTIFICATE")). Room(body.Room). Identity(user.ID). Name(user.Name). CanPublish(true). // false = listen only TTL(time.Hour). // short-lived ToJWT() if err != nil { http.Error(w, "could not sign", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "token": jwt, "url": os.Getenv("GRAVIX_URL"), // wss://YOUR-REGION-URL }) } ``` ## 5. Flutter client: join a room ```dart import 'package:gravix_rtc/gravix_rtc.dart'; // One provider per signed-in user. It caches each token until it is about to expire. final tokens = GravixTokenProvider.endpoint( Uri.parse('https://YOUR-BACKEND/rtc/token'), headers: {'Authorization': 'Bearer $sessionJwt'}, // your app's own login session ); final room = GravixRoomService(); Future join(String roomId, String userId, String name) async { room.onRemoteVideoTrack = (uid, track) { // show it with VideoTrackRenderer(track) }; final ok = await room.connectWithTokenProvider( tokenProvider: tokens, request: GravixTokenRequest( room: roomId, identity: userId, name: name, canPublish: true, ), publishMic: true, enableVideo: true, ); if (!ok) { // room.lastTokenError is set when your token endpoint failed } } Future leave() => room.disconnect(); ``` - `connectWithTokenProvider` returns false instead of throwing. `room.lastTokenError?.message` says why when the token endpoint failed. - Observable state is `ValueNotifier`s: `isConnected`, `isMicMuted`, `isCameraEnabled`, `activeSpeakers`. - Controls: `setMicEnabled(bool)`, `setCameraEnabled(bool)`, `switchCamera()`, `disconnect()`, then `dispose()` when the screen is gone. - Video: `room.onRemoteVideoTrack = (uid, track) {...}`, `room.onRemoteVideoTrackRemoved = (uid) {...}`, own camera from `room.localVideoTrack`. Show a track with `VideoTrackRenderer(track, fit: VideoViewFit.cover)`. ## 6. Flutter platform setup Android, android/app/src/main/AndroidManifest.xml: ```xml ``` iOS, ios/Runner/Info.plist: ```xml NSMicrophoneUsageDescription Talk in calls NSCameraUsageDescription Video in calls UIBackgroundModes audio ``` - Ask for microphone (and camera) access at runtime before the user joins with them on. On Android 12+ also request BLUETOOTH_CONNECT so Bluetooth headsets are found. - Android background audio: the SDK has a foreground service, off by default. Turn it on once before the first join with `GravixForegroundService.defaults = const GravixForegroundServiceOptions(enabled: true, notificationTitle: '...', notificationText: '...', showLeaveAction: true);` and listen to `GravixForegroundService.leaveRequests`. ## 7. Web client: join a room ```ts import { Room, RoomEvent, GravixTokenProvider, connectWithTokenProvider } from 'gravix-rtc'; // Calls YOUR backend with the user's session cookie. Tokens are cached until they expire. const tokens = GravixTokenProvider.endpoint('https://YOUR-BACKEND/rtc/token', { credentials: 'include', }); const room = new Room(); const stage = document.getElementById('stage')!; room .on(RoomEvent.TrackSubscribed, (track) => { stage.appendChild(track.attach()); // a