Package Exports
- @usequota/nextjs
- @usequota/nextjs/server
- @usequota/nextjs/styles.css
Readme
@usequota/nextjs
Next.js SDK for Quota - AI credit wallet and multi-provider inference API.
Installation
npm install @usequota/nextjsQuick Start
1. Set up environment variables
# .env.local
QUOTA_CLIENT_ID=your_client_id
QUOTA_CLIENT_SECRET=your_client_secret
NEXT_PUBLIC_QUOTA_CLIENT_ID=your_client_id2. Configure middleware
Create middleware.ts in your project root:
import { createQuotaMiddleware } from "@usequota/nextjs";
export const middleware = createQuotaMiddleware({
clientId: process.env.QUOTA_CLIENT_ID!,
clientSecret: process.env.QUOTA_CLIENT_SECRET!,
});
export const config = {
matcher: "/api/quota/callback",
};3. Add QuotaProvider
Wrap your app with the provider in app/layout.tsx:
import { QuotaProvider } from "@usequota/nextjs";
export default function RootLayout({ children }) {
return (
<html>
<body>
<QuotaProvider clientId={process.env.NEXT_PUBLIC_QUOTA_CLIENT_ID!}>
{children}
</QuotaProvider>
</body>
</html>
);
}4. Create API routes
Create the following API routes:
app/api/quota/me/route.ts - Fetch user data:
import { getQuotaUser } from "@usequota/nextjs/server";
export async function GET() {
const user = await getQuotaUser({
clientId: process.env.QUOTA_CLIENT_ID!,
clientSecret: process.env.QUOTA_CLIENT_SECRET!,
});
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
return Response.json(user);
}app/api/quota/logout/route.ts - Handle logout:
import { clearQuotaAuth } from "@usequota/nextjs/server";
export async function POST() {
await clearQuotaAuth();
return Response.json({ success: true });
}5. Use in components
"use client";
import { useQuota } from "@usequota/nextjs";
export function MyComponent() {
const { user, isLoading, login } = useQuota();
if (isLoading) return <div>Loading...</div>;
if (!user) return <button onClick={login}>Sign in with Quota</button>;
return (
<div>
<p>Welcome, {user.email}!</p>
<p>Balance: ${(user.balance / 1_000_000).toFixed(2)}</p>
</div>
);
}Features
- OAuth 2.0 Authentication - Secure user authentication flow
- Token Management - Automatic token refresh and cookie management
- React Hooks - Convenient hooks for auth state and user data
- Server-Side Utilities - Functions for API routes and server components
- TypeScript Support - Full type safety with TypeScript
- Hosted Mode - Optional server-side token storage for enhanced security
API Reference
Client-Side
Hooks
useQuota()- Access full Quota contextuseQuotaUser()- Get current useruseQuotaAuth()- Auth state and actionsuseQuotaBalance()- User credit balance
Components
<QuotaProvider>- React context provider
Server-Side
Import from @usequota/nextjs/server:
getQuotaUser(config)- Get authenticated userrequireQuotaAuth(config)- Require auth (redirects if not logged in)getQuotaPackages(config?)- Fetch available credit packagescreateQuotaCheckout(config)- Create Stripe checkout sessionclearQuotaAuth(config?)- Clear auth cookiescreateQuotaRouteHandlers(config)- Generate all 6 API route handlers in one callwithQuotaAuth(config, handler)- Wrap a route handler with automatic Quota auth
Typed Errors
QuotaError- Base error class (code,statusCode,hint)QuotaInsufficientCreditsError- 402, includesbalanceandrequiredQuotaNotConnectedError- 401, user has no Quota account connectedQuotaTokenExpiredError- 401, token expired and refresh failedQuotaRateLimitError- 429, includesretryAfter(seconds)
Middleware
createQuotaMiddleware(config)- Create OAuth callback middleware
Storage Modes
Client Mode (Default)
Tokens are stored in httpOnly cookies:
export const middleware = createQuotaMiddleware({
clientId: process.env.QUOTA_CLIENT_ID!,
clientSecret: process.env.QUOTA_CLIENT_SECRET!,
storageMode: "client", // default
});Hosted Mode
Tokens stored server-side by Quota (more secure):
export const middleware = createQuotaMiddleware({
clientId: process.env.QUOTA_CLIENT_ID!,
clientSecret: process.env.QUOTA_CLIENT_SECRET!,
storageMode: "hosted",
getExternalUserId: async (request) => {
// Return your user's ID from your auth system
const session = await getSession(request);
return session.userId;
},
});Advanced Usage
Custom OAuth Flow
const { login } = useQuota();
// Trigger OAuth flow
login();Server Components
import { getQuotaUser } from "@usequota/nextjs/server";
export default async function DashboardPage() {
const user = await getQuotaUser({
clientId: process.env.QUOTA_CLIENT_ID!,
clientSecret: process.env.QUOTA_CLIENT_SECRET!,
});
if (!user) {
redirect("/");
}
return <div>Welcome, {user.email}!</div>;
}Protected Routes
import { requireQuotaAuth } from "@usequota/nextjs/server";
export default async function ProtectedPage() {
// Automatically redirects to login if not authenticated
const user = await requireQuotaAuth({
clientId: process.env.QUOTA_CLIENT_ID!,
clientSecret: process.env.QUOTA_CLIENT_SECRET!,
});
return <div>Protected content for {user.email}</div>;
}Credit Packages
"use client";
import { useState, useEffect } from "react";
import type { CreditPackage } from "@usequota/nextjs";
export function BuyCredits() {
const [packages, setPackages] = useState<CreditPackage[]>([]);
useEffect(() => {
fetch("/api/packages")
.then((res) => res.json())
.then(setPackages);
}, []);
const handleBuy = async (packageId: string) => {
const res = await fetch("/api/checkout", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ packageId }),
});
const { url } = await res.json();
window.location.href = url;
};
return (
<div>
{packages.map((pkg) => (
<button key={pkg.id} onClick={() => handleBuy(pkg.id)}>
Buy {pkg.credits} credits for {pkg.price_display}
</button>
))}
</div>
);
}Webhook Verification
Quota can send webhooks to notify your application about events. The SDK provides utilities to verify webhook signatures and handle events securely.
Set up webhook endpoint
Add your webhook secret to .env.local:
QUOTA_WEBHOOK_SECRET=your_webhook_secretCreate webhook handler
app/api/quota/webhook/route.ts:
import { createWebhookHandler } from "@usequota/nextjs";
export const POST = createWebhookHandler(process.env.QUOTA_WEBHOOK_SECRET!, {
"balance.low": async (event) => {
// Send email notification when user balance is low
await sendLowBalanceEmail(event.data.user_id, event.data.current_balance);
},
"user.connected": async (event) => {
// Track new user connection
await analytics.track("user_connected", event.data);
},
"usage.completed": async (event) => {
// Log usage for analytics
await logUsage(event.data);
},
});Webhook event types
Quota sends the following webhook events:
user.connected- User completed OAuth connectionuser.disconnected- User disconnected their accountbalance.updated- User's credit balance changedbalance.low- User's balance fell below thresholdusage.completed- API request completed and billed
Manual signature verification
For custom implementations:
import { verifyWebhookSignature, parseWebhook } from "@usequota/nextjs";
export async function POST(req: Request) {
try {
// Option 1: Parse and verify in one step
const event = await parseWebhook(req, process.env.QUOTA_WEBHOOK_SECRET!);
// Option 2: Manual verification
const payload = await req.text();
const signature = req.headers.get("x-quota-signature")!;
const isValid = verifyWebhookSignature({
payload,
signature,
secret: process.env.QUOTA_WEBHOOK_SECRET!,
});
if (!isValid) {
return Response.json({ error: "Invalid signature" }, { status: 400 });
}
const event = JSON.parse(payload);
// Handle event...
return Response.json({ received: true });
} catch (error) {
return Response.json({ error: error.message }, { status: 400 });
}
}Route Handler Factory
createQuotaRouteHandlers generates all API route handlers for Quota integration in one call, replacing 6 separate route files with boilerplate.
// lib/quota.ts
import { createQuotaRouteHandlers } from "@usequota/nextjs/server";
export const {
authorize, // GET - initiates OAuth flow
callback, // GET - handles OAuth callback
status, // GET - returns connection status + balance
packages, // GET - returns credit packages
checkout, // POST - creates Stripe checkout session
disconnect, // POST - disconnects user's Quota account
} = createQuotaRouteHandlers({
clientId: process.env.QUOTA_CLIENT_ID!,
clientSecret: process.env.QUOTA_CLIENT_SECRET!,
});Then create thin route files:
// 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";withQuotaAuth
Wraps a route handler with automatic Quota authentication, token refresh, and error handling:
// app/api/summarize/route.ts
import { withQuotaAuth } from "@usequota/nextjs/server";
export const POST = withQuotaAuth(
{
clientId: process.env.QUOTA_CLIENT_ID!,
clientSecret: process.env.QUOTA_CLIENT_SECRET!,
},
async (request, { user, accessToken }) => {
// user is guaranteed to exist
// accessToken can proxy requests through Quota
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: "Hello" }],
}),
},
);
return Response.json(await response.json());
},
);Typed Errors
Handle specific failure modes with instanceof checks:
import {
QuotaInsufficientCreditsError,
QuotaNotConnectedError,
QuotaRateLimitError,
QuotaError,
} from "@usequota/nextjs/server";
try {
await callQuotaAPI();
} catch (e) {
if (e instanceof QuotaInsufficientCreditsError) {
console.log(e.balance, e.required); // current balance, credits needed
}
if (e instanceof QuotaNotConnectedError) {
// redirect to login
}
if (e instanceof QuotaRateLimitError) {
await delay(e.retryAfter * 1000); // seconds until retry
}
if (e instanceof QuotaError) {
console.log(e.code, e.statusCode, e.hint);
}
}License
MIT