How to implement pricing plans with rate limits?
how can i get started? i'm using next.js.
i want to model a pricing plan similar to discord's in a way. it's for a forum thing in which answers are created by an AI. and it works with github repos.
so you can ask a question in, say, the nextjs repo, and an LLM will explore the codebase and answer. that's the basic idea. posts are public by default but users can make private questions if they upgrade ($10/mo). so the PRO plan gives a user:
higher rate limits for "premium" models private posts interestingly, it gives users 2 "boosts" that they can use in any repository forum. boosts give the forum higher rate limits for public questions. if you're not PRO, you have an unlimited amount of questions you can ask, and you don't consume the public rate limits. if you're FREE, you can only ask if the forum has rate limits available. rate limits are per day (but ideally we can configure that).
well actually, thinking about it, i kind of like the idea of boosts but i'm not sure the rate limit should be per forum. maybe it makes sense for it to be per user... thoughts? and help on how to impl this.
Perfect! Now I have a better understanding. Let me create a comprehensive guide for you:
##Getting Started with Your AI Code Q&A Forum + Autumn
Since you're using Next.js and want to use Autumn for billing, here's the best approach:
###1. Architecture Overview
You'll have:
- Autumn (self-hosted or cloud) = Billing + Rate Limiting Management
- Your Next.js app = Forum UI + Question/Answer logic
- LLM agent = Explores repos and answers questions
###2. Set Up Autumn (Cloud is easier for Next.js)
Instead of self-hosting, I'd recommend starting with Autumn Cloud (https://app.useautumn.com):
- Sign up and create an organization
- Create a Stripe account and connect it
- Define your products/plans via the UI:
FREE Plan
├─ Tier: free
├─ Features:
│ └─ daily_questions: 5/day
PRO Plan ($10/mo)
├─ Tier: pro
├─ Features:
│ ├─ daily_questions: 50/day
│ ├─ private_posts: unlimited
│ └─ monthly_boosts: 2/month
###3. Next.js Integration
Create a new Next.js project or add to existing:
npm create next-app@latest forum-ai -- --typescript --tailwind
cd forum-ai
npm install axios # for API calls
Set up environment variables (.env.local):
NEXT_PUBLIC_AUTUMN_API_KEY=pk_your_publishable_key
NEXT_PUBLIC_AUTUMN_ORG_ID=org_xxx
AUTUMN_API_URL=https://api.useautumn.com # or your self-hosted instance
NEXT_PRIVATE_AUTUMN_SECRET_KEY=sk_xxx
###4. Key Code Snippets
Helper function to interact with Autumn (lib/autumn.ts):
import axios from 'axios';
const autumnClient = axios.create({
baseURL: process.env.AUTUMN_API_URL,
headers: {
'Authorization': `Bearer ${process.env.NEXT_PRIVATE_AUTUMN_SECRET_KEY}`,
},
});
export async function checkFeatureAccess(userId: string, featureId: string) {
const res = await autumnClient.post('/check', {
customerId: userId,
featureId,
});
return res.data;
}
export async function trackUsage(userId: string, featureId: string, value: number = 1) {
const res = await autumnClient.post('/track', {
customerId: userId,
featureId,
value,
});
return res.data;
}
export async function getPricingCheckout(userId: string, productId: string) {
const res = await autumnClient.post('/attach', {
customerId: userId,
productId,
redirectUrl: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard`,
});
return res.data;
}
API route for asking a question (app/api/questions/route.ts):
import { NextRequest, NextResponse } from 'next/server';
import { checkFeatureAccess, trackUsage } from '@/lib/autumn';
import { askLLMAgent } from '@/lib/llm';
export async function POST(req: NextRequest) {
const { userId, repoUrl, question, isPrivate, usesBoost } = await req.json();
// Check if user is PRO for private posts
if (isPrivate) {
const access = await checkFeatureAccess(userId, 'private_posts');
if (!access.data.allowed) {
return NextResponse.json(
{ error: 'Upgrade to PRO for private posts' },
{ status: 403 }
);
}
}
// Check if user has daily quota
if (!usesBoost) { // Boosts don't consume quota
const dailyQuota = await checkFeatureAccess(userId, 'daily_questions');
if (!dailyQuota.data.allowed) {
return NextResponse.json(
{ error: 'Daily question limit reached' },
{ status: 429 }
);
}
}
// Track usage (unless using boost)
if (!usesBoost) {
await trackUsage(userId, 'daily_questions', 1);
}
// Call your LLM agent
const answer = await askLLMAgent(repoUrl, question);
// Save to database
const newQuestion = await db.questions.create({
userId,
repoUrl,
question,
answer,
isPrivate,
usedBoost: usesBoost,
});
return NextResponse.json(newQuestion);
}
Client component for asking questions (components/AskQuestion.tsx):
'use client';
import { useState } from 'react';
import { getPricingCheckout } from '@/lib/autumn';
export function AskQuestion({ userId, userTier }: { userId: string; userTier: 'FREE' | 'PRO' }) {
const [isPrivate, setIsPrivate] = useState(false);
const [loading, setLoading] = useState(false);
const handleUpgrade = async () => {
const { url } = await getPricingCheckout(userId, 'pro');
window.location.href = url;
};
const handleAsk = async (question: string, repo: string) => {
if (isPrivate && userTier === 'FREE') {
alert('Upgrade to PRO for private posts');
await handleUpgrade();
return;
}
setLoading(true);
const res = await fetch('/api/questions', {
method: 'POST',
body: JSON.stringify({
userId,
repoUrl: repo,
question,
isPrivate,
usesBoost: false, // TODO: implement boost selection
}),
});
const data = await res.json();
setLoading(false);
// Handle response...
};
return (
<form onSubmit={(e) => { e.preventDefault(); /* call handleAsk */ }}>
<input type="text" placeholder="Ask a question..." />
<checkbox
checked={isPrivate}
onChange={(e) => setIsPrivate(e.target.checked)}
label="Private"
disabled={userTier === 'FREE'}
/>
<button type="submit" disabled={loading}>
Ask
</button>
</form>
);
}
###5. Database Schema (Prisma example)
model User {
id String @id
email String @unique
autumnCustomerId String @unique // Maps to Autumn's customerId
tier String @default("FREE") // FREE | PRO
boostsRemaining Int @default(2)
boostResetDate DateTime
createdAt DateTime @default(now())
questions Question[]
activeBoosts ActiveBoost[]
}
model Question {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id])
repoUrl String
question String
answer String
isPrivate Boolean @default(false)
usedBoost Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([repoUrl])
}
model ActiveBoost {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id])
repoUrl String
expiresAt DateTime
createdAt DateTime @default(now())
@@unique([userId, repoUrl])
@@index([expiresAt])
}
###6. Rate Limit Strategy (Recommended)
Per-user quota system:
// User pays for X questions/day with their plan
// Boosts don't consume quota - they're "free passes"
// This is simpler than per-forum rate limiting
const QUOTAS = {
FREE: 5, // 5 public questions/day
PRO: 50, // 50 any questions/day
};
// When user asks a question:
// 1. If private + FREE tier → reject
// 2. If public + quota available → allow, track
// 3. If public + boost active → allow, don't track
// 4. Boosts reset monthly (automatically by Autumn)
##Why This Structure?
✅ Autumn handles: Subscriptions, billing, feature entitlements, usage tracking
✅ Your app handles: Forum logic, LLM calls, user experience
✅ Per-user quotas: Simpler to implement and understand
✅ Boosts: Can be applied to any forum, don't consume daily quota
##Next Steps
- Sign up for Autumn Cloud (free tier available)
- Create your products with the pricing structure above
- Get API keys from Autumn dashboard
- Start building your Next.js app with the snippets above
- Integrate the LLM agent (Claude, GPT-4, etc.) for code exploration
Would you like me to dive deeper into any specific part? The boost system, LLM agent integration, or database schema?
cool! can you give me the autumn products and functions that i'll need to use? also i'm using fetch, not axios. just give me the stuff i need related to billing -- the rest of my app is already built (it's https://forums.basehub.com/basehub-ai/forums/1)
Perfect! Now I have everything. Let me create a comprehensive, concise guide specifically for your billing needs with fetch:
##Autumn Billing API - Essential Reference
Since your app is already built (https://forums.basehub.com/basehub-ai/forums/1), here's exactly what you need for billing:
###3 Core Endpoints
####1. /check - Verify user has access to a feature
const checkAccess = async (userId: string, featureId: string) => {
const res = await fetch(`${AUTUMN_API_URL}/check`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${AUTUMN_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
customer_id: userId,
feature_id: featureId,
required_balance: 1, // optional: check for specific amount
}),
});
return res.json();
};
// Usage:
const { data } = await checkAccess('user_123', 'private_posts');
if (!data.allowed) {
// User doesn't have access to private posts
}
Response:
{
"allowed": true,
"balance": 45, // Remaining balance for the feature
"limit": 50,
"reset_at": 1704067200000
}
####2. /track - Record usage consumption
const trackUsage = async (userId: string, featureId: string, value: number = 1) => {
const res = await fetch(`${AUTUMN_API_URL}/track`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${AUTUMN_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
customer_id: userId,
feature_id: featureId,
value: value, // How much to deduct (default 1)
}),
});
return res.json();
};
// Usage:
await trackUsage('user_123', 'daily_questions', 1);
Response:
{
"id": "evt_123",
"code": "event_received",
"customer_id": "user_123",
"feature_id": "daily_questions"
}
####3. /attach - Upgrade a user to a plan (or return checkout URL)
const attachProduct = async (userId: string, productId: string) => {
const res = await fetch(`${AUTUMN_API_URL}/attach`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${AUTUMN_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
customer_id: userId,
product_id: productId, // e.g., "pro_plan"
success_url: 'https://forums.basehub.com/success',
force_checkout: true, // Force Stripe Checkout if no payment method
}),
});
return res.json();
};
// Usage:
const result = await attachProduct('user_123', 'pro_plan');
if (result.url) {
window.location.href = result.url; // Redirect to Stripe Checkout
}
Response:
{
"success": true,
"url": "https://checkout.stripe.com/...", // If needs payment
"customer_id": "user_123",
"product_ids": ["pro_plan"]
}
###Products You Need to Create in Autumn Dashboard
Go to https://app.useautumn.com and create these products:
| Product | Price | Features |
|---|---|---|
| FREE (default) | $0 | daily_questions: 5/day |
| PRO | $10/month | daily_questions: 50/day, private_posts: unlimited, monthly_boosts: 2/month |
###Your Billing Flow
// app/api/questions/route.ts
export async function POST(req: NextRequest) {
const { userId, question, isPrivate, usesBoost } = await req.json();
// 1. Check if private post requires PRO
if (isPrivate) {
const access = await checkAccess(userId, 'private_posts');
if (!access.data.allowed) {
// Redirect to upgrade
const result = await attachProduct(userId, 'pro_plan');
return NextResponse.json({ upgrade_url: result.url }, { status: 403 });
}
}
// 2. Check daily question quota (unless using boost)
if (!usesBoost) {
const quota = await checkAccess(userId, 'daily_questions');
if (!quota.data.allowed) {
return NextResponse.json(
{ error: 'Daily limit reached' },
{ status: 429 }
);
}
}
// 3. Track the usage (unless boost)
if (!usesBoost) {
await trackUsage(userId, 'daily_questions', 1);
}
// 4. Process the question
const answer = await askLLM(question);
// 5. Save to your DB
await db.questions.create({
userId,
question,
answer,
isPrivate,
usedBoost,
});
return NextResponse.json({ answer });
}
###Setup: Environment Variables
Add to .env.local:
NEXT_PUBLIC_AUTUMN_API_URL=https://api.useautumn.com
# Get these from Autumn dashboard → Settings → API Keys
AUTUMN_SECRET_KEY=sk_org_xxx_sec_xxx
AUTUMN_ORG_ID=org_xxx
###Helper Functions (lib/autumn.ts)
const AUTUMN_API_URL = process.env.NEXT_PUBLIC_AUTUMN_API_URL;
const AUTUMN_SECRET_KEY = process.env.AUTUMN_SECRET_KEY;
export async function checkAccess(userId: string, featureId: string) {
const res = await fetch(`${AUTUMN_API_URL}/check`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${AUTUMN_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
customer_id: userId,
feature_id: featureId,
}),
});
if (!res.ok) throw new Error(`Check failed: ${res.statusText}`);
return res.json();
}
export async function trackUsage(userId: string, featureId: string, value = 1) {
const res = await fetch(`${AUTUMN_API_URL}/track`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${AUTUMN_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
customer_id: userId,
feature_id: featureId,
value,
}),
});
if (!res.ok) throw new Error(`Track failed: ${res.statusText}`);
return res.json();
}
export async function attachProduct(userId: string, productId: string) {
const res = await fetch(`${AUTUMN_API_URL}/attach`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${AUTUMN_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
customer_id: userId,
product_id: productId,
force_checkout: true,
}),
});
if (!res.ok) throw new Error(`Attach failed: ${res.statusText}`);
return res.json();
}
###That's it!
These 3 functions (check, track, attach) + the 2 products handle your entire billing model. No webhooks, no complex logic.
can't i create the products using the autumn sdk + cli? also use the sdk instead of the rest api?
Perfect! Now I have all the information. Let me compile the comprehensive answer:
##Yes! Use the SDK + CLI
###1. Install the SDK
npm install autumn-js
###2. Create Products with CLI (atmn)
Install the CLI:
npm install -g atmn
Initialize in your project:
npx atmn init
This creates autumn.config.ts where you define products as code:
// autumn.config.ts
import { defineConfig } from 'atmn';
export default defineConfig({
products: [
{
id: 'free',
name: 'Free Plan',
features: [
{
id: 'daily_questions',
type: 'metered',
limit: 5,
reset_period: 'daily',
},
],
},
{
id: 'pro',
name: 'Pro Plan',
price: 1000, // $10/month in cents
billing_period: 'monthly',
features: [
{
id: 'daily_questions',
type: 'metered',
limit: 50,
reset_period: 'daily',
},
{
id: 'private_posts',
type: 'boolean',
},
{
id: 'monthly_boosts',
type: 'metered',
limit: 2,
reset_period: 'monthly',
},
],
},
],
});
Deploy your config:
npx atmn push
Pull existing config:
npx atmn pull
###3. Use the SDK in Your Next.js App
Add to .env.local:
AUTUMN_SECRET_KEY=sk_org_xxx_sec_xxx
Server-side usage (lib/autumn.ts):
import { Autumn } from 'autumn-js';
export const autumn = new Autumn({
secretKey: process.env.AUTUMN_SECRET_KEY,
});
Check access (app/api/questions/route.ts):
import { autumn } from '@/lib/autumn';
import { NextRequest, NextResponse } from 'next/server';
export async function POST(req: NextRequest) {
const { userId, question, isPrivate } = await req.json();
// Check if user can create private posts
if (isPrivate) {
const access = await autumn.check({
customer_id: userId,
feature_id: 'private_posts',
});
if (!access.data.allowed) {
return NextResponse.json(
{ error: 'Upgrade to PRO' },
{ status: 403 }
);
}
}
// Check daily question quota
const quota = await autumn.check({
customer_id: userId,
feature_id: 'daily_questions',
});
if (!quota.data.allowed) {
return NextResponse.json(
{ error: 'Daily limit reached' },
{ status: 429 }
);
}
// Track the usage
await autumn.track({
customer_id: userId,
feature_id: 'daily_questions',
value: 1,
});
// ... rest of logic
return NextResponse.json({ success: true });
}
Attach product (upgrade):
const result = await autumn.attach({
customer_id: userId,
product_id: 'pro',
force_checkout: true,
});
if (result.url) {
window.location.href = result.url; // Redirect to Stripe Checkout
}
###4. SDK Functions You Need
All the core functions are in the SDK:
// Check feature access
const access = await autumn.check({
customer_id: 'user_123',
feature_id: 'private_posts',
required_balance: 1, // optional
});
// Track usage
await autumn.track({
customer_id: 'user_123',
feature_id: 'daily_questions',
value: 1,
});
// Upgrade to a product
const result = await autumn.attach({
customer_id: 'user_123',
product_id: 'pro',
force_checkout: true,
});
// Get customer data
const customer = await autumn.query({
customer_id: 'user_123',
});
// Checkout
const checkout = await autumn.checkout({
customer_id: 'user_123',
product_id: 'pro',
success_url: 'https://yourapp.com/success',
});
###Summary: CLI vs SDK vs REST
| Task | Tool |
|---|---|
| Define products/features | atmn CLI (pricing as code) |
| Check access | autumn-js SDK |
| Track usage | autumn-js SDK |
| Upgrade users | autumn-js SDK |
| Raw API | REST (only if SDK doesn't support it) |
The SDK wraps the REST API for you, so use it instead of fetch. The CLI lets you define pricing in code and deploy it.
END OF POST