Next.js Quickstart
This tutorial walks you through integrating Quota into a Next.js app so your users can pay for their own AI usage. By the end you will have a working chat completion with per-user billing.
@usequota/core package works with any JavaScript or TypeScript environment — Node.js, Deno, Bun, Cloudflare Workers, React Native, and more.1. Install the SDK
npm install @usequota/nextjsThis gives you React hooks, pre-built components, middleware, and server utilities. The package also installs @usequota/types for TypeScript definitions.
2. Get Your API Credentials
- Register at api.usequota.ai to create a developer account.
- Create an OAuth app to get a
client_idandclient_secret:
curl -X POST https://api.usequota.ai/developers/apps \
-H "Authorization: Bearer sess_..." \
-H "Content-Type: application/json" \
-d '{
"name": "My Next.js App",
"redirect_uris": ["http://localhost:3000/api/quota/callback"],
"billing_mode": "user"
}'Save the client_id and client_secret from the response. You will also want an API key for server-side calls:
curl -X POST https://api.usequota.ai/developers/keys \
-H "Authorization: Bearer sess_..." \
-H "Content-Type: application/json" \
-d '{"name": "server-key"}'Add these to your .env.local:
# .env.local
NEXT_PUBLIC_QUOTA_CLIENT_ID=your_client_id
QUOTA_CLIENT_SECRET=your_client_secret
QUOTA_API_KEY=sk-quota-xxxxxxxxxxxxx3. Set Up Route Handlers
The SDK provides a factory that generates all the API routes your app needs (OAuth authorize, callback, status, packages, checkout, disconnect) in one call.
First, create a shared config file:
// lib/quota.ts
import { createQuotaRouteHandlers } from "@usequota/nextjs/server";
export const {
authorize,
callback,
status,
packages,
checkout,
disconnect,
} = createQuotaRouteHandlers({
clientId: process.env.NEXT_PUBLIC_QUOTA_CLIENT_ID!,
clientSecret: process.env.QUOTA_CLIENT_SECRET!,
});Then create thin route files that re-export the handlers:
// app/api/quota/authorize/route.ts
export { authorize as GET } from "@/lib/quota";
// app/api/quota/callback/route.ts
export { callback as GET } from "@/lib/quota";
// app/api/quota/status/route.ts
export { status as GET } from "@/lib/quota";
// app/api/quota/packages/route.ts
export { packages as GET } from "@/lib/quota";
// app/api/quota/checkout/route.ts
export { checkout as POST } from "@/lib/quota";
// app/api/quota/disconnect/route.ts
export { disconnect as POST } from "@/lib/quota";That is six route files, each a single line. The SDK handles OAuth token exchange, cookie management, token refresh, and CSRF protection.
4. Wrap Your App with QuotaProvider
Add the provider to your root layout. This makes authentication state available to all client components via React context.
// app/layout.tsx
import { QuotaProvider } from "@usequota/nextjs";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<QuotaProvider
clientId={process.env.NEXT_PUBLIC_QUOTA_CLIENT_ID!}
>
{children}
</QuotaProvider>
</body>
</html>
);
}5. Add Login and Balance Display
Use the pre-built components for a quick integration, or use hooks for full control.
Option A: Pre-built components
"use client";
import {
QuotaConnectButton,
QuotaBalance,
QuotaBuyCredits,
} from "@usequota/nextjs";
export default function Header() {
return (
<nav>
<QuotaConnectButton>Sign in with Quota</QuotaConnectButton>
<QuotaBalance format="dollars" showRefresh />
<QuotaBuyCredits packageId="starter">
Add Credits
</QuotaBuyCredits>
</nav>
);
}QuotaConnectButton automatically hides itself once the user is logged in. QuotaBalance shows --- when not authenticated and the dollar balance when logged in.
Option B: Hooks
"use client";
import { useQuotaAuth, useQuotaBalance } from "@usequota/nextjs";
export default function Header() {
const { isAuthenticated, isLoading, login, logout } = useQuotaAuth();
const { balance } = useQuotaBalance();
if (isLoading) return <div>Loading...</div>;
if (!isAuthenticated) {
return <button onClick={login}>Connect Wallet</button>;
}
return (
<div>
<span>Balance: ${(balance! / 1_000_000).toFixed(2)}</span>
<button onClick={logout}>Disconnect</button>
</div>
);
}6. Make an AI Call
The Quota API is OpenAI-compatible. You can call it from a Next.js server action or API route using your API key, or from the client using the user's OAuth token.
Server-side (API route with API key)
This approach uses your developer API key. Good for server-side logic where you control the billing.
// app/api/chat/route.ts
import { NextResponse } from "next/server";
export async function POST(request: Request) {
const { message } = await request.json();
const response = await fetch(
"https://api.usequota.ai/v1/chat/completions",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.QUOTA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "gpt-4o-mini",
messages: [{ role: "user", content: message }],
}),
}
);
const data = await response.json();
// Billing metadata is included in the response
// data.quota.credits_used, data.quota.balance_after
return NextResponse.json(data);
}Server-side (OAuth token for per-user billing)
Use withQuotaAuth to automatically resolve the user's OAuth token and bill their account:
// app/api/chat/route.ts
import { withQuotaAuth } from "@usequota/nextjs/server";
export const POST = withQuotaAuth(
{
clientId: process.env.NEXT_PUBLIC_QUOTA_CLIENT_ID!,
clientSecret: process.env.QUOTA_CLIENT_SECRET!,
},
async (request, { user, accessToken }) => {
const { message } = await request.json();
const response = await fetch(
"https://api.usequota.ai/v1/chat/completions",
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "gpt-4o-mini",
messages: [{ role: "user", content: message }],
}),
}
);
const data = await response.json();
return Response.json(data);
}
);Client-side component
"use client";
import { useState } from "react";
import { useQuotaBalance } from "@usequota/nextjs";
export default function Chat() {
const [input, setInput] = useState("");
const [reply, setReply] = useState("");
const { refetch } = useQuotaBalance();
async function send() {
const res = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: input }),
});
const data = await res.json();
setReply(data.choices[0].message.content);
// Refresh the balance display after the call
await refetch();
}
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Ask something..."
/>
<button onClick={send}>Send</button>
{reply && <p>{reply}</p>}
</div>
);
}7. Multi-Provider Models
Quota routes to different AI providers based on the model name. Use provider prefixes to access Anthropic or Google models through the same API:
// OpenAI (default, no prefix needed)
{ model: "gpt-4o-mini" }
{ model: "gpt-4o" }
// Anthropic
{ model: "anthropic/claude-sonnet-4-20250514" }
{ model: "anthropic/claude-haiku-3.5" }
// Google
{ model: "google/gemini-2.0-flash" }
{ model: "google/gemini-2.5-pro-preview-06-05" }The request and response format stays OpenAI-compatible regardless of provider. Quota handles the translation.
8. Billing Response Metadata
Every chat completion response includes a quota object with billing details:
{
"id": "chatcmpl-...",
"choices": [...],
"usage": {
"prompt_tokens": 12,
"completion_tokens": 45,
"total_tokens": 57
},
"quota": {
"credits_used": 3,
"balance_before": 1000,
"balance_after": 997,
"ledger_id": "led_...",
"billing_mode": "user"
}
}The same data is also available as response headers: X-Quota-Credits-Used and X-Quota-Balance.
Full File Structure
Here is what your project looks like after setup:
your-app/
.env.local # API credentials
lib/quota.ts # Shared route handler config
app/
layout.tsx # QuotaProvider wrapper
page.tsx # Your app
api/
quota/
authorize/route.ts # GET - starts OAuth flow
callback/route.ts # GET - handles OAuth callback
status/route.ts # GET - returns user + balance
packages/route.ts # GET - available credit packages
checkout/route.ts # POST - Stripe checkout
disconnect/route.ts # POST - logout
chat/route.ts # Your AI endpointNext Steps
- Next.js SDK reference — full API for hooks, components, server utilities, and webhooks
- Chat Completions API — streaming, function calling, and all supported parameters
- Billing Modes — developer billing vs. user billing explained
- Webhooks — get notified on balance changes, low balance, and usage events
- Credits & Funding — add credits programmatically for your users
- Authentication — API keys, OAuth flows, and session tokens