API Reference
HTTP surface for chat streaming, uploads, billing webhooks, and admin utilities. Most UI still uses Server Actions; these routes exist for streaming, files, and providers that need raw HTTP.
Authentication
All dashboard API routes require authentication via Supabase session cookies.
Chat API
GET /api/chat
Health check endpoint.
Response:
{ "message": "Chat API is working" }
POST /api/chat
Send a message and receive AI response.
Request Body:
{
messages: Array<{
role: "user" | "assistant" | "system";
content: string;
}>;
chatId?: string; // Existing chat UUID (optional)
modelId?: // AI model (default: "gpt-4o-mini")
| "gpt-4o-mini"
| "gpt-4o"
| "gpt-4-turbo"
| "claude-3-5-sonnet-20241022"
| "claude-3-opus-20240229";
documentIds?: string[]; // Document UUIDs for RAG context (optional)
}
Response:
- 200: Streaming text response
- 401: Unauthorized
- 402: Insufficient credits
- 403: Model not allowed for tier
- 422: Validation error (invalid body)
- 500: Server error
Headers:
x-chat-id: Chat ID (returned for new chats)
Example:
const response = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
messages: [{ role: "user", content: "Hello!" }],
modelId: "gpt-4o-mini",
}),
});
const reader = response.body.getReader();
// Read streaming response...
Upload API
POST /api/upload
Upload a document for RAG processing.
Request Body: FormData
file: File (allowed types and max size are enforced bysrc/config/rag.ts)
Response:
{ "documentId": "uuid", "filename": "my.pdf", "status": "processing" }
Status Codes:
- 200: Success
- 400: Invalid file type or size
- 401: Unauthorized
- 500: Upload failed
Example:
const formData = new FormData();
formData.append("file", file);
const response = await fetch("/api/upload", {
method: "POST",
body: formData,
});
const { documentId } = await response.json();
RAG API
POST /api/rag
Trigger document vectorization.
Request Body:
{
documentId: string;
}
Response:
{ "success": true, "message": "Document processing started" }
Status Codes:
- 200: Vectorization started (async)
- 400: Invalid document ID
- 401: Unauthorized
- 403: Forbidden (document does not belong to user)
- 404: Document not found
- 500: Vectorization failed
Billing API
POST /api/billing/portal
Create a Stripe Billing Portal session and return a redirect URL.
Response:
{ "url": "https://billing.stripe.com/session/..." }
GET /api/billing/invoices
Fetch user's Stripe invoices.
Query Parameters:
customerId: Stripe customer ID
Response:
{
invoices: Array<{
id: string;
amount: number;
currency: string;
status: string | null;
date: string;
invoiceUrl: string | null;
description: string | null;
}>;
}
GET /api/billing/credit-history
Fetch the user's credit transaction history.
Query Parameters:
limit(optional, default50)offset(optional, default0)type(optional): filter bytransaction_type(e.g.usage,reset,topup)
Response:
{
transactions: any[];
total: number;
limit: number;
offset: number;
}
Webhook API
POST /api/webhooks/stripe
Handle Stripe webhook events.
Headers:
stripe-signature: Stripe signature header
Handled Events:
checkout.session.completed: Upgrade/top-up successinvoice.paid: Monthly credit renewal (subscription cycles)customer.subscription.deleted: Downgrade to free
Response:
- 200:
{ received: true } - 400: Invalid signature
Server Actions
generateCheckoutLink
Create Stripe checkout session.
Location: src/app/actions/stripe.ts
import { generateCheckoutLink } from "@/app/actions/stripe";
const url = await generateCheckoutLink(
priceId, // Stripe price ID
"subscription" // "subscription" | "one-time"
);
// Redirect to url
signOut
Sign out user.
Location: src/app/(auth)/actions/auth.ts
import { signOut } from "@/app/(auth)/actions/auth";
// Use in form action
<form action={signOut}>
<button type="submit">Logout</button>
</form>
Error Codes
| Code | Description |
|---|---|
| 400 | Bad Request - Invalid input |
| 401 | Unauthorized - Not logged in |
| 402 | Payment Required - Insufficient credits |
| 403 | Forbidden - Feature not available for tier |
| 404 | Not Found - Resource doesn't exist |
| 500 | Server Error - Internal error |
Cron API
GET /api/cron/reset-credits
Admin route used by the scheduler defined in vercel.json to reset monthly credits.
Auth:
Authorization: Bearer <CRON_SECRET>
Rate Limiting
Rate limiting is not enforced by default. A helper exists in src/lib/rate-limit/, but it is not wired into the API routes yet. Consider adding:
- Per-user limits via Supabase RLS
- API route middleware with Upstash Ratelimit
- Cloudflare Workers for edge rate limiting
Cron API
GET /api/cron/reset-credits
Monthly credit reset job. Intended to be triggered by a scheduler (e.g. Vercel Cron).
Auth:
Authorization: Bearer <CRON_SECRET>(recommended)
Response:
{ "message": "Credit reset completed", "resetCount": 3 }
CORS
API routes are same-origin only. For external access:
- Add CORS headers to route handlers
- Configure
next.config.tsheaders - Use API keys instead of cookies