Developer Reference

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" });

Ready to build?

Follow the quickstart to get a local dev environment running in under 5 minutes.