← Admin dashboard

Integration guide

GoLive gives you a turnkey video-calling backend powered by LiveKit. You manage rooms and mint access tokens from your own backend using the credentials we issued you (api_key + api_secret). Your end users open the call from their browser, mobile, or desktop app using the LiveKit Client SDK.

Endpoints
Control plane: https://live.superskynet.com
Signaling (WSS): wss://live.superskynet.com

1. Architecture

┌──────── YOUR BACKEND ──────────┐         ┌─── GoLive control plane ──┐
│ keeps api_key + api_secret     │  HTTPS  │ /admin/* (operator only)  │
│ mints a JWT for each end user  │ ──────▶ │ /api/v1/* (your tenant)   │
└────────────┬───────────────────┘         └─────────────┬─────────────┘
             │ JWT + ws_url                              │ creates room,
             ▼                                           │ mints token
┌─── YOUR END USERS ─────────────┐         ┌─── LiveKit SFU ───────────┐
│ browser / iOS / Android / RN   │ ◀──WSS─▶│ signaling + WebRTC media  │
└────────────────────────────────┘         └───────────────────────────┘

2. Server side — get a token for an end user

From your backend, call POST /api/v1/rooms/{room}/tokens with HTTP Basic auth using your api_key and api_secret. (Create the room once via POST /api/v1/rooms.)

KEY="lkv_..."          # your api_key
SECRET="..."           # your api_secret

# 1) create the room (idempotent — 409 if it already exists)
curl -sS -u "$KEY:$SECRET" -X POST https://live.superskynet.com/api/v1/rooms \
  -H "Content-Type: application/json" \
  -d '{"name":"team-standup","max_participants":25}'

# 2) mint a token for a specific user
curl -sS -u "$KEY:$SECRET" -X POST \
  https://live.superskynet.com/api/v1/rooms/team-standup/tokens \
  -H "Content-Type: application/json" \
  -d '{"identity":"user-123","name":"Jane Doe","ttl_seconds":3600}'

# response:
# { "token": "eyJhbGci…", "ws_url": "wss://live.superskynet.com",
#   "room": "<tenant>__team-standup", "identity": "user-123",
#   "expires_in": 3600 }
// Node 18+ — uses built-in fetch
const KEY = process.env.GOLIVE_API_KEY;
const SECRET = process.env.GOLIVE_API_SECRET;
const BASE = "https://live.superskynet.com";

const basic = "Basic " + Buffer.from(`${KEY}:${SECRET}`).toString("base64");

async function getToken(roomName, identity, displayName) {
  // ensure room exists (ignore 409)
  await fetch(`${BASE}/api/v1/rooms`, {
    method: "POST",
    headers: { Authorization: basic, "Content-Type": "application/json" },
    body: JSON.stringify({ name: roomName, max_participants: 25 }),
  }).catch(() => {});

  const res = await fetch(`${BASE}/api/v1/rooms/${roomName}/tokens`, {
    method: "POST",
    headers: { Authorization: basic, "Content-Type": "application/json" },
    body: JSON.stringify({ identity, name: displayName, ttl_seconds: 3600 }),
  });
  if (!res.ok) throw new Error(await res.text());
  return res.json(); // { token, ws_url, room, identity, expires_in }
}

// Expose this from your own /api/get-call-token endpoint and call it from the client.
import os, base64, httpx

KEY = os.environ["GOLIVE_API_KEY"]
SECRET = os.environ["GOLIVE_API_SECRET"]
BASE = "https://live.superskynet.com"

auth = (KEY, SECRET)

def get_token(room: str, identity: str, name: str | None = None) -> dict:
    with httpx.Client(auth=auth, timeout=10) as c:
        c.post(f"{BASE}/api/v1/rooms", json={"name": room, "max_participants": 25})  # ignore 409
        r = c.post(f"{BASE}/api/v1/rooms/{room}/tokens",
                   json={"identity": identity, "name": name, "ttl_seconds": 3600})
        r.raise_for_status()
        return r.json()  # token, ws_url, room, identity, expires_in
<?php
$key = getenv('GOLIVE_API_KEY');
$secret = getenv('GOLIVE_API_SECRET');
$base = 'https://live.superskynet.com';

function golive($base, $auth, $method, $path, $body = null) {
  $ch = curl_init("$base$path");
  curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => $method,
    CURLOPT_USERPWD => $auth,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS => $body ? json_encode($body) : null,
  ]);
  $resp = curl_exec($ch); curl_close($ch);
  return json_decode($resp, true);
}

$auth = "$key:$secret";
golive($base, $auth, 'POST', '/api/v1/rooms', ['name' => 'team-standup']);
$tok = golive($base, $auth, 'POST', '/api/v1/rooms/team-standup/tokens',
              ['identity' => 'user-123', 'name' => 'Jane', 'ttl_seconds' => 3600]);
echo $tok['token']; // hand to client
package main

import (
  "bytes"; "encoding/json"; "fmt"; "net/http"; "os"
)

const Base = "https://live.superskynet.com"

func goLive(method, path string, body any) (map[string]any, error) {
  buf, _ := json.Marshal(body)
  req, _ := http.NewRequest(method, Base+path, bytes.NewReader(buf))
  req.SetBasicAuth(os.Getenv("GOLIVE_API_KEY"), os.Getenv("GOLIVE_API_SECRET"))
  req.Header.Set("Content-Type", "application/json")
  res, err := http.DefaultClient.Do(req)
  if err != nil { return nil, err }
  defer res.Body.Close()
  var out map[string]any
  json.NewDecoder(res.Body).Decode(&out)
  if res.StatusCode >= 300 { return nil, fmt.Errorf("%d: %v", res.StatusCode, out) }
  return out, nil
}

func main() {
  goLive("POST", "/api/v1/rooms", map[string]any{"name":"team-standup"})
  tok, _ := goLive("POST", "/api/v1/rooms/team-standup/tokens",
    map[string]any{"identity":"user-123","name":"Jane","ttl_seconds":3600})
  fmt.Println(tok["token"])
}

3. Client side — connect a user to the call

Your client app calls your own backend (built above) to get { token, ws_url }, then hands them to the LiveKit Client SDK.

// npm i @livekit/components-react livekit-client
import { LiveKitRoom, VideoConference } from "@livekit/components-react";
import "@livekit/components-styles";
import { useEffect, useState } from "react";

export function Call({ roomName, identity, displayName }) {
  const [creds, setCreds] = useState(null);

  useEffect(() => {
    // Ask YOUR backend (which holds the api_secret) for a token.
    fetch("/api/get-call-token", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ room: roomName, identity, name: displayName }),
    }).then(r => r.json()).then(setCreds);
  }, [roomName, identity]);

  if (!creds) return <p>Joining…</p>;

  return (
    <LiveKitRoom
      serverUrl={creds.ws_url}
      token={creds.token}
      connect
      video audio
      data-lk-theme="default"
      style={{ height: "100vh" }}
    >
      <VideoConference />
    </LiveKitRoom>
  );
}
<!-- index.html -->
<script type="module">
  import { Room, RoomEvent } from 'https://cdn.jsdelivr.net/npm/livekit-client@2.5.7/+esm';

  const { token, ws_url } = await fetch('/api/get-call-token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ room: 'team-standup', identity: 'user-123' })
  }).then(r => r.json());

  const room = new Room({ adaptiveStream: true, dynacast: true });
  room.on(RoomEvent.TrackSubscribed, (track, _, p) => {
    document.body.appendChild(track.attach());
  });
  await room.connect(ws_url, token);
  await room.localParticipant.enableCameraAndMicrophone();
</script>
// Package.swift: .package(url: "https://github.com/livekit/client-sdk-swift", from: "2.0.0")
import LiveKit

class CallVM: ObservableObject {
  let room = Room()

  func join() async throws {
    let creds = try await fetchCreds()  // calls YOUR backend
    try await room.connect(url: creds.wsUrl, token: creds.token)
    try await room.localParticipant.setCamera(enabled: true)
    try await room.localParticipant.setMicrophone(enabled: true)
  }
}

// Info.plist: NSCameraUsageDescription, NSMicrophoneUsageDescription
// build.gradle:
// implementation "io.livekit:livekit-android:2.10.0"

class CallViewModel : ViewModel() {
  val room = LiveKit.create(applicationContext)

  fun join() = viewModelScope.launch {
    val creds = fetchCreds()  // calls YOUR backend
    room.connect(creds.wsUrl, creds.token)
    room.localParticipant.setCameraEnabled(true)
    room.localParticipant.setMicrophoneEnabled(true)
  }
}

// AndroidManifest.xml:
// <uses-permission android:name="android.permission.CAMERA" />
// <uses-permission android:name="android.permission.RECORD_AUDIO" />
# pubspec.yaml:  livekit_client: ^2.4.0

import 'package:livekit_client/livekit_client.dart';

Future<void> join() async {
  final creds = await fetchCreds(); // calls YOUR backend
  final room = Room();
  await room.connect(creds.wsUrl, creds.token);
  await room.localParticipant?.setCameraEnabled(true);
  await room.localParticipant?.setMicrophoneEnabled(true);
}

// iOS Info.plist + Android Manifest: same camera/mic permissions as native.
// npm i @livekit/react-native @livekit/react-native-webrtc
import { LiveKitRoom, AudioSession } from '@livekit/react-native';

export default function Call() {
  const [creds, setCreds] = useState(null);
  useEffect(() => { AudioSession.startAudioSession(); fetchCreds().then(setCreds); }, []);
  if (!creds) return null;
  return (
    <LiveKitRoom serverUrl={creds.wsUrl} token={creds.token} connect audio video>
      {/* Render participant videos here */}
    </LiveKitRoom>
  );
}

4. Reference — API endpoints you can call

MethodPathPurpose
GET/api/v1/meIdentity / health check for your credentials
POST/api/v1/roomsCreate a room (body: {name, max_participants, empty_timeout})
GET/api/v1/roomsList your rooms
DELETE/api/v1/rooms/{name}Delete a room
POST/api/v1/rooms/{name}/tokensMint a per-user JWT

Interactive reference: Swagger UI · ReDoc

5. Token request — fields

{
  "identity": "user-123",      // REQUIRED — stable unique ID in your system
  "name": "Jane Doe",          // optional — display name
  "can_publish": true,         // false for view-only participants
  "can_subscribe": true,       // false for "broadcast-only" senders
  "can_publish_data": true,    // data-channel messages
  "ttl_seconds": 3600          // 60–86400; default 3600
}

6. Security best practices

7. Troubleshooting

SymptomLikely cause
401 from /api/v1/*Wrong api_key/secret, or tenant deactivated. Re-check with GET /api/v1/me.
404 minting tokenRoom doesn't exist. Create it with POST /api/v1/rooms first.
Client connects but no mediaFirewall blocks UDP. Client will auto-fall back to TCP on port 7881; ensure outbound 7881 is allowed.
Token works once then 401TTL expired — refresh by requesting a new token before reconnecting.
Permission denied on joinToken was minted with can_publish: false or wrong room name (rooms are tenant-namespaced internally; use the plain name in our API, not the livekit_name).

Need help? Email the operator who issued your credentials.