BYOK - Bring Your Own Key
Overview
PhotoSwipe Pro with AI SEO supports BYOK (Bring Your Own Key) - allowing you to use your own Gemini or OpenRouter API key instead of relying on a server-provided key.
Why BYOK?
For Users
✅ Pay only for what you use - Direct billing from AI provider
✅ No usage quotas - Process unlimited images
✅ Faster processing - Direct API access (no server proxy)
✅ Privacy - Your requests go directly to AI provider
✅ Flexibility - Choose your own AI model and provider
For Server Owners
✅ $0 AI costs - Users pay their own AI bills
✅ Infinitely scalable - No cost risk for high-volume users
✅ Simple pricing - Charge for PhotoSwipe Pro license only
✅ No cost management - No quotas, billing, or cost monitoring needed
How It Works
Traditional Model (Server-Provided Key)
User → Server (validates license) → AI Provider (server's API key) → Response
Server owner pays all AI costs 💸
BYOK Model (User-Provided Key)
User → Server (validates license) → AI Provider (user's API key) → Response
User pays AI costs directly ✅
What's Required
| Component | Required? | Who Provides? |
|---|---|---|
| PhotoSwipe Pro License | ✅ YES | User purchases from you |
| AI API Key (Gemini/OpenRouter) | ✅ YES | User brings their own |
Business model: You sell PhotoSwipe Pro license. Users bring their own AI API keys.
Getting API Keys
Option 1: Gemini (Recommended - FREE tier available)
- Go to https://aistudio.google.com/app/apikey
- Click "Create API Key"
- Copy your API key (starts with
AIza...) - Cost: FREE up to 15 requests/minute, then ~$0.001 per image
Option 2: OpenRouter (Pay-per-use)
- Go to https://openrouter.ai/
- Sign up and add credits
- Go to Keys → Create Key
- Copy your API key (starts with
sk-or-v1-...) - Cost: ~$0.01 per image (GPT-4o Vision)
Usage
Method 1: Set API Key in Constructor (Recommended)
import { CaptionProvider } from 'photoswipe-pro/ai';
// User provides their own Gemini API key
const provider = new CaptionProvider({
baseUrl: '/api/ai',
apiKey: 'AIzaSyABC123...your-gemini-key', // User's API key
provider: 'gemini' // Which AI provider to use
});
// PhotoSwipe Pro license still required
const result = await provider.generate({
url: 'https://example.com/photo.jpg',
licenseKey: 'your-photoswipe-pro-license' // Still validates
});
console.log(result.alt); // AI-generated alt text
###Method 2: Pass API Key Per Request
const provider = new CaptionProvider({ baseUrl: '/api/ai' });
const result = await provider.generate({
url: 'https://example.com/photo.jpg',
licenseKey: 'your-photoswipe-pro-license',
apiKey: 'AIzaSyABC123...your-gemini-key', // Per-request API key
provider: 'gemini'
});
Method 3: Batch Processing with BYOK
const provider = new CaptionProvider({
baseUrl: '/api/ai',
apiKey: 'AIzaSyABC123...', // User's Gemini key
provider: 'gemini'
});
// Process 100 images with user's own API key
const result = await provider.generateBatch({
images: [
{ url: 'photo1.jpg' },
{ url: 'photo2.jpg' },
// ... 100 images
],
licenseKey: 'your-photoswipe-pro-license'
});
console.log(`Processed ${result.summary.success}/100 images`);
// Cost: $0.10 (100 × $0.001) - billed to user's Gemini account
Error Handling
When API Key is Required
try {
const result = await provider.generate({
url: 'photo.jpg',
licenseKey: 'your-license'
// No apiKey provided
});
} catch (error) {
if (error.byok) {
// Server requires user to provide their own API key
console.error('Please provide your Gemini/OpenRouter API key');
console.error('Get yours at:', error.message);
// Prompt user for API key
const userApiKey = prompt('Enter your Gemini API key:');
// Retry with user's API key
const result = await provider.generate({
url: 'photo.jpg',
licenseKey: 'your-license',
apiKey: userApiKey,
provider: 'gemini'
});
}
}
UI Implementation
Example: Prompt for API Key on First Use
import { CaptionProvider } from 'photoswipe-pro/ai';
class AIService {
constructor() {
this.provider = new CaptionProvider({ baseUrl: '/api/ai' });
this.apiKey = localStorage.getItem('user_ai_api_key');
this.aiProvider = localStorage.getItem('user_ai_provider') || 'gemini';
}
async generateCaption(url, licenseKey) {
try {
return await this.provider.generate({
url,
licenseKey,
apiKey: this.apiKey,
provider: this.aiProvider
});
} catch (error) {
if (error.byok && !this.apiKey) {
// Prompt user for API key on first use
await this.promptForApiKey();
// Retry with new API key
return this.generateCaption(url, licenseKey);
}
throw error;
}
}
async promptForApiKey() {
const modal = `
<div class="api-key-modal">
<h2>AI Caption Generation Setup</h2>
<p>To use AI-powered captions, please provide your own AI API key:</p>
<label>
<input type="radio" name="provider" value="gemini" checked />
Gemini (FREE tier, ~$0.001/image)
<a href="https://aistudio.google.com/app/apikey" target="_blank">Get key →</a>
</label>
<label>
<input type="radio" name="provider" value="openrouter" />
OpenRouter (~$0.01/image, better quality)
<a href="https://openrouter.ai/" target="_blank">Get key →</a>
</label>
<input type="text" id="apiKeyInput" placeholder="Paste your API key here" />
<button id="saveApiKey">Save & Continue</button>
</div>
`;
// Show modal and wait for user input
const { apiKey, provider } = await showModalAndWaitForInput(modal);
// Save to localStorage
localStorage.setItem('user_ai_api_key', apiKey);
localStorage.setItem('user_ai_provider', provider);
this.apiKey = apiKey;
this.aiProvider = provider;
}
}
Example: Settings Panel
// Settings UI
function renderAISettings() {
const currentKey = localStorage.getItem('user_ai_api_key');
const currentProvider = localStorage.getItem('user_ai_provider') || 'gemini';
return `
<div class="ai-settings">
<h3>AI Caption Settings</h3>
<label>
Provider:
<select id="aiProvider">
<option value="gemini" ${currentProvider === 'gemini' ? 'selected' : ''}>
Gemini (FREE tier)
</option>
<option value="openrouter" ${currentProvider === 'openrouter' ? 'selected' : ''}>
OpenRouter (GPT-4o)
</option>
</select>
</label>
<label>
Your API Key:
<input type="password" id="aiApiKey"
value="${currentKey || ''}"
placeholder="Enter your API key" />
<small>
${currentProvider === 'gemini'
? 'Get your free Gemini API key at https://aistudio.google.com/app/apikey'
: 'Get your OpenRouter API key at https://openrouter.ai/'}
</small>
</label>
<button onclick="saveAISettings()">Save Settings</button>
<p class="cost-info">
${currentProvider === 'gemini'
? '✓ Free tier: 15 requests/minute, then ~$0.001 per image'
: 'Cost: ~$0.01 per image (paid from your OpenRouter account)'}
</p>
</div>
`;
}
function saveAISettings() {
const provider = document.getElementById('aiProvider').value;
const apiKey = document.getElementById('aiApiKey').value;
localStorage.setItem('user_ai_provider', provider);
localStorage.setItem('user_ai_api_key', apiKey);
alert('Settings saved! You can now use AI captions.');
}
Server Configuration
Enable BYOK Mode
# .env
# Don't set AI API keys on server - users provide their own
# GEMINI_API_KEY= # Leave empty
# OPENROUTER_API_KEY= # Leave empty
# License validation still required
LEMON_SQUEEZY_API_KEY=your-ls-key
LEMON_SQUEEZY_STORE_ID=12345
LEMON_SQUEEZY_PRODUCT_ID=67890
Behavior
- ✅ If server has no API key → Requires user to provide API key (BYOK)
- ✅ If server has API key → Falls back to server key if user doesn't provide one
- ✅ PhotoSwipe Pro license always required (validates before processing)
Cost Comparison
For 10,000 images/year
| Model | User Cost | Cost Estimate |
|---|---|---|
| Server-Provided (included) | $0 | You pay $10-100/year |
| BYOK - Gemini | $10/year | User pays directly to Google |
| BYOK - OpenRouter (GPT-4o) | $100/year | User pays directly to OpenRouter |
Pricing Strategy
Option A: BYOK Only (Recommended)
PhotoSwipe Pro: $49/year
- Includes: Pro features + AI caption code
- User provides: Their own Gemini/OpenRouter API key
- AI costs: User pays directly (~$10-100/year depending on usage)
Your revenue: $49/year per customer
Your AI costs: $0
Customer total cost: $59-149/year ($49 + $10-100 AI)
Option B: Hybrid (Both models)
PhotoSwipe Pro Basic: $49/year
- User provides own API key
- Unlimited processing
- Cost: $49 + their AI costs
PhotoSwipe Pro Plus: $99/year
- Includes 10,000 captions/year (Gemini)
- Server-provided API key
- Cost: $99 (all-in)
Recommended: Option A (BYOK only) - Simpler, more scalable, $0 risk.
Security Considerations
API Key Storage
Client-side:
// Store in localStorage (user's browser only)
localStorage.setItem('user_ai_api_key', apiKey);
// NEVER expose in HTML or client-side code
// NEVER commit API keys to Git
Server-side:
// API keys are passed in request body, never stored
router.post('/caption', async (req, res) => {
const userApiKey = req.body.apiKey; // Used once, not stored
// Process request...
// API key discarded after request
});
Best Practices
✅ DO:
- Store API keys in user's browser (localStorage)
- Allow users to update/remove API keys
- Show clear messaging about API key usage
- Validate PhotoSwipe Pro license before accepting API keys
❌ DON'T:
- Store user API keys in your database
- Log API keys to server logs
- Expose API keys in URLs or GET parameters
- Use user API keys for other purposes
Migration Path
If You Currently Provide API Keys
Phase 1: Communicate Change
Email to existing customers:
Subject: New Feature - Bring Your Own API Key (Save Money!)
We're introducing BYOK (Bring Your Own Key) for AI captions:
✓ Pay only for what you use (~$0.001 per image)
✓ Gemini offers FREE tier for low-volume users
✓ No more monthly quotas or limits
Your PhotoSwipe Pro license now gives you access to the feature.
You provide your own Gemini/OpenRouter API key.
[Guide: How to Get Your Free Gemini API Key →]
Transition period: We'll continue providing server keys until [DATE]
Phase 2: Gradual Migration
# .env
# Keep server key for now (fallback)
GEMINI_API_KEY=your-server-key
# New customers: Prompt for BYOK
NEW_CUSTOMERS_BYOK=true
Phase 3: Full BYOK
# .env
# Remove server key
# GEMINI_API_KEY= # Removed
# All users provide own keys
FAQ
Q: Do users need a PhotoSwipe Pro license?
A: YES. License is still required to access the AI caption feature. They just provide their own AI API key instead of using yours.
Q: What if a user doesn't have an API key?
A: Show a helpful error with links to get one:
if (error.byok) {
alert('Please get your free Gemini API key at https://aistudio.google.com/app/apikey');
}
Q: Can users choose which AI provider to use?
A: YES. They can specify provider: 'gemini' or provider: 'openrouter' in requests.
Q: What if I want to offer both models (server + BYOK)?
A: Supported! If server has an API key, it's used as fallback. If user provides their own, their key takes priority.
Q: Is this secure?
A: YES. User API keys are:
- Never stored on your server
- Passed per-request only
- Used once and discarded
- Stored only in user's browser
Summary
✅ BYOK = Best Business Model
- You pay $0 for AI costs
- Users pay only for what they use
- Infinitely scalable
- Simple pricing (PhotoSwipe Pro license only)
✅ User-Friendly
- FREE Gemini tier available
- Pay-as-you-go pricing
- No quotas or limits
- Direct billing (no markup)
✅ Easy Implementation
- Just add
apiKeyparameter - Error handling included
- Helpful prompts for users
- Backward compatible
Get started: Update your pricing to "PhotoSwipe Pro + BYOK" and save thousands in AI costs! 🚀