Adding AI Features
Integrate Claude, OpenAI, or any AI provider into your SaaS using Edge Functions and the credit system.
Adding AI Features
ScaleRocket doesn't include a specific AI SDK — that's intentional. Your users might need text generation, image analysis, code assistance, or nothing AI-related at all. Instead, this guide shows you how to add any AI provider and connect it to the existing credit system.
Architecture
User clicks "Generate" in dashboard
→ apps/app calls Edge Function
→ Edge Function deducts credits (use-credits)
→ Edge Function calls AI API (Claude, OpenAI, etc.)
→ Returns result to userThe key pattern: AI calls happen in Edge Functions, never in the client. This keeps your API keys secure and lets you control costs via credits.
Example: Claude SDK
1. Install the SDK
Add the Anthropic SDK to your Edge Function dependencies. Since Edge Functions run on Deno, you import directly:
// No npm install needed — Deno imports from npm
import Anthropic from "npm:@anthropic-ai/sdk";2. Set your API key
npx supabase secrets set ANTHROPIC_API_KEY=sk-ant-...3. Create an Edge Function
// supabase/functions/generate-text/index.ts
import { corsHeaders, supabaseAdmin } from "../_shared/supabase.ts";
import Anthropic from "npm:@anthropic-ai/sdk";
const anthropic = new Anthropic({
apiKey: Deno.env.get("ANTHROPIC_API_KEY"),
});
Deno.serve(async (req) => {
// Handle CORS preflight
if (req.method === "OPTIONS") {
return new Response("ok", { headers: corsHeaders });
}
try {
// 1. Authenticate the user
const authHeader = req.headers.get("Authorization");
if (!authHeader) {
return new Response(
JSON.stringify({ error: "Not authenticated" }),
{ status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
const token = authHeader.replace("Bearer ", "");
const { data: { user }, error: authError } = await supabaseAdmin.auth.getUser(token);
if (authError || !user) {
return new Response(
JSON.stringify({ error: "Invalid token" }),
{ status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
// 2. Parse the request
const { prompt } = await req.json();
if (!prompt || typeof prompt !== "string") {
return new Response(
JSON.stringify({ error: "Missing prompt" }),
{ status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
// 3. Deduct credits (1 credit per generation)
const { data: credits, error: creditError } = await supabaseAdmin
.from("credits")
.update({ balance: supabaseAdmin.rpc("decrement_balance", { amount: 1 }) })
.eq("user_id", user.id)
.gte("balance", 1)
.select()
.single();
if (creditError || !credits) {
return new Response(
JSON.stringify({ error: "Insufficient credits" }),
{ status: 402, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
// 4. Call Claude
const message = await anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
});
const text = message.content[0].type === "text" ? message.content[0].text : "";
// 5. Return the result
return new Response(
JSON.stringify({
result: text,
creditsRemaining: credits.balance,
}),
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
} catch (error) {
console.error("Generation error:", error);
return new Response(
JSON.stringify({ error: "Generation failed" }),
{ status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);
}
});4. Call from the dashboard
// apps/app/src/lib/api.ts — add this function
export async function generateText(prompt: string) {
return callFunction<{ result: string; creditsRemaining: number }>(
"generate-text",
{ method: "POST", body: { prompt } }
);
}5. Use in a component
// apps/app/src/routes/_authenticated/generate.tsx
import { createFileRoute } from "@tanstack/react-router";
import { useState } from "react";
import { Button, Card, CardHeader, CardTitle, CardContent } from "@saas/ui";
import { generateText } from "@/lib/api";
export const Route = createFileRoute("/_authenticated/generate")({
component: GeneratePage,
});
function GeneratePage() {
const [prompt, setPrompt] = useState("");
const [result, setResult] = useState("");
const [loading, setLoading] = useState(false);
const handleGenerate = async () => {
setLoading(true);
const { data, error } = await generateText(prompt);
setLoading(false);
if (error) {
alert(error);
return;
}
setResult(data.result);
};
return (
<div className="p-6 max-w-2xl">
<Card>
<CardHeader>
<CardTitle>AI Generator</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="Enter your prompt..."
className="w-full rounded-md border p-3 min-h-[100px]"
/>
<Button onClick={handleGenerate} isLoading={loading}>
Generate (1 credit)
</Button>
{result && (
<div className="rounded-md bg-muted p-4 whitespace-pre-wrap">
{result}
</div>
)}
</CardContent>
</Card>
</div>
);
}6. Deploy
npx supabase functions deploy generate-textUsing OpenAI Instead
The pattern is identical — just swap the SDK:
// supabase/functions/generate-text/index.ts
import OpenAI from "npm:openai";
const openai = new OpenAI({
apiKey: Deno.env.get("OPENAI_API_KEY"),
});
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: prompt }],
});
const text = response.choices[0].message.content;Set the key:
npx supabase secrets set OPENAI_API_KEY=sk-...Credit Costs
You can vary credit costs based on the operation:
// Light operation: 1 credit
const CREDIT_COST = {
"text-short": 1, // Quick text generation
"text-long": 3, // Long-form content
"image": 5, // Image generation
"analysis": 2, // Document analysis
};
// Deduct the appropriate amount
const cost = CREDIT_COST[operationType];This integrates naturally with ScaleRocket's existing credit system — users buy a plan with a monthly allowance, and each AI operation deducts from their balance.
Security Tips
- Never expose AI API keys in client code — always call through Edge Functions
- Always deduct credits before calling the AI — if the AI call fails, refund the credits
- Set usage limits — cap the
max_tokensto prevent expensive requests - Validate inputs — sanitize prompts to prevent prompt injection
- Log usage — track which users make how many requests for cost monitoring
Next Steps
- Credits system — how the credit deduction works
- API calls — how to create and call Edge Functions
- Deployment — deploy your AI Edge Function to production
Done reading? Mark this page as complete.