Complete reference for building browser AI apps. No backend needed.
FranzAI Bridge is a Chrome extension that lets you call AI APIs (OpenAI, Anthropic, Google, Mistral) directly from any webpage. It solves two problems:
world: "MAIN" for guaranteed synchronous hook installationconst bridge = window.franzai;
if (bridge) {
const { ok, version } = await bridge.ping();
console.log('Bridge ready:', version);
} else {
console.log('Extension not installed');
}
// Just use fetch() - the extension handles everything
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'gpt-5-mini',
messages: [{ role: 'user', content: 'Hello!' }]
})
});
const data = await response.json();
console.log(data.choices[0].message.content);
| Model | Description |
|---|---|
gpt-5.2 | Newest, best for complex tasks and coding |
gpt-5-mini | Faster, cost-efficient |
gpt-5-nano | Fastest, most affordable |
gpt-5.1-codex-mini | Optimized for code generation |
| Model | Description |
|---|---|
claude-opus-4-5 | Most capable, best for complex tasks |
claude-sonnet-4-5 | Balanced performance and cost |
claude-haiku-4-5 | Fastest, most affordable |
Use the aliases above (they point to the latest dated version in each family).
| Model | Description |
|---|---|
gemini-3-pro-preview | Latest, most capable (preview) |
gemini-3-flash-preview | Fast next-gen model (preview) |
gemini-2.5-pro | High capability (stable) |
gemini-2.5-flash | Fast and efficient (stable) |
gemini-2.5-flash-lite | Lightweight, fastest (stable) |
| Model | Description |
|---|---|
mistral-large-latest | Flagship model (Mistral Large 3) |
mistral-medium-latest | Balanced (Mistral Medium 3.1) |
mistral-small-latest | Fast and efficient (Mistral Small 3.2) |
ministral-8b-latest | Small, edge-optimized |
codestral-latest | Optimized for code completion |
devstral-latest | Optimized for code agents |
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'gpt-5-mini',
messages: [{ role: 'user', content: 'Hello!' }]
})
});
const data = await response.json();
console.log(data.choices[0].message.content);
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'anthropic-version': '2023-06-01'
},
body: JSON.stringify({
model: 'claude-haiku-4-5',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hello!' }]
})
});
const data = await response.json();
console.log(data.content[0].text);
const response = await fetch(
'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contents: [{ parts: [{ text: 'Hello!' }] }]
})
}
);
const data = await response.json();
console.log(data.candidates[0].content.parts[0].text);
const response = await fetch('https://api.mistral.ai/v1/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'mistral-small-latest',
messages: [{ role: 'user', content: 'Hello!' }]
})
});
const data = await response.json();
console.log(data.choices[0].message.content);
Check if the extension is installed and ready.
const { ok, version } = await window.franzai.ping();
// ok: true if extension is ready
// version: string like "2.0.53"
Explicit bridge call. Same as regular fetch() but only routes through the extension when the domain is enabled (still ignores per-request franzai.mode overrides).
const response = await window.franzai.fetch(
'https://api.openai.com/v1/chat/completions',
{ method: 'POST', body: '...' }
);
Control when the bridge intercepts requests.
| Mode | Behavior |
|---|---|
'auto' | Only intercept cross-origin requests (default) |
'always' | Route ALL requests through bridge |
'off' | Disable bridge, use native fetch |
window.franzai.setMode('always');
Get current bridge mode.
const mode = window.franzai.getMode(); // 'auto' | 'always' | 'off'
Current bridge version string (same as ping()).
const version = window.franzai.version; // "2.0.53"
Check if a key is configured in the extension (does not expose the key).
const hasKey = await window.franzai.isKeySet('OPENAI_API_KEY');
Alias for isKeySet. Checks if a specific API key is configured.
const hasKey = await window.franzai.hasApiKey('OPENAI_API_KEY');
Array of configured key names (values are never exposed).
const keys = window.franzai.keys; // ['OPENAI_API_KEY', 'MISTRAL_API_KEY']
Get bridge readiness for the current domain.
| Field | Description |
|---|---|
installed | Extension present |
version | Bridge version string |
domainEnabled | Domain toggle state |
domainSource | user | meta | default |
originAllowed | Origin policy check (if enforced) |
hasApiKeys | At least one API key configured |
ready | Bridge ready to intercept |
reason | Human-readable status message |
const status = await window.franzai.getStatus();
Override bridge routing for a single request when using window.fetch.
Note: This does not affect window.franzai.fetch, but it will still be blocked if the domain is disabled.
// One-off bypass
await fetch('/api/data', { franzai: { mode: 'off' } });
// Or via Request
const req = new Request('/api/data', { franzai: { mode: 'off' } });
await fetch(req);
FranzAI Bridge provides OAuth authentication for Google APIs like Search Console and Analytics. Tokens are stored locally and auto-refreshed.
Authenticate with Google. Shows consent screen on first call, silent on subsequent calls if scopes match.
// Request Search Console access
await franzai.google.auth(['webmasters.readonly']);
// Request multiple scopes
await franzai.google.auth(['webmasters.readonly', 'analytics.readonly']);
Clear stored OAuth tokens. Next auth() call will show account picker again.
await franzai.google.logout();
Fetch from Google APIs with automatic OAuth token injection. Token is refreshed automatically if expired.
// List Search Console sites
const response = await franzai.google.fetch(
'https://searchconsole.googleapis.com/webmasters/v3/sites'
);
const data = await response.json();
console.log(data.siteEntry);
Boolean indicating if user is currently authenticated.
if (franzai.google.isAuthenticated) {
console.log('Logged in as:', franzai.google.email);
}
Email address of authenticated user, or null if not authenticated.
const email = franzai.google.email; // "[email protected]" or null
Array of currently authorized scopes.
const scopes = franzai.google.scopes; // ['webmasters.readonly']
Check if specific scopes are already authorized (without prompting).
const hasAccess = await franzai.google.hasScopes(['analytics.readonly']);
| Shorthand | Full Scope | Access |
|---|---|---|
webmasters.readonly | https://www.googleapis.com/auth/webmasters.readonly | Search Console (read) |
analytics.readonly | https://www.googleapis.com/auth/analytics.readonly | Analytics (read) |
analytics | https://www.googleapis.com/auth/analytics | Analytics (read/write) |
// Complete example: Fetch Search Console performance data
async function getSearchPerformance(siteUrl) {
// Authenticate if needed
if (!franzai.google.isAuthenticated) {
await franzai.google.auth(['webmasters.readonly']);
}
// Fetch performance data
const response = await franzai.google.fetch(
`https://searchconsole.googleapis.com/webmasters/v3/sites/${encodeURIComponent(siteUrl)}/searchAnalytics/query`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
startDate: '2025-12-01',
endDate: '2025-12-31',
dimensions: ['query'],
rowLimit: 10
})
}
);
const data = await response.json();
return data.rows;
}
// Usage
const queries = await getSearchPerformance('sc-domain:example.com');
console.log('Top queries:', queries);
The bridge only intercepts on domains you enable in the extension's Domains tab. You can also enable a domain by adding this meta tag to your page:
<meta name="franzai-bridge" content="enabled">
Valid values: enabled, enabled-by-default, true. Meta-enabled domains show up with source meta.
Add API keys in the extension's Settings panel. Each key has three properties:
| Property | Description |
|---|---|
Name | Variable name (e.g., OPENAI_API_KEY) |
Target | Domain where key is sent (e.g., api.openai.com) |
Value | Your actual API key |
Built-in keys (auto-configured targets):
OPENAI_API_KEY → api.openai.comANTHROPIC_API_KEY → api.anthropic.comGOOGLE_API_KEY → generativelanguage.googleapis.comMISTRAL_API_KEY → api.mistral.aiapi.openai.com will ONLY be sent to that domain - never to other sites.
A full working example you can copy and use:
// Multi-provider AI chat function
async function chat(provider, message) {
const configs = {
openai: {
url: 'https://api.openai.com/v1/chat/completions',
body: { model: 'gpt-5-mini', messages: [{ role: 'user', content: message }] },
extract: data => data.choices[0].message.content
},
anthropic: {
url: 'https://api.anthropic.com/v1/messages',
headers: { 'anthropic-version': '2023-06-01' },
body: { model: 'claude-haiku-4-5', max_tokens: 1024, messages: [{ role: 'user', content: message }] },
extract: data => data.content[0].text
},
gemini: {
url: 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent',
body: { contents: [{ parts: [{ text: message }] }] },
extract: data => data.candidates[0].content.parts[0].text
},
mistral: {
url: 'https://api.mistral.ai/v1/chat/completions',
body: { model: 'mistral-small-latest', messages: [{ role: 'user', content: message }] },
extract: data => data.choices[0].message.content
}
};
const config = configs[provider];
const response = await fetch(config.url, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...config.headers },
body: JSON.stringify(config.body)
});
if (!response.ok) throw new Error(`API error: ${response.status}`);
return config.extract(await response.json());
}
// Usage
const reply = await chat('openai', 'What is 2+2?');
console.log(reply);
async function safeChat(message) {
// Check extension first
if (!window.franzai) {
throw new Error('FranzAI Bridge extension not installed');
}
try {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'gpt-5-mini',
messages: [{ role: 'user', content: message }]
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error?.message || `HTTP ${response.status}`);
}
const data = await response.json();
return data.choices[0].message.content;
} catch (err) {
console.error('Chat failed:', err.message);
throw err;
}
}
| Problem | Solution |
|---|---|
| Extension not detected | Reload page, check extension is enabled in chrome://extensions |
| API key not working | Check key name matches exactly (e.g., OPENAI_API_KEY) |
| "Bridge disabled for this domain" | Enable the domain in the Domains tab or add <meta name="franzai-bridge" content="enabled"> to your page |
| "Destination not allowed" | Add the API domain to Allowed Destinations in Settings |
| 401 Unauthorized | API key is invalid or expired |
| 429 Rate Limited | Too many requests, wait and retry |
world: "MAIN" for race-condition-free hook installationThe extension uses Chrome's world: "MAIN" content script injection to guarantee the fetch hook is installed before any page script runs. This eliminates race conditions entirely.
// Chrome injects this SYNCHRONOUSLY at document_start
// before any <script> tags in the page execute
// 1. MAIN world: hooks window.fetch (same context as page)
// 2. ISOLATED world: relays messages to background
// 3. Background: makes actual fetch (no CORS), injects API keys
// The hook is protected with Object.defineProperty
// - configurable: false (cannot be deleted)
// - writable: false (cannot be overwritten)