API Reference & Usage Examples
Everything you need to build on the EISH platform — AI Gateway calls, server functions, authenticated endpoints, admin gating, storage, and database access. All examples are copy-paste ready for TanStack Start.
AI Gateway (Chat & Streaming)
Call any supported chat model through the Lovable AI Gateway. Never expose LOVABLE_API_KEY to the browser — always call from a server function.
import { createServerFn } from "@tanstack/react-start";
import { generateText } from "ai";
import { createLovableAiGatewayProvider } from "@/lib/ai-gateway";
export const summarize = createServerFn({ method: "POST" })
.inputValidator((d: { text: string }) => d)
.handler(async ({ data }) => {
const gateway = createLovableAiGatewayProvider(process.env.LOVABLE_API_KEY!);
const model = gateway("google/gemini-3-flash-preview");
const { text } = await generateText({ model, prompt: `Summarize: ${data.text}` });
return { summary: text };
});Server Functions (createServerFn)
Typed RPC from client to server. Validate input with Zod, keep secrets inside the handler.
// src/lib/courses.functions.ts
import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";
export const getCourse = createServerFn({ method: "GET" })
.inputValidator((d) => z.object({ slug: z.string() }).parse(d))
.handler(async ({ data }) => {
// read from DB, external API, etc.
return { slug: data.slug, title: "Intro to Hospitality" };
});
// Client:
import { useServerFn } from "@tanstack/react-start";
const call = useServerFn(getCourse);
const course = await call({ data: { slug: "intro" } });Authentication
Session lives in the Supabase client. Protected server functions use requireSupabaseAuth; the bearer token is attached automatically.
import { createServerFn } from "@tanstack/react-start";
import { requireSupabaseAuth } from "@/integrations/supabase/auth-middleware";
export const getMyProfile = createServerFn({ method: "GET" })
.middleware([requireSupabaseAuth])
.handler(async ({ context }) => {
const { supabase, userId } = context;
const { data } = await supabase.from("profiles").select("*").eq("id", userId).single();
return data;
});Admin Gate
Restricted routes live under src/routes/_authenticated/admin.*. Access is granted when the caller is the workspace owner, holds the admin/owner role, or matches BACKUP_ADMIN_RECOVERY_EMAIL. See src/lib/admin-gate.ts for the pure evaluator (unit-tested).
import { evaluateAdminGate } from "@/lib/admin-gate";
const result = evaluateAdminGate({
email: claims.email,
hasOwnerRole: false,
hasAdminRole: true,
backupRecoveryEmail: process.env.BACKUP_ADMIN_RECOVERY_EMAIL ?? "",
});
if (!result.allowed) throw new Response("Forbidden", { status: 403 });Storage
Signed URLs for uploads and downloads via the Supabase client.
import { supabase } from "@/integrations/supabase/client";
const { data } = await supabase.storage
.from("course-materials")
.createSignedUploadUrl(`${userId}/lesson-1.mp4`);
// Use data.signedUrl with a PUT request from the browser.Database (with RLS)
All public tables have RLS enabled. Reads/writes go through the authenticated client so policies apply automatically.
const { data, error } = await supabase
.from("courses")
.select("id, title, price_cents")
.eq("published", true)
.order("created_at", { ascending: false })
.limit(20);Image Generation
Generate images through the AI Gateway image endpoint. Same key, same routing.
const gateway = createLovableAiGatewayProvider(process.env.LOVABLE_API_KEY!);
const model = gateway.image("google/imagen-4-fast");
const { images } = await generateImage({ model, prompt: "A cozy hospitality classroom" });