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.
This is a really interesting concept! Let me think through the architecture with you, and then I'll help you get started with the implementation.
Let me start by exploring the repo structure:
Okay, so this is the Autumn repo - an existing project. Let me check the README and structure to understand what we're working with:
Excellent! Now I understand. Autumn is a pricing/billing infrastructure framework, and you want to build your AI-powered code Q&A forum using Autumn as the foundation for the billing layer.
Let me think through your architecture questions first, then help you get started:
##Rate Limit Strategy: Per-User vs Per-Forum
I'd recommend per-user for a few reasons:
- Cleaner mental model: Users understand their personal limits
- Simpler enforcement: You check the user's total usage across all forums
- Boosts make more sense: A user gets 2 boosts/month to apply wherever they want
- Scale better: Forum-level limits create coordination problems (what if 10 users hit the same forum limit simultaneously?)
However, you could implement a hybrid approach:
- User limits (primary enforcement): Each user has daily quota based on their tier
- Forum boost multiplier (optional): When a boost is active on a forum, that forum's public questions don't count against user limits OR get multiplied quota
- Global public rate limit (soft): Monitor if a forum is getting abused, but this is observational, not hard blocking
##Proposed Data Model
You'd need these entities in Autumn:
// Core entities
- User (with tier: FREE | PRO)
- Question (public/private flag, created by User)
- Forum (GitHub repo)
- DailyQuotaUsage (user + date + count)
- Boost (user + forum + expiry)
Core flows:
- PRO signup: Attach product → grant "private_posts" + "2_monthly_boosts" features
- Ask question:
- Check quota:
check({ featureId: "daily_questions", userId, date }) - If private: require PRO
- If public + no boost: track usage
track({ featureId: "daily_questions", value: 1 }) - If public + boost active: don't charge quota
- Check quota:
- Apply boost: Update user's active boosts on forum
##Getting Started with Next.js + Autumn
Let me check if there's existing Next.js integration:
Now let me check the SDK/client setup:
Let me check the package.json to see what client libraries are available:
Now let me look at how Autumn exposes its API and check the client implementation:
Let me check the actual API routes:
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 let me look at the actual API request/response types:
Perfect! Now let me check the actual request/response formats:
Perfect! Now let me check what the response looks like and look for the docs:
Perfect! Now I have everything I need. Let me compile the exact API endpoints and request/response formats you'll need:
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?
Yes! Great question. Let me check the Autumn SDK and CLI:
Perfect! So there's an autumn-js SDK. Let me check the CLI and SDK docs:
Great! Now let me check the actual SDK implementation and the CLI docs:
Perfect! Now let me find the actual usage examples:
Great! Let me check the docs for the actual SDK usage:
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
Claude Haiku 4.5