API & Integrations
Regurai exposes three kinds of server-side surfaces. Pick the right one for the job.
| Surface | Mechanism | Caller | Auth |
|---|---|---|---|
| Internal app logic | createServerFn | Same-origin React code | User session (requireSupabaseAuth) |
| Public HTTP endpoint | TanStack server route under src/routes/api/public/* | External services (webhooks, cron) | Per-route signature verification |
| Isolated security primitive | Supabase Edge Function (Deno) | Internal callers needing IP visibility, isolation, or service-role power | Function-specific |
Internal APIs (server functions)
Authoring convention (see server-side-modern knowledge):
// src/lib/something.functions.ts
import { createServerFn } from "@tanstack/react-start";
import { requireSupabaseAuth } from "@/integrations/supabase/auth-middleware";
import { z } from "zod";
export const doThing = createServerFn({ method: "POST" })
.middleware([requireSupabaseAuth])
.inputValidator((input) => z.object({ id: z.string().uuid() }).parse(input))
.handler(async ({ data, context }) => {
const { supabase, userId } = context; // RLS scoped to user
// ...
});
Global middleware in src/start.ts:
errorMiddleware(request-level error envelope).attachSupabaseAuth(function-level — adds the user's bearer token to every server fn RPC).
Without attachSupabaseAuth, protected server fns return 401.
External APIs / webhooks
Server routes under src/routes/api/public/* are exempt from the auth gate on published deployments. Every public route MUST verify caller identity before doing anything — usually via HMAC signature.
Canonical webhook shape:
// src/routes/api/public/webhook.ts
import { createFileRoute } from "@tanstack/react-router";
import { createHmac, timingSafeEqual } from "crypto";
export const Route = createFileRoute("/api/public/webhook")({
server: {
handlers: {
POST: async ({ request }) => {
const sig = request.headers.get("x-webhook-signature");
const body = await request.text();
const expected = createHmac("sha256", process.env.WEBHOOK_SECRET!)
.update(body).digest("hex");
if (!sig || !timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return new Response("Invalid signature", { status: 401 });
}
// ...
return new Response("ok");
},
},
},
});
Stable URLs for external configuration:
- Production:
project--<id>.lovable.app - Preview:
project--<id>-dev.lovable.app
Edge functions catalogue
| Function | Path | Purpose | Auth model |
|---|---|---|---|
login-rate-limit | supabase/functions/login-rate-limit/index.ts | IP-keyed brute-force lockout (5/15 min) | Anon; CORS open to app origin |
To add an edge function:
- Scaffold under
supabase/functions/<name>/index.ts. - Use the service-role client only inside the function — never expose its key.
- Deploy via the Lovable Cloud edge-function tooling.
- Add the entry to the table above in this chapter.
Third-party integrations
| Integration | Purpose | Location |
|---|---|---|
| Supabase Auth | Identity, sessions, MFA | @supabase/supabase-js, @/integrations/supabase/* |
| Supabase Postgres | Application data | RLS-scoped via server fns |
| Cloudflare Workers | Edge runtime / SSR | Built-in to TanStack Start |
| Lovable AI Gateway | LLM calls (where used) | Preferred over direct provider keys |
When adding a new third-party integration, prefer existing platform connectors before adding a custom API key. Document each new integration here with: what it does, what data is shared, what credential is required, and where the secret is stored.
Error handling standards
- All server functions throw native
ErrororResponse(401,403, etc.). - Public routes return
Responseobjects with a clearstatuscode; never leak stack traces. - Client code surfaces server fn errors via TanStack Query's
errorstate; user messaging is generic for auth failures. - All errors are logged to the platform's edge / SSR log stream; no PII in log messages.
Fairness decisions ingestion (Phase 5.3)
External AI systems push decision events into Regurai via:
POST /api/public/hooks/fairness-decisions
Headers:
x-tenant-id: <uuid>
x-signature: <hex sha256 hmac of raw body>
x-secret-label: <optional secret label for rotation>
Body: { "decisions": [ DecisionEvent, ... ] } // max 500 per batch, body ≤1 MB
Authentication is HMAC-SHA256 of the raw request body, using a per-tenant
secret stored in public.fairness_ingest_secrets. The secret column is
not readable by any authenticated role (column-level grants); only the
backend service role can read it during verification. Admins create and
rotate secrets through the (admin-only) RLS path; the plaintext is shown
once at creation time and never again.
DecisionEvent schema (Zod-validated):
| Field | Type | Notes |
|---|---|---|
case_ref | string (≤128) | Required. Caller-side identifier. |
subject_ref | string (≤128) | Optional. Caller-side identifier. |
model_id | uuid | Optional. References public.ai_models. |
decision_label | string (≤64) | Required. |
decision_score | number | Optional. |
outcome | enum | One of: approved, declined, escalated, referred, pending. |
protected_group | string | Optional. Must match the allow-list `^(age_band |
reviewer_override | boolean | Optional. |
latency_ms | int (0–600 000) | Optional. |
features_redacted | object | Optional. Must be pre-redacted; strings capped at 256 chars. |
rationale | string (≤2 000) | Optional. |
Defence-in-depth: the entire payload is scanned for SSN / IBAN / email /
credit-card shapes after Zod validation; matches cause HTTP 422 even if the
caller smuggled them inside features_redacted strings.
On success the row is inserted with is_seed = false, tenant scoped by
the matched secret's tenant_id — never by any tenant value in the body.
Out of scope for 5.3 and deferred to Phase 5.6 — Client AI Connectors: SDKs, batch/CSV import UI, Regurai-side polling, schema discovery, and real-time streaming.