1) Create an OAuth2 client → 2) Generate PKCE + state → 3) Send user to `/oauth2/authorize` → 4) Exchange code at `/oauth2/token` → 5) Store `access_token` + `refresh_token` → 6) Call `/oauth2/v1/*` with Bearer token.
1) Get a Client ID
Register your app and obtain client_id (and optional client_secret) from the Developer Dashboard or via REST.
POST /api/v1/oauth2/clients
Content-Type: application/json
{
"clientName": "My Flutter App",
"redirectUri": "myapp://auth/callback",
"requestedScopes": ["api:access", "profile:read"]
}
Save the client secret if you generate one—it is only shown once. Public apps (Flutter, web) typically use PKCE without a client secret.
2) Generate PKCE + State
Generate these per login:
- code_verifier: random 43–128 chars
- code_challenge:
BASE64URL(SHA256(code_verifier)) - state: random string to prevent CSRF
const verifier = generateRandom(64);
const challenge = await sha256Base64Url(verifier);
const state = generateRandom(24);
final verifier = generateRandomString(64);
final challenge = base64UrlEncode(
sha256.convert(utf8.encode(verifier)).bytes
).replaceAll('=', '');
final state = generateRandomString(24);
Keep code_verifier and state until the redirect returns so you can validate and exchange the code.
3) Launch the OAuth Screen
Open the user's browser (or url_launcher in Flutter) to:
GET https://aipass.one/oauth2/authorize
?client_id=YOUR_CLIENT_ID
&response_type=code
&redirect_uri=YOUR_REDIRECT_URI
&scope=api:access profile:read
&state=STATE_VALUE
&code_challenge=PKCE_CHALLENGE
&code_challenge_method=S256
Use custom schemes like myapp://auth/callback on mobile. Validate the returned state before exchanging the code.
4) Exchange Code & Refresh
Exchange the authorization code
POST https://aipass.one/oauth2/token
Content-Type: application/json
{
"grantType": "authorization_code",
"code": "CODE_FROM_REDIRECT",
"codeVerifier": "ORIGINAL_CODE_VERIFIER",
"clientId": "YOUR_CLIENT_ID",
"redirectUri": "YOUR_REDIRECT_URI"
}
Response fields: access_token, refresh_token, expires_in, token_type (Bearer), scope.
Refresh tokens
POST https://aipass.one/oauth2/token
Content-Type: application/json
{
"grantType": "refresh_token",
"refreshToken": "YOUR_REFRESH_TOKEN",
"clientId": "YOUR_CLIENT_ID"
}
On 401 responses from the API, refresh once, then restart OAuth if it fails.
5) Call AI Pass Endpoints
Send the Bearer token in every request. The proxy mirrors OpenAI-style payloads and streams via SSE when stream: true.
Chat completion
curl -X POST https://aipass.one/oauth2/v1/chat/completions \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o-mini",
"messages": [
{"role":"system","content":"You answer briefly."},
{"role":"user","content":"One tip for better focus?"}
],
"max_tokens": 200,
"stream": false
}'
For streaming, set "stream": true and consume Server-Sent Events. Budget errors arrive as JSON; surface them to the user.
6) Vision API (Multimodal)
Same /oauth2/v1/chat/completions endpoint, but with multimodal content. Send text + images for analysis.
Structure: Image + Text
The content field accepts an array of parts instead of a string:
{
"model": "gpt-4o-mini",
"messages": [{
"role": "user",
"content": [
{
"type": "text",
"text": "What's in this image?"
},
{
"type": "image_url",
"image_url": {
"url": "data:image/jpeg;base64,/9j/4AAQSkZJRg..."
}
}
]
}],
"max_tokens": 2000
}
Receipt scanning example
{
"model": "gemini/gemini-2.5-flash-lite",
"messages": [{
"role": "user",
"content": [
{
"type": "text",
"text": "Extract receipt data in JSON: {amount, currency, date, merchant, category, confidence}"
},
{
"type": "image_url",
"image_url": {
"url": "data:image/jpeg;base64,BASE64_ENCODED_IMAGE"
}
}
]
}],
"temperature": 0.7,
"max_tokens": 2000
}
// Analyze receipt image
Future<Map<String, dynamic>> analyzeReceipt(
String imageBase64
) async {
final response = await http.post(
Uri.parse('$baseUrl/oauth2/v1/chat/completions'),
headers: {
'Authorization': 'Bearer $accessToken',
'Content-Type': 'application/json',
},
body: jsonEncode({
'model': 'gemini/gemini-2.5-flash-lite',
'messages': [{
'role': 'user',
'content': [
{
'type': 'text',
'text': 'Extract: {amount, currency, date, merchant, category}'
},
{
'type': 'image_url',
'image_url': {
'url': 'data:image/jpeg;base64,$imageBase64'
}
}
]
}],
'max_tokens': 2000,
}),
);
final data = jsonDecode(response.body);
final content = data['choices'][0]['message']['content'];
// Extract JSON from response
final jsonMatch = RegExp(r'\{[\s\S]*\}').firstMatch(content);
return jsonMatch != null
? jsonDecode(jsonMatch.group(0)!)
: {};
}
async function analyzeReceipt(imageBase64) {
const response = await fetch(
'https://aipass.one/oauth2/v1/chat/completions',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'gemini/gemini-2.5-flash-lite',
messages: [{
role: 'user',
content: [
{
type: 'text',
text: 'Extract: {amount, currency, date, merchant, category}'
},
{
type: 'image_url',
image_url: {
url: `data:image/jpeg;base64,${imageBase64}`
}
}
]
}],
max_tokens: 2000
})
}
);
const data = await response.json();
const content = data.choices[0].message.content;
const jsonMatch = content.match(/\{[\s\S]*\}/);
return jsonMatch ? JSON.parse(jsonMatch[0]) : {};
}
Compress images to ~800KB before encoding to base64. Vision models support: JPEG, PNG, GIF, WebP.
7) Usage & Balance Tracking
Monitor your AI API costs and remaining budget in real-time.
GET https://aipass.one/api/v1/usage/me/summary
Authorization: Bearer ACCESS_TOKEN
// Response:
{
"success": true,
"message": "Usage summary retrieved successfully",
"data": {
"totalCost": 2.45,
"maxBudget": 10.00,
"remainingBudget": 7.55
},
"timestamp": "2024-01-15T14:30:00Z"
}
Future<Map<String, dynamic>> getBalance() async {
final response = await http.get(
Uri.parse('$baseUrl/api/v1/usage/me/summary'),
headers: {'Authorization': 'Bearer $accessToken'},
);
if (response.statusCode == 200) {
final result = jsonDecode(response.body);
return result['data'];
}
throw Exception('Failed to get balance');
}
async function getUserBalance() {
const response = await fetch(
'https://aipass.one/api/v1/usage/me/summary',
{
headers: {
'Authorization': `Bearer ${accessToken}`
}
}
);
const result = await response.json();
if (result.success) {
console.log(`Spent: $${result.data.totalCost}`);
console.log(`Remaining: $${result.data.remainingBudget}`);
return result.data;
}
throw new Error(result.message);
}
Cache balance data and refresh periodically (every 5-10 minutes) to reduce API calls.
8) All REST Endpoints
GET /oauth2/v1/modelsGET /oauth2/v1/models/{id}POST /oauth2/v1/chat/completionsPOST /oauth2/v1/embeddingsPOST /oauth2/v1/images/generationsPOST /oauth2/v1/images/editsPOST /oauth2/v1/images/variationsPOST /oauth2/v1/audio/speechPOST /oauth2/v1/audio/transcriptionsPOST /oauth2/v1/videosPOST /oauth2/v1/videos/*/remixGET /oauth2/v1/videos/{id}GET /oauth2/v1/videos/{id}/contentGET /oauth2/userinfo (needs profile:read)GET /api/v1/usage/me/summaryPOST /oauth2/revoke?token=ACCESS_TOKENOnly allowlisted endpoints above are proxied. All calls require api:access scope.
9) AI Agent Prompts
Copy-paste these into another agent to auto-generate platform-specific code. Text is intentionally short for LLM consumption.