Build an AI API with Holdify
Complete end-to-end tutorial: from Polar integration to feature-gated AI endpoints.
This tutorial walks you through building a monetized AI API like the one used by CodePilot. By the end, you'll have:
- Custom plans with different quotas and features
- Automatic subscription sync with Polar
- API key verification with rate limiting and quota
- Feature gating (e.g., only Pro users can use GPT-4o)
- Per-customer usage tracking
Create a Holdify project
Sign up at app.holdify.io and create a new project. You'll get a project API key (hld_proj_live_xxx)
that you'll use to authenticate your backend with Holdify.
Important: Keep your project API key secret. It should only be used in your backend, never exposed to clients.
Connect Polar
Go to Settings → Integrations → Polar in the Holdify dashboard. Click "Connect Polar" and authorize the integration. This enables automatic webhook sync.
When a customer subscribes via Polar, Holdify automatically:
- Creates a tenant for the customer
- Creates an entitlement with the correct plan
- Sets quota and rate limits based on the plan
- Updates entitlements when customers upgrade/downgrade/cancel
Create custom plans
Go to Settings → Plans and create your pricing tiers. Each plan defines:
- Requests per month: The monthly quota limit
- Rate limit: Maximum requests per minute
- Features array: String identifiers for feature gating
// Example plan configuration in Holdify Dashboard
// Settings → Plans → Create Plan
Free Plan:
- Name: "free"
- Requests per month: 100
- Rate limit: 10/minute
- Features: []
Pro Plan:
- Name: "pro"
- Requests per month: 5000
- Rate limit: 60/minute
- Features: ["model:gpt-4o", "model:claude-sonnet"]
Business Plan:
- Name: "business"
- Requests per month: 25000
- Rate limit: 300/minute
- Features: ["model:gpt-4o", "model:claude-sonnet", "priority-queue"]Map Polar products to plans
In the Polar integration settings, map each Polar product to a Holdify plan:
// In Holdify Dashboard: Settings → Integrations → Polar
// Map your Polar products to Holdify plans
Polar Product ID → Holdify Plan
────────────────────────────────────────────
prod_free_tier → free
prod_pro_monthly → pro
prod_pro_yearly → pro
prod_business_monthly → business
prod_business_yearly → businessAdd verification to your backend
Install the SDK and add API key verification to your routes. The middleware should:
- Extract the API key from the x-api-key header
- Call Holdify /v1/verify to validate and get plan info
- Check rate limits and quota, return 429/402 if exceeded
- Gate features based on the features array
import { Hono } from 'hono';
import { Holdify } from '@holdify/sdk';
const app = new Hono();
const holdify = new Holdify({
apiKey: process.env.HOLDIFY_PROJECT_KEY!,
});
"text-gray-500">// Middleware to verify API keys
app.use('/api/*', async (c, next) => {
const apiKey = c.req.header('x-api-key');
if (!apiKey) {
return c.json({ error: 'API key required' }, 401);
}
const result = await holdify.verify(apiKey, {
resource: 'chat_requests',
units: 1,
tenantId: apiKey.slice(-8), "text-gray-500">// Use last 8 chars as tenant ID
});
if (!result.valid) {
return c.json({ error: 'Invalid API key' }, 401);
}
if (result.rateLimit.remaining <= 0) {
return c.json(
{ error: 'Rate limit exceeded', retryAfter: result.rateLimit.reset },
429
);
}
if (result.quota.remaining <= 0) {
return c.json(
{ error: 'Monthly quota exceeded', resetAt: result.quota.resetAt },
402
);
}
"text-gray-500">// Store result for route handlers
c.set('holdify', result);
await next();
});
"text-gray-500">// Chat endpoint with feature gating
app.post('/api/chat', async (c) => {
const { model, messages } = await c.req.json();
const holdifyResult = c.get('holdify');
"text-gray-500">// Feature gating: check if user has access to the requested model
const modelFeature = `model:${model}`;
if (!holdifyResult.features.includes(modelFeature)) {
return c.json({
error: `Upgrade to access ${model}`,
currentPlan: holdifyResult.plan,
requiredFeature: modelFeature,
}, 403);
}
"text-gray-500">// Route to AI provider based on model
const response = await callAIProvider(model, messages);
return c.json({
response,
usage: {
quotaRemaining: holdifyResult.quota.remaining,
rateLimitRemaining: holdifyResult.rateLimit.remaining,
},
});
});
export default app;Understand the user flow
Here's how everything connects from user signup to API usage:
┌─────────────────────────────────────────────────────────────────┐
│ User Flow │
└─────────────────────────────────────────────────────────────────┘
1. User visits your pricing page
└── yourapp.com/pricing
2. User clicks "Subscribe to Pro"
└── Redirects to Polar checkout
3. User completes payment
└── Polar processes payment
4. Polar sends webhook to Holdify
└── POST https://api.holdify.io/webhooks/polar
└── Holdify creates/updates entitlement automatically
5. User gets API key
└── Option A: From your dashboard (you fetch from Holdify API)
└── Option B: Auto-generated and emailed
└── Option C: User creates via your UI → Holdify API
6. User makes API requests
└── Your backend calls Holdify /v1/verify
└── Holdify returns plan, features, quota
└── You route to AI provider accordingly
7. Quota is tracked automatically
└── Each verify call with units > 0 decrements quota
└── When quota hits 0, verify returns quota.remaining = 0How users get their API keys
After subscribing via Polar, users need an API key. You have several options:
Option A: Dashboard UI (Recommended)
Build a "API Keys" page in your customer dashboard. Use the Holdify API to list and create keys for the logged-in customer.
Option B: Auto-generate on subscription
Listen for Polar webhooks in your own backend, then automatically create a Holdify API key and email it to the customer.
Option C: CLI authentication
For CLI tools, implement OAuth or device flow. After auth, your backend creates/returns a Holdify API key.
// Fetch customer's API key from your dashboard
// This is what you show in YOUR customer dashboard
// Backend endpoint to get customer's keys
app.get('/api/my-keys', async (req, res) => {
const customerId = req.session.userId; // Your auth
// Call Holdify API to get keys for this customer
const response = await fetch(
`https://api.holdify.io/v1/api-keys?tenantId=${customerId}`,
{
headers: {
'Authorization': `Bearer ${process.env.HOLDIFY_PROJECT_KEY}`,
},
}
);
const { keys } = await response.json();
res.json({ keys });
});
// Create a new key for customer
app.post('/api/my-keys', async (req, res) => {
const customerId = req.session.userId;
const response = await fetch('https://api.holdify.io/v1/api-keys', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.HOLDIFY_PROJECT_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
tenantId: customerId,
name: req.body.keyName || 'Default Key',
}),
});
const key = await response.json();
res.json({ key });
});Test your integration
Test the full flow with curl:
# Test your API with a customer key
curl -X POST https://your-api.com/api/chat \
-H "x-api-key: hld_live_customer_key_here" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Hello!"}]
}'
# Response for Pro user:
{
"response": "Hello! How can I help you today?",
"usage": {
"quotaRemaining": 4999
}
}
# Response for Free user trying to use GPT-4o:
{
"error": "Upgrade to access gpt-4o",
"currentPlan": "free"
}You're all set!
Your AI API now has:
- Automatic subscription sync with Polar
- Plan-based quotas that reset monthly
- Per-minute rate limiting
- Feature gating for premium models
- Per-customer usage tracking