Step 1: Get Your Client ID
You need a Client ID to use the SDK. Get one for free in 30 seconds:
- Go to AI Pass Developer Panel
- Sign up or log in
- Click "Create OAuth App"
- Copy your Client ID
Tip: Your Client ID looks like: aipass_xxxxxxxxxxxx
5-Minute Quick Start
Get a working AI chat app in 5 minutes. Copy and paste these steps:
1. Create new Expo project
npx create-expo-app@latest my-ai-app
cd my-ai-app
2. Install dependencies
npx expo install expo-auth-session expo-crypto expo-linking expo-secure-store expo-web-browser @react-native-async-storage/async-storage buffer
3. Download the SDK
# Create lib folder and download SDK
mkdir -p lib
curl -o lib/AiPassSDK.js https://aipass.one/docs/sdk/reactnative/AiPassSDK.js
Or download manually and save to lib/AiPassSDK.js
4. Configure app.json
Add scheme to your app.json:
{
"expo": {
"scheme": "myaiapp"
}
}
5. Replace App.js with this complete example
import React, { useState } from 'react';
import {
View, Text, TextInput, Button,
FlatList, ActivityIndicator, StyleSheet, SafeAreaView
} from 'react-native';
import { AiPassProvider, useAiPass, useAuthState, useBalance, useAiPassEvent } from './lib/AiPassSDK';
// ============================================
// REPLACE WITH YOUR CLIENT ID FROM STEP 1
// ============================================
const CLIENT_ID = 'YOUR_CLIENT_ID_HERE';
function ChatScreen() {
const { sdk, login, logout, openDashboard } = useAiPass();
const { isReady, isAuthenticated } = useAuthState();
const { balance } = useBalance();
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [loading, setLoading] = useState(false);
// Handle budget exceeded
useAiPassEvent('budgetExceeded', ({ message }) => {
alert('Budget exceeded: ' + message);
});
// Loading state
if (!isReady) {
return (
<View style={styles.center}>
<ActivityIndicator size="large" color="#8a4fff" />
<Text style={styles.loadingText}>Loading AI Pass...</Text>
</View>
);
}
// Login screen
if (!isAuthenticated) {
return (
<View style={styles.center}>
<Text style={styles.title}>AI Chat</Text>
<Text style={styles.subtitle}>Powered by AI Pass</Text>
<Button title="Sign in with AI Pass" onPress={login} color="#8a4fff" />
</View>
);
}
// Send message
const sendMessage = async () => {
if (!input.trim() || loading) return;
const userMessage = { role: 'user', content: input };
setMessages(prev => [...prev, userMessage]);
setInput('');
setLoading(true);
try {
const result = await sdk.generateCompletion({
messages: [...messages, userMessage],
model: 'gemini/gemini-2.5-flash-lite',
maxTokens: 1000
});
const aiMessage = result.choices[0].message;
setMessages(prev => [...prev, aiMessage]);
} catch (error) {
alert('Error: ' + error.message);
// Remove the user message if there was an error
setMessages(prev => prev.slice(0, -1));
} finally {
setLoading(false);
}
};
// Chat screen
return (
<SafeAreaView style={styles.container}>
<View style={styles.header}>
<Text style={styles.balance}>
Balance: ${balance?.toFixed(2) || '...'}
</Text>
<View style={styles.headerButtons}>
<Button title="Dashboard" onPress={openDashboard} />
<Button title="Logout" onPress={logout} color="#ff4444" />
</View>
</View>
<FlatList
data={messages}
keyExtractor={(_, i) => i.toString()}
style={styles.messageList}
renderItem={({ item }) => (
<View style={[
styles.message,
item.role === 'user' ? styles.userMsg : styles.aiMsg
]}>
<Text style={styles.messageText}>{item.content}</Text>
</View>
)}
ListEmptyComponent={
<Text style={styles.emptyText}>Send a message to start chatting!</Text>
}
/>
<View style={styles.inputRow}>
<TextInput
style={styles.input}
value={input}
onChangeText={setInput}
placeholder="Type a message..."
editable={!loading}
onSubmitEditing={sendMessage}
/>
<Button
title={loading ? '...' : 'Send'}
onPress={sendMessage}
disabled={loading || !input.trim()}
color="#8a4fff"
/>
</View>
</SafeAreaView>
);
}
export default function App() {
return (
<AiPassProvider config={{ clientId: CLIENT_ID, debug: __DEV__ }}>
<ChatScreen />
</AiPassProvider>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#fff' },
center: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 },
title: { fontSize: 32, fontWeight: 'bold', color: '#8a4fff', marginBottom: 8 },
subtitle: { fontSize: 16, color: '#666', marginBottom: 24 },
loadingText: { marginTop: 12, color: '#666' },
header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', padding: 16, borderBottomWidth: 1, borderBottomColor: '#eee' },
headerButtons: { flexDirection: 'row', gap: 8 },
balance: { fontSize: 16, fontWeight: '600' },
messageList: { flex: 1, padding: 16 },
message: { padding: 12, borderRadius: 12, marginVertical: 4, maxWidth: '80%' },
userMsg: { backgroundColor: '#8a4fff', alignSelf: 'flex-end' },
aiMsg: { backgroundColor: '#f0f0f0', alignSelf: 'flex-start' },
messageText: { fontSize: 16 },
emptyText: { textAlign: 'center', color: '#999', marginTop: 40 },
inputRow: { flexDirection: 'row', padding: 16, borderTopWidth: 1, borderTopColor: '#eee', alignItems: 'center' },
input: { flex: 1, borderWidth: 1, borderColor: '#ddd', borderRadius: 24, paddingHorizontal: 16, paddingVertical: 12, marginRight: 8, fontSize: 16 }
});
6. Run the app
npx expo start
Important: Replace YOUR_CLIENT_ID_HERE with your actual Client ID from Step 1!
Testing Checklist
- ✅ App loads and shows "Sign in with AI Pass" button
- ✅ Clicking login opens browser for authentication
- ✅ After login, chat screen appears with your balance
- ✅ Can send messages and receive AI responses
- ✅ Dashboard button opens AI Pass in browser
- ✅ Logout returns to login screen
Overview
The AI Pass React Native SDK provides everything you need to integrate AI capabilities into your mobile application:
OAuth2 + PKCE
Secure browser-based authentication with automatic token management
Secure Storage
Tokens stored with expo-secure-store (AsyncStorage fallback)
Auto Refresh
Automatic token refresh with AppState awareness
AI APIs
Chat, image, video, speech, embeddings and more
React Hooks
useAiPass, useAuthState, useBalance, useChat hooks
Streaming Support
Real-time streaming for chat completions
Prerequisites
Required Expo packages:
# Core dependencies
npx expo install expo-auth-session expo-crypto expo-linking expo-secure-store expo-web-browser @react-native-async-storage/async-storage buffer
# Optional: For audio features (TTS, speech-to-text)
npx expo install expo-av expo-file-system
Note: The SDK is designed for Expo projects. For bare React Native, additional configuration may be required.
Installation
1. Download the SDK
Download AiPassSDK.js and add it to your project (e.g., lib/AiPassSDK.js).
2. Configure Deep Linking
Add the scheme to your app.json:
{
"expo": {
"scheme": "your-app-scheme",
"ios": {
"bundleIdentifier": "com.yourcompany.yourapp"
},
"android": {
"package": "com.yourcompany.yourapp"
}
}
}
3. Wrap Your App with Provider
import { AiPassProvider } from './lib/AiPassSDK';
export default function App() {
return (
<AiPassProvider config={{ clientId: 'your_client_id' }}>
<YourApp />
</AiPassProvider>
);
}
Quick Start
Basic Usage with Hooks
import React from 'react';
import { View, Button, Text, ActivityIndicator } from 'react-native';
import { useAiPass, useAuthState, useBalance } from './lib/AiPassSDK';
function HomeScreen() {
const { login, logout, sdk } = useAiPass();
const { isReady, isAuthenticated } = useAuthState();
const { balance } = useBalance();
if (!isReady) {
return <ActivityIndicator />;
}
if (!isAuthenticated) {
return (
<View>
<Button title="Sign in with AI Pass" onPress={login} />
</View>
);
}
return (
<View>
<Text>Balance: ${balance?.toFixed(2)}</Text>
<Button title="Generate Text" onPress={async () => {
const result = await sdk.generateCompletion({
prompt: 'Hello, AI!',
model: 'gemini/gemini-2.5-flash-lite'
});
console.log(result.choices[0].message.content);
}} />
<Button title="Logout" onPress={logout} />
</View>
);
}
Direct SDK Usage (Without Provider)
import { AiPass } from './lib/AiPassSDK';
// Initialize once at app startup
await AiPass.initialize({ clientId: 'your_client_id' });
// Check authentication
if (!AiPass.isAuthenticated()) {
await AiPass.login();
}
// Make API calls
const result = await AiPass.generateCompletion({
prompt: 'Explain React Native',
model: 'gemini/gemini-2.5-flash-lite',
maxTokens: 500
});
Simple Examples
Copy-paste these components into your app. All examples include proper error handling.
1. Login Button
import React from 'react';
import { Button, View, Text } from 'react-native';
import { useAuthState } from './lib/AiPassSDK';
function LoginButton() {
const { isReady, isAuthenticated, login, logout, error } = useAuthState();
if (!isReady) return <Text>Loading...</Text>;
if (error) return <Text>Error: {error.message}</Text>;
return isAuthenticated
? <Button title="Logout" onPress={logout} color="#ff4444" />
: <Button title="Login with AI Pass" onPress={login} color="#8a4fff" />;
}
2. Balance Display
import React from 'react';
import { Text, TouchableOpacity, StyleSheet } from 'react-native';
import { useBalance } from './lib/AiPassSDK';
function BalanceWidget() {
const { balance, refreshBalance, isAuthenticated } = useBalance();
if (!isAuthenticated) return null;
const isLow = balance !== null && balance < 1;
return (
<TouchableOpacity onPress={refreshBalance} style={styles.container}>
<Text style={[styles.text, isLow && styles.lowBalance]}>
Balance: ${balance?.toFixed(2) || '...'}
</Text>
{isLow && <Text style={styles.warning}>Low balance!</Text>}
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
container: { padding: 8 },
text: { fontSize: 16, fontWeight: '600', color: '#333' },
lowBalance: { color: '#ff4444' },
warning: { fontSize: 12, color: '#ff4444' }
});
3. Simple Chat (with useChat hook)
import React, { useState } from 'react';
import { View, TextInput, Button, Text, ActivityIndicator, StyleSheet } from 'react-native';
import { useChat } from './lib/AiPassSDK';
function SimpleChat() {
const { generateCompletion, isLoading, error } = useChat();
const [input, setInput] = useState('');
const [response, setResponse] = useState('');
const askAI = async () => {
if (!input.trim()) return;
try {
const result = await generateCompletion({
prompt: input,
model: 'gemini/gemini-2.5-flash-lite'
});
setResponse(result.choices[0].message.content);
} catch (err) {
setResponse('Error: ' + err.message);
}
};
return (
<View style={styles.container}>
<TextInput
value={input}
onChangeText={setInput}
placeholder="Ask anything..."
style={styles.input}
/>
<Button title={isLoading ? 'Thinking...' : 'Ask'} onPress={askAI} disabled={isLoading} />
{isLoading && <ActivityIndicator style={styles.loader} />}
{response ? <Text style={styles.response}>{response}</Text> : null}
{error && <Text style={styles.error}>{error.message}</Text>}
</View>
);
}
const styles = StyleSheet.create({
container: { padding: 16 },
input: { borderWidth: 1, borderColor: '#ddd', borderRadius: 8, padding: 12, marginBottom: 12 },
loader: { marginVertical: 12 },
response: { marginTop: 12, padding: 12, backgroundColor: '#f5f5f5', borderRadius: 8 },
error: { marginTop: 12, color: '#ff4444' }
});
4. Handle Budget Exceeded
import React from 'react';
import { Alert } from 'react-native';
import { useAiPassEvent, useAiPass } from './lib/AiPassSDK';
function BudgetHandler() {
const { openDashboard } = useAiPass();
// This hook auto-subscribes and cleans up
useAiPassEvent('budgetExceeded', ({ message }) => {
Alert.alert(
'Budget Exceeded',
message,
[
{ text: 'Cancel', style: 'cancel' },
{ text: 'Add Funds', onPress: () => openDashboard() }
]
);
});
return null; // This component just handles events
}
// Usage: Add <BudgetHandler /> anywhere in your app tree
5. Generate Image
import React, { useState } from 'react';
import { View, TextInput, Button, Image, Text, StyleSheet } from 'react-native';
import { useAiPass } from './lib/AiPassSDK';
function ImageGenerator() {
const { sdk } = useAiPass();
const [prompt, setPrompt] = useState('');
const [imageUrl, setImageUrl] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const generate = async () => {
if (!prompt.trim()) return;
setLoading(true);
setError(null);
try {
const result = await sdk.generateImage({
prompt,
model: 'gpt-image-1',
size: '1024x1024'
});
setImageUrl(result.data[0].url);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
return (
<View style={styles.container}>
<TextInput
value={prompt}
onChangeText={setPrompt}
placeholder="Describe an image..."
style={styles.input}
/>
<Button
title={loading ? 'Generating...' : 'Generate Image'}
onPress={generate}
disabled={loading || !prompt.trim()}
/>
{error && <Text style={styles.error}>{error}</Text>}
{imageUrl && <Image source={{ uri: imageUrl }} style={styles.image} />}
</View>
);
}
const styles = StyleSheet.create({
container: { padding: 16 },
input: { borderWidth: 1, borderColor: '#ddd', borderRadius: 8, padding: 12, marginBottom: 12 },
image: { width: '100%', height: 300, borderRadius: 8, marginTop: 12 },
error: { color: '#ff4444', marginTop: 8 }
});
6. Text-to-Speech
Requires: npx expo install expo-av
import React, { useState } from 'react';
import { View, TextInput, Button, Text, StyleSheet } from 'react-native';
import { Audio } from 'expo-av';
import { useAiPass } from './lib/AiPassSDK';
function TextToSpeech() {
const { sdk } = useAiPass();
const [text, setText] = useState('Hello from AI Pass!');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const speak = async () => {
if (!text.trim()) return;
setLoading(true);
setError(null);
try {
const audioBlob = await sdk.generateSpeech({
text,
model: 'tts-1',
voice: 'nova'
});
// Convert blob to base64 and play
const reader = new FileReader();
reader.readAsDataURL(audioBlob);
reader.onloadend = async () => {
try {
const base64 = reader.result.split(',')[1];
const { sound } = await Audio.Sound.createAsync({
uri: `data:audio/mp3;base64,${base64}`
});
await sound.playAsync();
} catch (playError) {
setError('Playback error: ' + playError.message);
}
};
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
return (
<View style={styles.container}>
<TextInput
value={text}
onChangeText={setText}
placeholder="Enter text to speak..."
style={styles.input}
multiline
/>
<Button
title={loading ? 'Generating...' : 'Speak'}
onPress={speak}
disabled={loading || !text.trim()}
/>
{error && <Text style={styles.error}>{error}</Text>}
</View>
);
}
const styles = StyleSheet.create({
container: { padding: 16 },
input: { borderWidth: 1, borderColor: '#ddd', borderRadius: 8, padding: 12, marginBottom: 12, minHeight: 80 },
error: { color: '#ff4444', marginTop: 8 }
});
Configuration Options
Pass these options to AiPass.initialize() or the AiPassProvider:
| Option | Type | Default | Description |
|---|---|---|---|
| clientId | string | required | Your AI Pass client ID |
| baseUrl | string | 'https://aipass.one' | API base URL |
| scopes | string[] | ['api:access', 'profile:read'] | OAuth scopes to request |
| storageKey | string | 'aipass_oauth_token' | Key for token storage |
| tokenRefreshBuffer | number | 300000 (5 min) | Refresh tokens this many ms before expiry |
| enableBackgroundRefresh | boolean | true | Enable automatic token refresh |
| debug | boolean | false | Enable debug logging |
React Hooks
useAiPass()
Main hook providing access to SDK instance and common actions.
const {
sdk,
isReady,
isAuthenticated,
balance,
error,
login,
logout,
refreshBalance,
openDashboard,
showPaymentModal,
getTokenInfo
} = useAiPass();
- sdk - The AiPassSDK instance
- isReady - Whether SDK is initialized
- isAuthenticated - Whether user is logged in
- balance - Current user balance
- error - Initialization error (if any)
- login() - Start OAuth login flow
- logout() - Logout and revoke token
- refreshBalance() - Manually refresh balance
- openDashboard() - Open AI Pass dashboard in browser
- showPaymentModal() - Emit payment required event
- getTokenInfo() - Get token expiration info
useAuthState()
Hook for authentication state with login/logout actions.
const { isReady, isAuthenticated, error, login, logout } = useAuthState();
useBalance()
Hook for tracking user balance.
const { balance, refreshBalance, isAuthenticated, showPaymentModal } = useBalance();
useChat()
Hook for chat completion with loading state management.
const { generateCompletion, isLoading, error, isReady, isAuthenticated } = useChat();
// Usage
const handleSend = async () => {
const result = await generateCompletion({
messages: [{ role: 'user', content: 'Hello!' }],
model: 'gemini/gemini-2.5-flash-lite'
});
console.log(result.choices[0].message.content);
};
useAiPassEvent()
Hook for subscribing to SDK events.
import { Alert } from 'react-native';
// Subscribe to budget exceeded events
useAiPassEvent('budgetExceeded', (data) => {
Alert.alert('Budget Exceeded', data.message);
});
// Subscribe to payment required events
useAiPassEvent('paymentRequired', ({ balance, dashboardUrl }) => {
navigation.navigate('AddFunds', { balance });
});
API Methods
Authentication & Navigation
| Method | Description |
|---|---|
| initialize(config) | Initialize the SDK with configuration |
| login() | Start OAuth2 PKCE authentication flow |
| logout() | Logout and revoke tokens |
| isAuthenticated() | Check if user is authenticated |
| getAccessToken() | Get current access token (refreshes if needed) |
| refreshAccessToken() | Manually refresh the access token |
| getTokenInfo() | Get token expiration info |
| openDashboard() | Open AI Pass dashboard in device browser |
| openDeveloperPanel() | Open developer panel in device browser |
| showPaymentModal(options?) | Emit 'paymentRequired' event for your app to handle |
Chat Completions
// Simple completion
const result = await sdk.generateCompletion({
prompt: 'Write a haiku about coding',
model: 'gemini/gemini-2.5-flash-lite',
temperature: 0.7,
maxTokens: 100
});
// With message history
const result = await sdk.generateCompletion({
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'Hello!' }
],
model: 'claude/claude-sonnet-4-20250514'
});
// Streaming
for await (const chunk of sdk.generateCompletion({
prompt: 'Tell me a story',
stream: true
})) {
const content = chunk.choices[0]?.delta?.content;
if (content) console.log(content);
}
Image Generation
// Generate image
const result = await sdk.generateImage({
prompt: 'A futuristic city at sunset',
model: 'gpt-image-1',
size: '1024x1024',
n: 1
});
console.log(result.data[0].url);
// Edit image (with FormData)
const result = await sdk.editImage({
image: imageFile,
prompt: 'Add flying cars',
model: 'gpt-image-1'
});
Audio
// Text-to-Speech
const audioBlob = await sdk.generateSpeech({
text: 'Hello, welcome to AI Pass!',
model: 'tts-1',
voice: 'alloy',
responseFormat: 'mp3'
});
// Speech-to-Text
const result = await sdk.transcribeAudio({
audioFile: audioFile,
model: 'whisper-1',
language: 'en'
});
Video Generation
// Generate video
const result = await sdk.generateVideo({
prompt: 'A rocket launching into space',
model: 'gemini/veo-3.1-generate-preview',
size: '1280x720',
seconds: 4
});
// Check status
const status = await sdk.getVideoStatus(result.id);
// Download when ready
if (status.status === 'completed') {
const videoBlob = await sdk.downloadVideo(result.id);
}
User & Balance
// Get user info
const userInfo = await sdk.getUserInfo();
// Get balance
const balance = await sdk.getUserBalance();
console.log('Remaining:', balance.data.remainingBudget);
// Get available models
const models = await sdk.getModels();
Events
Subscribe to SDK events for real-time updates:
import { AiPass, useAiPassEvent } from './lib/AiPassSDK';
import { Alert } from 'react-native';
// Option 1: Using the hook (recommended in components)
function MyComponent() {
useAiPassEvent('budgetExceeded', ({ message }) => {
Alert.alert('Budget Exceeded', message);
});
useAiPassEvent('paymentRequired', ({ balance, dashboardUrl }) => {
// Navigate to your payment screen
navigation.navigate('AddFunds', { balance });
});
return <YourUI />;
}
// Option 2: Direct subscription
AiPass.on('login', (tokenData) => {
console.log('User logged in');
});
AiPass.on('logout', ({ reason }) => {
console.log('User logged out:', reason);
});
AiPass.on('balanceUpdated', ({ balance }) => {
console.log('New balance:', balance);
});
// Remove listener
const unsubscribe = AiPass.on('tokenRefreshed', () => {});
unsubscribe(); // Call to remove
Complete Example
A full example app with authentication and chat:
// App.js
import React from 'react';
import { AiPassProvider } from './lib/AiPassSDK';
import ChatScreen from './screens/ChatScreen';
export default function App() {
return (
<AiPassProvider config={{
clientId: 'your_client_id',
debug: __DEV__
}}>
<ChatScreen />
</AiPassProvider>
);
}
// screens/ChatScreen.js
import React, { useState } from 'react';
import {
View, Text, TextInput, Button,
FlatList, ActivityIndicator, StyleSheet, Alert
} from 'react-native';
import { useAiPass, useAuthState, useBalance } from '../lib/AiPassSDK';
export default function ChatScreen() {
const { sdk, login, logout } = useAiPass();
const { isReady, isAuthenticated } = useAuthState();
const { balance } = useBalance();
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [loading, setLoading] = useState(false);
if (!isReady) {
return (
<View style={styles.center}>
<ActivityIndicator size="large" />
</View>
);
}
if (!isAuthenticated) {
return (
<View style={styles.center}>
<Text style={styles.title}>Welcome to AI Chat</Text>
<Button title="Sign in with AI Pass" onPress={login} />
</View>
);
}
const sendMessage = async () => {
if (!input.trim()) return;
const userMessage = { role: 'user', content: input };
setMessages(prev => [...prev, userMessage]);
setInput('');
setLoading(true);
try {
const result = await sdk.generateCompletion({
messages: [...messages, userMessage],
model: 'gemini/gemini-2.5-flash-lite',
maxTokens: 1000
});
const aiMessage = result.choices[0].message;
setMessages(prev => [...prev, aiMessage]);
} catch (error) {
Alert.alert('Error', error.message);
} finally {
setLoading(false);
}
};
return (
<View style={styles.container}>
<View style={styles.header}>
<Text>Balance: ${balance?.toFixed(2) || '...'}</Text>
<Button title="Logout" onPress={logout} />
</View>
<FlatList
data={messages}
keyExtractor={(_, i) => i.toString()}
renderItem={({ item }) => (
<View style={[
styles.message,
item.role === 'user' ? styles.userMsg : styles.aiMsg
]}>
<Text>{item.content}</Text>
</View>
)}
/>
<View style={styles.inputRow}>
<TextInput
style={styles.input}
value={input}
onChangeText={setInput}
placeholder="Type a message..."
editable={!loading}
/>
<Button
title={loading ? '...' : 'Send'}
onPress={sendMessage}
disabled={loading}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 16 },
center: { flex: 1, justifyContent: 'center', alignItems: 'center' },
title: { fontSize: 24, marginBottom: 20 },
header: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 16 },
message: { padding: 12, borderRadius: 8, marginVertical: 4, maxWidth: '80%' },
userMsg: { backgroundColor: '#e3f2fd', alignSelf: 'flex-end' },
aiMsg: { backgroundColor: '#f5f5f5', alignSelf: 'flex-start' },
inputRow: { flexDirection: 'row', marginTop: 16 },
input: { flex: 1, borderWidth: 1, borderColor: '#ddd', borderRadius: 8, padding: 12, marginRight: 8 }
});
Troubleshooting
Deep Link Not Working
Make sure your app.json has the correct scheme configured and you've rebuilt the app after changes.
Token Storage Issues
The SDK automatically falls back to AsyncStorage if SecureStore has size limitations (2KB). For large tokens, this is handled automatically.
Expo Go vs Development Build
In Expo Go, deep linking uses exp:// scheme. For production, create a development build with your custom scheme.
Enable Debug Mode
AiPass.initialize({
clientId: 'your_client_id',
debug: true // See detailed logs
});