API Routes Reference

Complete reference of all 34 REST API endpoints with authentication, rate limits, request schemas, and response formats

Kit provides 34 REST API endpoints organized into 11 categories. Every endpoint uses Next.js App Router route handlers — there are no server actions. All requests and responses use JSON unless otherwise noted (file uploads use raw streams).
This page is a lookup reference. For architectural context, see Security Overview. For endpoint-specific guides, follow the links in each section.

Quick Reference

All endpoints at a glance. Click any path to jump to its detailed section.
PathMethod(s)AuthRate LimitDescription
/api/user/statusGETClerkapiCurrent user subscription status
/api/checkout/create-urlPOSTClerkpaymentsCreate Lemon Squeezy checkout URL
/api/payments/create-checkoutPOST, GETClerkpaymentsCreate checkout session with options
/api/payments/cancel-subscriptionPOSTClerkpaymentsCancel active subscription
/api/payments/change-planPOSTClerkpaymentsUpgrade or downgrade plan
/api/payments/create-portal-sessionPOSTClerkGenerate customer portal URL
/api/payments/customer-portalPOSTClerkpaymentsCustomer portal redirect
/api/payments/subscriptionGETClerkCurrent subscription details
/api/credits/bonus-packagesGETClerkapiAvailable bonus credit packages
/api/credits/checkoutPOSTClerkpaymentsCreate bonus credit checkout URL
/api/credits/historyGETClerkapiPaginated credit transaction history
/api/credits/preferencesGETClerkapiGet bonus credit preference
/api/credits/preferencesPATCHClerkapiUpdate bonus credit preference
/api/config/publicGETPublic pricing configuration
/api/pricing/configGETFull pricing config with validation
/api/ai/chatPOSTClerkcreditsSynchronous AI completion
/api/ai/streamPOSTClerkcreditsStreaming AI completion (SSE)
/api/ai/usageGETClerkAI usage stats and limits
/api/ai/speech-to-textPOSTClerkcreditsAudio transcription (Whisper STT)
/api/ai/image-genPOSTClerkcreditsAI image generation (GPT Image)
/api/ai/generate-contentPOSTClerkcreditsTemplate-based content generation (SSE)
/api/ai/rag/askPOSTClerkcreditsRAG-powered Q&A
/api/ai/rag/conversationsGET, DELETEClerkList or delete conversations
/api/ai/rag/conversations/[id]GETClerkSingle conversation with messages
/api/uploadPOSTClerkuploadUpload file to Vercel Blob
/api/files/[id]GET, DELETEClerkGet or delete uploaded file
/api/email/sendPOSTClerkemailSend transactional email
/api/email/testPOSTClerkSend test email (dev only)
/api/webhooks/clerkPOSTSvixwebhooksClerk user lifecycle events
/api/webhooks/lemonsqueezyPOSTHMACwebhooksPayment and subscription events
/api/webhooks/resendPOSTHMACwebhooksEmail delivery status events
/api/cron/check-trialsPOST, GETBearerExpire ended trial subscriptions
/api/cron/expire-bonus-creditsPOST, GETBearerExpire bonus credit purchases
/api/healthGETApplication health check

Rate Limit Categories

Every rate-limited endpoint belongs to one of six categories. Rate limits are enforced per-user and/or per-IP using Upstash Redis:
Rate limit configuration per category
const API_LIMITS: Record<
  APICategory,
  { user?: RateLimitConfig; ip?: RateLimitConfig }
> = {
  upload: {
    user: { requests: 10, window: '1 h', identifier: 'user' },
    ip: { requests: 20, window: '1 h', identifier: 'ip' },
  },
  email: {
    user: { requests: 5, window: '1 h', identifier: 'user' },
    ip: { requests: 10, window: '1 h', identifier: 'ip' },
  },
  contact: {
    ip: { requests: 3, window: '1 h', identifier: 'ip' },
  },
  payments: {
    user: { requests: 20, window: '1 h', identifier: 'user' },
  },
  webhooks: {
    ip: { requests: 100, window: '1 h', identifier: 'ip' },
  },
  api: {
    user: { requests: 100, window: '1 h', identifier: 'user' },
    ip: { requests: 200, window: '1 h', identifier: 'ip' },
  },
}
AI endpoints use a separate credit-based rate limiting system — see Cost Management for details.

Authentication & User

Get User Status

GET/api/user/status
Returns the authenticated user's subscription status for personalized UI display (e.g., dynamic CTA buttons on the pricing page).
  • Auth: Clerk (required)
  • Rate Limit: api — 100 req/hour per user
  • Cache: 5 minutes (private, max-age=300)
Response (200):
json
{
  "status": "active_subscriber",
  "tier": "pro",
  "billingPeriod": "monthly",
  "isTrial": false,
  "needsAction": false,
  "isLocked": false
}
Error Codes: 401 Unauthorized, 429 Rate limited, 500 Server error

Payments

Eight endpoints manage the complete payment lifecycle — from checkout creation through subscription management.

Create Checkout URL

POST/api/checkout/create-url
Creates a Lemon Squeezy checkout URL for a specific plan variant.
  • Auth: Clerk (required)
  • Rate Limit: payments
Request Body:
json
{
  "variantId": "123456",
  "tier": "pro"
}
Response (200):
json
{
  "checkoutUrl": "https://nextsaas.lemonsqueezy.com/checkout/..."
}
Error Codes: 400 Invalid variant/tier, 401 Unauthorized, 429 Rate limited, 500 Server error

Create Checkout Session

POSTGET/api/payments/create-checkout
Creates a checkout session with extended options including redirect URLs and discount codes.
  • Auth: Clerk (required)
  • Rate Limit: payments
Request Body:
json
{
  "variantId": "123456",
  "productId": "789",
  "redirectUrl": "https://yourapp.com/success",
  "cancelUrl": "https://yourapp.com/cancel",
  "discountCode": "LAUNCH20"
}
Only variantId is required. All other fields are optional.
Response (200):
json
{
  "checkoutUrl": "https://nextsaas.lemonsqueezy.com/checkout/..."
}
This endpoint also supports GET requests with query parameters tier and billing for the registration-first checkout flow:
GET /api/payments/create-checkout?tier=pro&billing=annual
→ 302 Redirect to Lemon Squeezy checkout
Error Codes: 400 Validation failed, 401 Unauthorized, 404 User not found, 500 Server error

Cancel Subscription

POST/api/payments/cancel-subscription
Cancels the user's active subscription. The subscription remains active until the current billing period ends.
  • Auth: Clerk (required)
  • Rate Limit: payments
Request Body: None required.
Response (200):
json
{
  "message": "Subscription cancelled successfully",
  "endsAt": "2026-03-14T00:00:00.000Z"
}
Error Codes: 401 Unauthorized, 404 No active subscription, 500 Server error

Change Subscription Plan

POST/api/payments/change-plan
Upgrades or downgrades the user's subscription to a different plan variant.
  • Auth: Clerk (required)
  • Rate Limit: payments
Request Body:
json
{
  "newVariantId": "654321"
}
Response (200):
json
{
  "success": true,
  "message": "Plan changed successfully",
  "newVariantId": "654321",
  "subscription": {
    "status": "active",
    "currentPeriodEnd": "2026-03-14T00:00:00.000Z"
  }
}
Error Codes: 400 Invalid variant or downgrade limit reached, 401 Unauthorized, 404 No subscription, 500 Server error

Create Portal Session

POST/api/payments/create-portal-session
Generates a Lemon Squeezy customer portal URL where users can manage billing, update payment methods, and view invoices.
  • Auth: Clerk (required)
  • Rate Limit:
Response (200):
json
{
  "url": "https://nextsaas.lemonsqueezy.com/billing/..."
}
The portal URL is valid for 24 hours.
Error Codes: 401 Unauthorized, 404 No subscription, 500 Server error

Customer Portal Access

POST/api/payments/customer-portal
Alternative endpoint for customer portal access. Returns the portal URL in a portalUrl field.
  • Auth: Clerk (required)
  • Rate Limit: payments
Response (200):
json
{
  "portalUrl": "https://nextsaas.lemonsqueezy.com/billing/..."
}
Error Codes: 401 Unauthorized, 404 No subscription, 500 Server error

Get Subscription Details

GET/api/payments/subscription
Returns the user's current subscription details, including plan tier, billing period, feature limits, and trial status.
  • Auth: Clerk (required)
  • Rate Limit:
  • Cache: 60 seconds
Response (200):
json
{
  "subscription": {
    "id": "sub_123",
    "status": "active",
    "variantId": "123456",
    "currentPeriodEnd": "2026-03-14T00:00:00.000Z"
  },
  "hasActiveSubscription": true,
  "tier": "pro",
  "billingPeriod": "monthly",
  "planName": "Pro",
  "isFreePlan": false,
  "user": {
    "downgradeCount": 0,
    "hasUsedTrial": true
  },
  "limits": {}
}
Returns subscription: null and isFreePlan: true for users without an active subscription.
Error Codes: 401 Unauthorized, 404 User not found, 500 Server error

Debug Payment State

GET/api/payments/debug
Development-only endpoint that returns detailed debug information about the user's payment state.
  • Auth: Clerk (required) or ?key=debug query parameter in development
  • Rate Limit:
Error Codes: 401 Unauthorized, 500 Server error

Credits

Four endpoints manage bonus credit packages and preferences — listing available packages, creating checkout sessions, viewing transaction history, and managing per-user bonus credit preferences. These are only available in credit-based pricing mode.

List Bonus Packages

GET/api/credits/bonus-packages
Returns the bonus credit packages available for the authenticated user's subscription tier, along with monthly purchase limits and current usage.
  • Auth: Clerk (required)
  • Rate Limit: api — 100 req/hour per user
Response (200):
json
{
  "enabled": true,
  "packages": [
    {
      "credits": 500,
      "price": 9.99,
      "variantId": "334455"
    },
    {
      "credits": 1000,
      "price": 19.99,
      "variantId": "334456"
    }
  ],
  "monthlyLimit": 2000,
  "monthlyPurchasedCredits": 500,
  "canPurchaseMore": true,
  "tier": "pro"
}
FieldTypeDescription
enabledbooleanAlways true when the endpoint returns 200
packagesarrayAvailable packages for the user's tier (up to 2 per tier)
monthlyLimitnumberMaximum bonus credits purchasable per calendar month
monthlyPurchasedCreditsnumberBonus credits already purchased this month
canPurchaseMorebooleanWhether the user is below the monthly purchase limit
tierstringUser's current subscription tier
Free-tier users cannot purchase bonus credits — they receive an empty packages array with monthlyLimit: 0.
Error Codes: 401 Unauthorized, 404 Feature disabled or not credit-based, 429 Rate limited, 500 Server error

Create Credit Checkout

POST/api/credits/checkout
Creates a Lemon Squeezy checkout URL for purchasing a bonus credit package. Validates the variant ID against the user's tier packages and enforces monthly purchase limits.
  • Auth: Clerk (required)
  • Rate Limit: payments — 20 req/hour per user
Request Body (Zod-validated):
json
{
  "variantId": "334455"
}
Response (200):
json
{
  "checkoutUrl": "https://nextsaas.lemonsqueezy.com/checkout/..."
}
After successful payment, the user is redirected to /dashboard/credits?purchase=success. The order_created webhook processes the purchase asynchronously.
Error Codes: 400 Invalid variant ID or not in tier packages, 401 Unauthorized, 403 Monthly purchase limit reached, 404 Feature disabled or not credit-based, 429 Rate limited, 500 Server error

Get Credit History

GET/api/credits/history
Returns a paginated list of credit transactions for the authenticated user, filtered by year.
  • Auth: Clerk (required)
  • Rate Limit: api — 100 req/hour per user
  • Cache: 60 seconds (private, max-age=60)
Query Parameters:
ParameterTypeDefaultConstraintsDescription
limitnumber501–100Number of transactions per page
offsetnumber0>= 0Pagination offset
yearnumberCurrent year2000–current+1Filter by calendar year
Response (200):
json
{
  "transactions": [
    {
      "id": "tx_abc123",
      "amount": -1.5,
      "balanceAfter": 848.5,
      "type": "usage",
      "operation": "chat_streaming",
      "metadata": { "model": "gpt-5-nano" },
      "createdAt": "2026-02-14T10:30:00.000Z"
    }
  ],
  "totalCount": 153,
  "availableYears": [2025, 2026]
}
FieldTypeDescription
transactionsarrayCredit transaction records, newest first
totalCountnumberTotal transactions matching the year filter
availableYearsnumber[]Years with at least one transaction (ascending)
Transaction amount is negative for deductions and positive for credits (purchases, refunds, resets). The type field identifies the category: usage, purchase, refund, monthly_reset, or adjustment.
Error Codes: 401 Unauthorized, 429 Rate limited, 500 Server error

Get Credit Preferences

GET/api/credits/preferences
Returns the user's bonus credit auto-use preference and the feature toggle status. Only available when credit-based pricing is active.
  • Auth: Clerk (required)
  • Rate Limit: api — 100 req/hour per user
  • Guard: Returns 404 if not in credit-based pricing mode
Response (200):
json
{
  "bonusCreditsAutoUse": false,
  "userToggleEnabled": true
}
FieldTypeDescription
bonusCreditsAutoUsebooleanWhether the user has opted into automatic bonus credit consumption
userToggleEnabledbooleanWhether the per-user toggle feature is enabled system-wide
Error Codes: 401 Unauthorized, 404 Not credit-based, 429 Rate limited, 500 Server error

Update Credit Preferences

PATCH/api/credits/preferences
Updates the user's bonus credit auto-use preference. Only available when both credit-based pricing and the user toggle feature are enabled.
  • Auth: Clerk (required)
  • Rate Limit: api — 100 req/hour per user
  • Guard: Returns 404 if not credit-based, 403 if user toggle feature is disabled
  • Validation: bonusPreferencesSchema
Request Body:
json
{
  "bonusCreditsAutoUse": true
}
Response (200):
json
{
  "success": true,
  "bonusCreditsAutoUse": true
}
Error Codes: 400 Invalid request body, 401 Unauthorized, 403 User toggle not enabled, 404 Not credit-based, 429 Rate limited, 500 Server error

Configuration

Get Public Config

GET/api/config/public
Returns the public pricing configuration. Used by the pricing page and checkout flows to display current plans, prices, and features.
  • Auth: None
  • Rate Limit: None
  • Cache: 1 hour (public, max-age=3600)
  • Runtime: Edge
Response (200):
json
{
  "model": "credit_based",
  "currency": "EUR",
  "tiers": [
    { "key": "basic", "name": "Basic", "monthlyPrice": 9.90 },
    { "key": "pro", "name": "Pro", "monthlyPrice": 19.90 },
    { "key": "enterprise", "name": "Enterprise", "monthlyPrice": 39.90 }
  ],
  "features": { "freeTierEnabled": true },
  "tierVisibility": {}
}
The response shape varies based on the active pricing model (credit_based or classic_saas). Credit-based responses include creditBased fields; classic SaaS responses include classicSaas fields.

Get Pricing Config

GET/api/pricing/config
Returns the complete pricing configuration with server-side validation. Ensures all required environment variables are set before returning.
  • Auth: None
  • Rate Limit: None
  • Cache: 60 seconds (must-revalidate)
Error Codes: 500 Incomplete configuration (missing environment variables)

AI Chat

Six endpoints power the standard (non-RAG) AI system: synchronous completions, streaming, speech-to-text, usage tracking, image generation, and content generation.

AI Chat Completion

POST/api/ai/chat
Generates AI completions synchronously. Supports single requests and batch mode (up to 10 concurrent requests).
  • Auth: Clerk (required)
  • Rate Limit: Credit-based (15 credits per request)
  • Feature Guard: Returns 404 if NEXT_PUBLIC_AI_LLM_CHAT_ENABLED is false
Request Body (Zod-validated):
ChatRequestSchema — request validation
const ChatRequestSchema = z.object({
  messages: z.array(
    z.object({
      role: z.enum(['system', 'user', 'assistant', 'function', 'tool']),
      content: z.string(),
      name: z.string().optional(),
    })
  ),
  model: z.string().optional(),
  temperature: z.number().min(0).max(2).optional(),
  maxTokens: z.number().positive().optional(),
  systemPrompt: z.string().optional(),
  context: z.string().optional(),
  // Optional caching control
  cache: z.boolean().optional(),
  cacheTTL: z.number().positive().optional(),
})
Response (200):
json
{
  "text": "The AI response content...",
  "model": "gpt-5-nano",
  "usage": { "promptTokens": 150, "completionTokens": 80 },
  "cost": 15
}
Error Codes: 400 Validation failed, 401 Unauthorized, 402 Insufficient credits, 429 Rate limited, 500 Server error

AI Streaming Completion

POST/api/ai/stream
Generates AI completions with Server-Sent Events (SSE) streaming. Supports text-only and multimodal (vision) requests via ContentPart[] message format.
  • Auth: Clerk (required)
  • Rate Limit: Credit-based (20 credits for text, 30 credits for image analysis — auto-detected)
  • Response Type: text/event-stream
Request Body (text-only):
json
{
  "messages": [{ "role": "user", "content": "Hello" }],
  "model": "gpt-5-nano",
  "temperature": 0.7,
  "maxTokens": 1000,
  "systemPrompt": "You are a helpful assistant."
}
Request Body (multimodal with images):
json
{
  "messages": [{
    "role": "user",
    "content": [
      { "type": "image", "image": "data:image/png;base64,..." },
      { "type": "text", "text": "Describe this image" }
    ]
  }]
}
Response: SSE stream with X-RateLimit-* headers.
Error Codes: 400 Validation failed, 401 Unauthorized, 402 Insufficient credits, 429 Rate limited, 500 Server error

Get AI Usage Stats

GET/api/ai/usage
Returns AI usage statistics and remaining limits for the authenticated user.
  • Auth: Clerk (required)
  • Rate Limit:
Response (200) — Credit system enabled:
json
{
  "unlimited": false,
  "tier": "pro",
  "monthlyLimit": 5000,
  "remaining": 4847,
  "bonusCredits": 50,
  "used": 153,
  "resetAt": "2026-02-01T00:00:00.000Z"
}
Response (200) — Credit system disabled (classic SaaS):
json
{
  "unlimited": true,
  "tier": "unlimited",
  "monthlyLimit": 999999,
  "remaining": 999999,
  "bonusCredits": 0,
  "used": 0
}
FieldTypeDescription
unlimitedbooleantrue when credit system is disabled (classic SaaS mode)
tierstringSubscription tier (free, basic, pro, enterprise) or "unlimited"
monthlyLimitnumberMonthly credit allocation for the user's tier
remainingnumberAvailable subscription credits this billing cycle
bonusCreditsnumberPurchased bonus credits (separate from subscription allocation)
usednumberCredits consumed this billing cycle (monthlyLimit - remaining)
resetAtstring?ISO 8601 timestamp of last credit reset (absent when credit system disabled)
Error Codes: 401 Unauthorized, 404 User not found, 500 Server error

Speech-to-Text

POST/api/ai/speech-to-text
Transcribes audio recordings using the OpenAI Whisper API. Used by the Audio Input feature in LLM Chat.
  • Auth: Clerk (required)
  • Rate Limit: Credit-based (20 credits per transcription)
  • Content Type: multipart/form-data
  • Feature Guard: Returns 404 if NEXT_PUBLIC_AI_AUDIO_INPUT_ENABLED is false
Request Body:
FieldTypeRequiredDescription
audioFileYesAudio file (WebM, WAV, MP3, M4A, OGG, FLAC — max 25 MB)
Response (200):
json
{
  "text": "The transcribed text content...",
  "language": "en",
  "duration": 5.2
}
FieldTypeDescription
textstringTranscribed text from the audio
languagestringDetected language code (ISO 639-1)
durationnumberAudio duration in seconds
Error Codes: 400 No audio file provided or invalid format, 401 Unauthorized, 402 Insufficient credits, 404 Feature disabled, 413 File too large (>25 MB), 500 Server error

AI Image Generation

POST/api/ai/image-gen
Generates images using OpenAI's GPT Image API. Supports multiple models, sizes, quality levels, and output formats.
  • Auth: Clerk (required)
  • Rate Limit: Credit-based (80 credits per generation)
  • Feature Guard: Returns 404 if NEXT_PUBLIC_AI_IMAGE_GEN_ENABLED is false
  • Max Duration: 60 seconds
Request Body (Zod-validated):
json
{
  "prompt": "A mountain landscape at sunset",
  "model": "gpt-image-1",
  "size": "1024x1024",
  "quality": "auto",
  "output_format": "png",
  "background": "opaque"
}
FieldTypeRequiredDefaultOptions
promptstringYes1-4000 characters
modelstringNogpt-image-1gpt-image-1, gpt-image-1.5, gpt-image-1-mini
sizestringNo1024x10241024x1024, 1536x1024, 1024x1536, auto
qualitystringNoautolow, medium, high, auto
output_formatstringNopngpng, jpeg, webp
backgroundstringNoopaqueopaque, transparent
Response (200):
json
{
  "image": "data:image/png;base64,...",
  "model": "gpt-image-1",
  "size": "1024x1024",
  "quality": "auto",
  "format": "png",
  "revised_prompt": "A detailed mountain landscape...",
  "credits_used": 80,
  "credits_remaining": 4920
}
Error Codes: 400 Validation failed or content policy violation, 401 Unauthorized, 402 Insufficient credits, 404 Feature disabled, 429 Rate limited, 500 Server error

AI Content Generation

POST/api/ai/generate-content
Generates template-based text content with Server-Sent Events (SSE) streaming. Supports five content templates with configurable tone, language, and length.
  • Auth: Clerk (required)
  • Rate Limit: Credit-based (25 credits per generation)
  • Response Type: text/event-stream
  • Feature Guard: Returns 404 if NEXT_PUBLIC_AI_CONTENT_GEN_ENABLED is false
  • Max Duration: 60 seconds
Request Body (Zod-validated):
json
{
  "template": "email",
  "fields": {
    "recipient": "Customer",
    "subject": "Welcome to our platform",
    "key_points": "Introduction, features overview"
  },
  "tone": "professional",
  "language": "en",
  "length": "medium"
}
FieldTypeRequiredDefaultOptions
templatestringYesemail, product_description, blog_outline, social_media, marketing_copy
fieldsobjectYesTemplate-specific key-value pairs
tonestringNoprofessionalprofessional, friendly, persuasive, neutral
languagestringNoende, en, fr, es
lengthstringNomediumshort, medium, long
Response: SSE stream with content chunks, followed by a meta event with credit info, then [DONE].
Error Codes: 400 Validation failed or missing required template fields, 401 Unauthorized, 402 Insufficient credits, 404 Feature disabled, 429 Rate limited, 500 Server error

AI RAG — Authenticated

Three endpoints for the authenticated RAG (Retrieval-Augmented Generation) chat system with conversation persistence.

Ask RAG Question

POST/api/ai/rag/ask
Submits a question to the RAG system. Retrieves relevant document chunks, generates a context-aware response, and persists the conversation.
  • Auth: Clerk (required)
  • Rate Limit: Credit-based (varies by query complexity)
  • Feature Guard: Returns 404 if NEXT_PUBLIC_AI_RAG_CHAT_ENABLED is false
Request Body:
json
{
  "question": "How do I configure authentication?",
  "conversationId": "conv_abc123",
  "stream": true
}
Accepts either question (string) or messages (Vercel AI SDK format). The conversationId is optional — omit it to start a new conversation.
Streaming Response Headers:
  • X-RAG-Sources — Base64-encoded JSON string of source document references
  • X-RAG-Chunks-Used — Number of retrieved chunks
  • X-RAG-Tokens-Estimated — Estimated token count
Error Codes: 400 Missing question, 401 Unauthorized, 402 Insufficient credits, 500 Server error

List RAG Conversations

GETDELETE/api/ai/rag/conversations
Lists the user's RAG conversations with pagination.
  • Auth: Clerk (required)
  • Rate Limit:
Query Parameters:
  • limit — Number of conversations (default: 20)
  • offset — Pagination offset (default: 0)
Response (200):
json
{
  "conversations": [
    {
      "id": "conv_abc123",
      "title": "Authentication Setup",
      "messageCount": 5,
      "lastMessage": "You can configure...",
      "createdAt": "2026-02-14T10:00:00.000Z"
    }
  ],
  "pagination": { "total": 12, "limit": 20, "offset": 0, "hasMore": false }
}
DELETE method on the same path deletes a conversation:
DELETE /api/ai/rag/conversations?id=conv_abc123
→ { "success": true, "deleted": 1 }
Error Codes: 400 Missing ID (DELETE), 401 Unauthorized, 404 Not found, 500 Server error

Get RAG Conversation

GET/api/ai/rag/conversations/[id]
Returns a single conversation with all its messages.
  • Auth: Clerk (required)
  • Rate Limit:
Response (200):
json
{
  "id": "conv_abc123",
  "title": "Authentication Setup",
  "tier": "pro",
  "messages": [
    {
      "id": "msg_1",
      "role": "user",
      "content": "How do I configure authentication?",
      "tokens": 8,
      "model": "gpt-5-nano",
      "createdAt": "2026-02-14T10:00:00.000Z"
    }
  ]
}
Error Codes: 401 Unauthorized, 404 Conversation not found, 500 Server error

File Storage

Upload File

POST/api/upload
Uploads a file to Vercel Blob storage. The file content is streamed directly from the request body — no multipart form data.
  • Auth: Clerk (required)
  • Rate Limit: upload — 10 req/hour per user, 20 req/hour per IP
Query Parameters (required):
  • filename — Original file name
  • filesize — File size in bytes
  • filetype — MIME type (e.g., image/png)
Request Body: Raw file content as ReadableStream.
Response (200):
json
{
  "id": "file_abc123",
  "url": "https://blob.vercel-storage.com/...",
  "originalName": "document.pdf",
  "size": 245760,
  "contentType": "application/pdf",
  "createdAt": "2026-02-14T10:00:00.000Z"
}
Error Codes: 400 Missing params or invalid file type, 401 Unauthorized, 409 Duplicate file, 503 Database error, 500 Server error

Get or Delete File

GETDELETE/api/files/[id]
Returns metadata for a specific uploaded file. Only the file owner can access it.
  • Auth: Clerk (required)
  • Rate Limit:
Response (200):
json
{
  "id": "file_abc123",
  "url": "https://blob.vercel-storage.com/...",
  "originalName": "document.pdf",
  "size": 245760,
  "contentType": "application/pdf",
  "createdAt": "2026-02-14T10:00:00.000Z"
}
DELETE method on the same path removes the file from both Vercel Blob and the database:
DELETE /api/files/file_abc123
→ { "success": true, "message": "File deleted successfully" }
Error Codes: 401 Unauthorized, 404 File not found, 500 Server error

Email

Send Email

POST/api/email/send
Sends a transactional email through Resend. Requires either HTML or plain text content.
  • Auth: Clerk (required)
  • Rate Limit: email — 5 req/hour per user, 10 req/hour per IP
Request Body:
json
{
  "to": "user@example.com",
  "subject": "Welcome to our platform",
  "html": "<h1>Welcome!</h1><p>Thanks for signing up.</p>",
  "type": "welcome"
}
Either html or text must be provided.
Response (200):
json
{
  "success": true,
  "messageId": "msg_resend_123",
  "emailLogId": "log_abc123"
}
Error Codes: 400 Validation failed, 401 Unauthorized, 404 User not found, 500 Server error

Send Test Email

POST/api/email/test
Sends a test email for development and debugging. Only available in non-production environments.
  • Auth: Clerk (optional)
  • Rate Limit:
Request Body:
json
{
  "email": "dev@example.com"
}
Error Codes: 400 Missing email, 403 Not in development, 500 Server error

Webhooks

All three webhook endpoints verify incoming signatures before processing events. They use the webhooks rate limit category (100 req/hour per IP).

Clerk Webhook

POST/api/webhooks/clerk
Processes Clerk user lifecycle events. Creates, updates, or deletes users in the database.
  • Auth: Svix signature verification (CLERK_WEBHOOK_SECRET)
  • Rate Limit: webhooks
Handled Events:
EventAction
user.createdCreates user with Free Tier credits
user.updatedUpdates email and name
user.deletedDeletes user from database
Response: 200 on success, 400 if Svix headers are missing, 401 if signature verification fails.

Lemon Squeezy Webhook

POST/api/webhooks/lemonsqueezy
Processes Lemon Squeezy payment and subscription events. This is the most complex webhook handler, supporting 11 event types.
  • Auth: HMAC signature verification (LEMONSQUEEZY_WEBHOOK_SECRET)
  • Rate Limit: webhooks
Handled Events:
EventAction
subscription_createdCreate subscription record
subscription_updatedUpdate plan/status
subscription_cancelledMark for end-of-period cancellation
subscription_resumedReactivate cancelled subscription
subscription_expiredDowngrade to free tier
subscription_pausedPause subscription
subscription_unpausedResume paused subscription
subscription_payment_failedLog failure, notify user
subscription_payment_successConfirm payment
subscription_payment_recoveredClear failed state
order_createdProcess one-time purchase (bonus credits)

Resend Webhook

POST/api/webhooks/resend
Processes Resend email delivery status events (delivered, bounced, complained).
  • Auth: HMAC signature verification (RESEND_WEBHOOK_SECRET)
  • Rate Limit: webhooks
Response: { "success": true, "processed": "email.delivered" }
Error Codes: 400 Invalid payload, 401 Signature verification failed, 500 Server error

Cron Jobs

Automated maintenance tasks triggered by Vercel Cron. Both endpoints require a CRON_SECRET bearer token in production.

Check Trial Expirations

POSTGET/api/cron/check-trials
Expires trial subscriptions that have reached their end date. Runs daily at 2:00 AM UTC.
  • Auth: Bearer token (CRON_SECRET) in production
  • Schedule: 0 2 * * * (daily)
  • Pricing Model: Classic SaaS only (no-op for credit-based)
Response (200):
json
{
  "success": true,
  "message": "Trial check completed",
  "expiredCount": 3,
  "timestamp": "2026-02-14T02:00:00.000Z"
}

Expire Bonus Credits

POSTGET/api/cron/expire-bonus-credits
Expires bonus credit purchases that have passed their expiry date. Runs daily at 3:00 AM UTC.
  • Auth: Bearer token (CRON_SECRET) in production
  • Schedule: 0 3 * * * (daily)
  • Pricing Model: Credit-based only
Response (200):
json
{
  "success": true,
  "message": "Bonus credit expiry check completed",
  "expiredCount": 1,
  "timestamp": "2026-02-14T03:00:00.000Z"
}

Health

Health Check

GET/api/health
Returns application health status including database connectivity and memory usage.
  • Auth: None
  • Rate Limit: None
  • Cache: None (no-cache, no-store, must-revalidate)
Response (200 healthy / 503 unhealthy):
json
{
  "status": "healthy",
  "timestamp": "2026-02-14T10:00:00.000Z",
  "version": "1.0.0",
  "environment": "production",
  "checks": {
    "database": "connected",
    "memory": "ok"
  },
  "responseTime": "12ms"
}
Use this endpoint for uptime monitoring services and load balancer health checks.

Error Codes

All endpoints follow a consistent error response format:
json
{
  "error": "Human-readable error message"
}
CodeMeaningCommon Cause
400Bad RequestValidation failed — check request body against the Zod schema
401UnauthorizedMissing or invalid Clerk session / webhook signature
402Payment RequiredInsufficient AI credits for the requested operation
403ForbiddenDevelopment-only endpoint accessed in production
404Not FoundResource doesn't exist or feature is disabled via feature flag
405Method Not AllowedWrong HTTP method for the endpoint
409ConflictDuplicate resource (e.g., re-uploading same file)
429Too Many RequestsRate limit exceeded — check X-RateLimit-* headers for reset time
500Server ErrorInternal error — check server logs
503Service UnavailableDatabase or health check failed