Skip to main content

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

ComponentRequired?Who Provides?
PhotoSwipe Pro License✅ YESUser purchases from you
AI API Key (Gemini/OpenRouter)✅ YESUser brings their own

Business model: You sell PhotoSwipe Pro license. Users bring their own AI API keys.


Getting API Keys

  1. Go to https://aistudio.google.com/app/apikey
  2. Click "Create API Key"
  3. Copy your API key (starts with AIza...)
  4. Cost: FREE up to 15 requests/minute, then ~$0.001 per image

Option 2: OpenRouter (Pay-per-use)

  1. Go to https://openrouter.ai/
  2. Sign up and add credits
  3. Go to Keys → Create Key
  4. Copy your API key (starts with sk-or-v1-...)
  5. Cost: ~$0.01 per image (GPT-4o Vision)

Usage

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

ModelUser CostCost Estimate
Server-Provided (included)$0You pay $10-100/year
BYOK - Gemini$10/yearUser pays directly to Google
BYOK - OpenRouter (GPT-4o)$100/yearUser 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 apiKey parameter
  • Error handling included
  • Helpful prompts for users
  • Backward compatible

Get started: Update your pricing to "PhotoSwipe Pro + BYOK" and save thousands in AI costs! 🚀