Quick Start

An easy to follow guide to setup the SDK and incorporate Orga AI into your app

Installation

Install the SDK from npm

npm install @orga-ai/sdk-react-native

Install the peer dependencies

npm install react-native-webrtc react-native-incallmanager

Project configuration

This SDK requires both camera and microphone permissions.

Configure app.json

{
  "expo": {
    "ios": {
      "infoPlist": {
        "NSCameraUsageDescription": "Allow $(PRODUCT_NAME) to access your camera",
        "NSMicrophoneUsageDescription": "Allow $(PRODUCT_NAME) to access your microphone"
      }
    },
    "android": {
      "permissions": [
        "android.permission.CAMERA",
        "android.permission.RECORD_AUDIO"
      ]
    }
  }
}

Development build

The SDK requires a development build and won’t workin in Expo Go due to native dependencies. Won’t work on a simulator either. Must use a physical device

npx expo prebuild 
eas build --platform all --profile development

Set Up Backend Proxy

You need a secure backend proxy to protect your API key. The location of your environment variables depends on your chosen approach:

Option A: Using Expo API Routes (SDK 50+)

If using Expo API Routes, create a .env file in your Expo project root:

ORGA_API_KEY=your_orga_api_key_here
USER_EMAIL=your_user_email_here

Get your API key from the OrgaAI dashboard: https://platform.orga-ai.com/login

// app/orga-client-secrets+api.ts

const ORGA_API_KEY = process.env.ORGA_API_KEY;

const fetchIceServers = async (ephemeralToken: string) => {
  const URL = `https://api.orga-ai.com/v1/realtime/ice-config`;
  try {
    const iceServersResponse = await fetch(URL, {
      method: "GET",
      headers: {
        Authorization: `Bearer ${ephemeralToken}`,
      },
    });
    const data = await iceServersResponse.json();
    return data.iceServers;
  } catch (error) {
    console.error("Error fetching ice servers:", error);
    throw error;
  }
};

export async function GET(request: Request) {
  const USER_EMAIL = process.env.USER_EMAIL;
  if (!ORGA_API_KEY) {
    return NextResponse.json(
      { error: 'Missing ORGA_API_KEY environment variable' }, 
      { status: 500 }
    );
  }
  if (!USER_EMAIL) {
    return NextResponse.json(
      { error: 'Missing USER_EMAIL environment variable' }, 
      { status: 500 }
    );
  }

  const apiUrl = `https://api.orga-ai.com/v1/realtime/client-secrets?email=${encodeURIComponent(USER_EMAIL)}`;
  const ephemeralResponse = await fetch(apiUrl, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${ORGA_API_KEY}`,
    },
  });
    
    if (!ephemeralResponse.ok) {
      throw new Error('Failed to fetch session token');
    }

    const { ephemeral_token } = await ephemeralResponse.json();
    const iceServers = await fetchIceServers(ephemeral_token);
    
    return Response.json({ ephemeralToken: ephemeral_token, iceServers }, {status: 200})
  } catch (error) {
    return Response.json({error: "Internal server error"}, { status: 500 });
  }
}

Option B: Custom Backend Server

If using a separate backend server, store your environment variables in your backend project:

Create a .env file in your backend project (NOT in your React Native app):

ORGA_API_KEY=your_orga_api_key_here

Your React Native app should only contain the backend URL:

# In your React Native app's .env
API_URL=https://your-backend-url.com

Never store the ORGA_API_KEY in your mobile app's environment variables. It should always be kept secure on the server side.

// Example using Express
import express, { Request, Response } from 'express';
import cors from 'cors';

// Note: RTCIceServer is available in modern browsers and Node.js environments
// If you get type errors, you may need to install @types/webrtc or define the type:
// interface RTCIceServer { urls: string | string[]; username?: string; credential?: string; }

// Define types for OrgaAI API responses
interface OrgaEphemeralTokenResponse {
  ephemeral_token: string;
}

interface OrgaIceConfigResponse {
  iceServers: RTCIceServer[];
}

interface OrgaEphemeralResponse {
  ephemeralToken: string;
  iceServers: RTCIceServer[];
}

// Extend Express Request to include user (if using authentication)
interface AuthenticatedRequest extends Request {
  user?: {
    id: string;
    email: string;
  };
}

const app = express();
app.use(cors()); // Configure appropriately for production

// Add your authentication middleware
app.use(authMiddleware);

const ORGA_API_KEY = process.env.ORGA_API_KEY;

app.get('/api/orga-client-secrets', async (req: AuthenticatedRequest, res: Response) => {
  // Your user authentication/session validation here
  const userId = req.user?.id; // Example: Get from your auth system

  if (!ORGA_API_KEY) {
    return res.status(500).json({ error: 'Server configuration error: missing ORGA_API_KEY' });
  }

  const userEmail = req.user?.email;
  if (!userEmail) {
    return res.status(400).json({ error: 'Missing user email (req.user.email). Make sure your auth middleware sets it.' });
  }

  try {
    const ephemeralResponse = await fetch(`https://api.orga-ai.com/v1/realtime/?email=${encodeURIComponent(userEmail)}`, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${ORGA_API_KEY}`,
      },
    });

    if (!ephemeralResponse.ok) {
      throw new Error('Failed to fetch ephemeral token');
    }

    const ephemeralData = (await ephemeralResponse.json()) as OrgaEphemeralTokenResponse;
    const { ephemeral_token } = ephemeralData;

    // Fetch ICE servers
    const iceResponse = await fetch('https://api.orga-ai.com/v1/realtime/ice-config', {
      headers: { Authorization: `Bearer ${ephemeral_token}` },
    });

    if (!iceResponse.ok) {
      throw new Error('Failed to fetch ICE servers');
    }

    const iceData = (await iceResponse.json()) as OrgaIceConfigResponse;
    const { iceServers } = iceData;

    const response: OrgaEphemeralResponse = {
      ephemeralToken: ephemeral_token,
      iceServers,
    };

    res.json(response);
  } catch (error) {
    console.error('Error:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});

app.listen(5000, () => console.log('Server running on port 5000'));

Security Note: Your backend proxy should:

  • Store the OrgaAI API key securely (use environment variables)

  • Implement proper authentication/authorization

  • Add rate limiting and request validation

  • Use HTTPS in production

  • Configure CORS appropriately

Initialize the SDK

Layout provider

// app/_layout.tsx
import { OrgaAI, OrgaAIProvider } from '@orga-ai/sdk-react-native';

OrgaAI.init({
  logLevel: 'debug',
  fetchSessionConfig: async () => {
    const response = await fetch('/api/orga-client-secrets'); //Expo API route
    //const response = await fetch('https://your_backend_url/api/orga-client-secrets');
    const { ephemeralToken, iceServers } = await response.json();
    return { ephemeralToken, iceServers };
  },
  // OR use sessionConfigEndpoint which points directly to your backend. 
  // Useful if you don't need any custom implementation.
  // sessionConfigEndpoint: 'https://<url>/api/orga-client-secrets',
  model: 'orga-1-beta',
  voice: 'alloy',
});

export default function RootLayout() {
  return (
    <OrgaAIProvider>
      <Stack />
    </OrgaAIProvider>
  );
}

Main Screen

// app/index.tsx
import { StyleSheet, Text, View } from "react-native";
import {
  OrgaAICameraView,
  OrgaAIControls,
  useOrgaAI
} from "@orga-ai/sdk-react-native";

export default function HomeScreen() {
  const {
    userVideoStream,
    connectionState,
    isMicOn,
    isCameraOn,
    startSession,
    endSession,
    toggleCamera,
    toggleMic,
    flipCamera,
  } = useOrgaAI();

  const handleStart = async () => {
    await startSession({
      onSessionConnected: () => {
        console.log("Connected!");
      },
    });
  };

  return (
    <View style={styles.container}>
      <OrgaAICameraView
        streamURL={userVideoStream ? userVideoStream.toURL() : undefined}
        containerStyle={styles.cameraViewContainer}
        style={{ width: "100%", height: "100%" }}
      >
        <OrgaAIControls
          connectionState={connectionState}
          isCameraOn={isCameraOn}
          isMicOn={isMicOn}
          onStartSession={startSession}
          onEndSession={endSession}
          onToggleCamera={toggleCamera}
          onToggleMic={toggleMic}
          onFlipCamera={flipCamera}
        />
      </OrgaAICameraView>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#1e293b",
  },
  cameraViewContainer: {
    width: "100%",
    height: "100%",
  },
  placeholder: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  placeholderText: {
    color: "white",
    fontSize: 16,
    marginTop: 12,
  },
});

Troubleshooting

ConfigurationError: OrgaAI must be initialized before use

Ensure that Orga.init is setup and has all of it required properties.

OrgaAI.init({
  logLevel: 'debug',
  fetchSessionConfig: async () => {
    const response = await fetch('/api/orga-client-secrets'); 
    const { ephemeralToken, iceServers } = await response.json();
    return { ephemeralToken, iceServers };
  }
});

Error: useOrgaAI must be used within an OrgaAIProvider

Wrap your app with the OrgaAIProvider

export default function RootLayout() {
  return (
    <OrgaAIProvider>
      <Stack />
    </OrgaAIProvider>
  );
}

Invariant Violation: new NativeEventEmitter() requires a non-null argument

Be sure both peer dependencies react-native-webrtc and react-native-incallmanager are installed.

After these are install make sure you’ve created created a new development build.

npx expo prebuild 
eas build --platform all --profile development