Docs

Integrate Gravix Cloud with an AI coding assistant

Let Claude Code, Cursor, Copilot or another coding assistant add voice and video to your app. Give it the prompt below and the plain-text docs, and it has everything it needs.

Copy-paste prompt

Paste this into your assistant from the root of your project. It works for Flutter apps, web apps and React apps, with a Node or Go backend.

prompt
Add Gravix Cloud real-time voice and video to this project.

Read https://www.gravixcloud.com/llms-full.txt first and follow it exactly.

Rules:
1. Detect the stack. Flutter app: add the gravix_rtc package
   (flutter pub add gravix_rtc). Web or React app: add gravix-rtc
   (npm install gravix-rtc). Use only APIs shown in that file.
2. The App Certificate is a server secret. Never put it, or code that
   signs tokens, in the Flutter app or the browser bundle.
3. On the backend, add POST /rtc/token. It takes the user from the app's
   existing login, signs a short-lived token with gravix-rtc-token (Node)
   or gravix-token-go (Go), and returns { token, url }. Read GRAVIX_APP_ID,
   GRAVIX_APP_CERTIFICATE and GRAVIX_URL from the environment.
4. In the app, create GravixTokenProvider.endpoint(<that endpoint>) and
   join with connectWithTokenProvider.
5. Add the microphone and camera permissions for each platform.
6. Leave the room (disconnect) when the screen closes.
7. Do not invent class names, options or events. If something is not in
   the file, stop and ask me.

Before you run it, have your App ID, App Certificate and region URL ready from your dashboard. Put them in your server's environment yourself. Do not paste the App Certificate into the chat.

Plain-text docs for assistants

Two files follow the llms.txt convention, so an assistant can read them without a browser.

  • /llms.txt: a short index with the key rules and links.
  • /llms-full.txt: the whole integration guide on one page, with all the code.

Both are made from the same samples as these docs, checked against gravix_rtc 0.4.13and gravix-rtc 0.6.6.

Packages

WherePackageInstall
Flutter app (Android, iOS)gravix_rtc on pub.devflutter pub add gravix_rtc
Web or React appgravix-rtc on npmnpm install gravix-rtc
Node backendgravix-rtc-token on npmnpm install gravix-rtc-token
Go backendgravix-token-goAsk us for access

Android and iOS apps use the Flutter SDK. There is no separate native Kotlin or Swift SDK, so an assistant should not look for one.

Token contract

This is the part an assistant must not change.

  • The App Certificate is a server secret. It never goes into app code, a mobile build or a web bundle.
  • Your backend has one endpoint behind your existing login. It signs a short-lived token for that user and room.
  • The server takes the identity from its own session and decides whether the user may publish. The request body is only a wish.
  • The endpoint answers { token, url }. The app never hard-codes the region URL.
  • Pass the plain room name. The token library adds your App ID prefix.
wire format
// Request: what the SDK posts to your endpoint
POST /rtc/token
{ "room": "stream-42", "identity": "user-17", "name": "Asha", "can_publish": false }

// Response: what your endpoint must answer
{ "token": "<signed JWT>", "url": "wss://YOUR-REGION-URL" }

Server code

The whole backend a join needs. Read the App ID, App Certificate and region URL from the environment.

server.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);
token.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
	})
}

Client code

The minimal join. The app points the SDK at your endpoint and never sees the App Certificate.

lib/call.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<void> 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<void> leave() => room.disconnect();
call.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 <video> or <audio> element
  })
  .on(RoomEvent.TrackUnsubscribed, (track) => {
    track.detach().forEach((el) => el.remove());
  });

export async function join(roomId: string, userId: string, name: string) {
  await connectWithTokenProvider(room, {
    tokenProvider: tokens,
    request: { room: roomId, identity: userId, name },
  });
  await room.localParticipant.setMicrophoneEnabled(true);
  await room.localParticipant.setCameraEnabled(true);
}

export const leave = () => room.disconnect();
  • Flutter: connectWithTokenProvider returns false instead of throwing. room.lastTokenError says why.
  • Web: connectWithTokenProvider throws when the token cannot be fetched. Catch it.
  • React: create the Room in useEffect and call room.disconnect() in the cleanup. The live streaming sample shows a full component.

Platform setup

Flutter apps declare microphone and camera access, and ask for them at runtime before the user joins with them on.

AndroidManifest.xml
<!-- android/app/src/main/AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.RECORD_AUDIO" />
  <uses-permission android:name="android.permission.CAMERA" />
  <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
  <!-- Bluetooth headsets: Android 12+ needs BLUETOOTH_CONNECT -->
  <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
  <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
  <!-- ... your <application> ... -->
</manifest>
Info.plist
<!-- ios/Runner/Info.plist -->
<key>NSMicrophoneUsageDescription</key>
<string>Talk in calls</string>
<key>NSCameraUsageDescription</key>
<string>Video in calls</string>
<!-- Keep call audio playing with the screen locked or the app in the background -->
<key>UIBackgroundModes</key>
<array>
  <string>audio</string>
</array>
  • Minimums: Android 7.0 (API 24), iOS 13, Flutter 3.38 and Dart 3.10.
  • On Android 12 and later, request BLUETOOTH_CONNECT at runtime so Bluetooth headsets are found.
  • Background audio on Android needs the SDK's foreground service, which is off by default. See Android.
  • Web: nothing to declare, but the page must be served over HTTPS (a local development server on your own machine also works).

Common mistakes

  • Signing tokens in the app, or putting the App Certificate in any client build or a browser-exposed env file.
  • Trusting identity or can_publish from the request body.
  • Returning only the token. The endpoint must return { token, url }.
  • Using the deprecated GravixCloudBackend, which sends a secret from the device. Use GravixTokenProvider.
  • Missing NSMicrophoneUsageDescription or NSCameraUsageDescription on iOS, or the Android permissions.
  • Not leaving the room when the screen closes. Call disconnect(), and dispose() on Flutter.
  • Inventing API. If a class, option or event is not in these docs, the assistant should ask you.

Done when

  • The App Certificate exists only in your server's configuration.
  • Two devices join the same room and see and hear each other.
  • Permissions are declared and requested on every platform you ship.
  • Leaving the screen leaves the room.

Get your keys

Sign up free for your App ID, App Certificate and 10,000 free minutes, then hand the prompt to your assistant.