Skip to main content

Edge Functions

supabase/functions/ contains 26 checked-in function source directories. This is a source inventory, not a claim about any hosted project's deployment state.

Every function is configured with verify_jwt = false because:

  • the gateway verifier rejects the project's ES256 Auth tokens,
  • browser WebSockets require custom query-token validation,
  • MCP discovery/challenge endpoints need custom unauthenticated responses, and
  • all functions implement their own identity/public-access decision.

Authenticated functions call auth.getUser(token). Gateway bypass is not an authorization bypass.

Shared Modules

Important modules in supabase/functions/_shared/ are:

ModuleResponsibility
http.tsCORS, OPTIONS, JSON, service client, bearer verification, trusted origin/IP
authz.tsActive company membership and admin/consultant equivalence
budgets.tsFeature-gated monthly budget check and fixed-window rate helper
models.tsCanonical text, Live, TTS model IDs and voice defaults
locale.tsBCP-47 normalization, user locale and prompt language
json.tsResilient JSON extraction from grounded/model prose
maturity-signals.tsParse/apply maturity signals
maturity-persist.tsOptimistic profile-version persistence
v2-prompt.tsAdvisor prompt composition
scope.tsUK-readiness scope guardrail
tasks.tsAI task generation and deduplication
live-usage.tsRealtime token estimates/accounting
email-templates.ts / locale assetsBranded localized email

Canonical CORS

All browser-callable functions use:

export const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers':
'authorization, x-client-info, apikey, content-type, baggage, sentry-trace',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
};

Do not remove baggage or sentry-trace; browser tracing depends on them. The project contract intentionally uses *.

Canonical authenticated HTTP shape

const pre = handleOptions(req);
if (pre) return pre;
if (req.method !== 'POST') return json({ error: 'method_not_allowed' }, 405);

const supabase = serviceClient();
const auth = await requireUser(req, supabase);
if (auth.response) return auth.response;
const { user } = auth;

// Bind requested resource to user here.

serviceClient() bypasses RLS. Identity verification must be followed by resource-specific authorization.

Checked-in Function Reference

FunctionMethodPrimary contractAuth and scope
aidaPOSTmodel contents + allowlisted tool declarations → text or tool callsUser; aida_enabled unless system admin; company access and budget/rate gate
aida-liveWSlive audio/control framesQuery-token user; Aida flag; company budget
chatPOST{session_id,message,hidden?,attachment?} → advisor response, maturity/task changesUser; session/company membership; budget and rate
chat-liveWSsession token/ID/resume handle; PCM/control framesQuery-token user; session/company membership; budget
chat-multiPOST{session_id} requests the next AI turnActive participant and company member; turn claim; budget
competitor-analysisPOSTcompany/profile or manual context, phase/options → grounded analysisCompany member; budget and rate
delete-accountPOSTauthenticated self-deleteUser may delete self only; transactional public-data cleanup then Auth delete
demoPOSTseed, teardown, sweep actionsUser; operations confined to caller-owned __demo-* workspace
documentsPOSTstatus, upload_url, confirm_upload, download_url, delete_objectUser; active company membership; uploader/admin action checks
early-accessPOSTemail/locale/source → registrationPublic, trusted-IP rate limit, validated input
gemini-ttsPOSTtext, voice, language → 24 kHz PCM16 payloadUser; budget/rate; usage recorded
generate-summaryPOST{session_id} → persisted structured summarySession/company member; single-winner claim; budget/rate
invite-codesPOSTaction-routed preflight/list/create/revokePreflight public and rate-limited; management system-admin only
lead-huntPOSTgrounded prospect criteria → lead setUser; feature flag, budget/rate; system-admin flag bypass
livekit-session-controlPOSTleave/remove, session and optional userParticipant may leave self; owner/admin removes others; DB + media plane
livekit-tokenPOST{room} → short-lived room token and URLUser; strict room-name/session/participant checks and budget
mcpHTTPStreamable HTTP MCP discovery and JSON-RPC toolsPublic discovery/challenge; OAuth bearer for tools; tenant/role scope
notify-feedbackPOSTfeedback ID → admin notificationsAuthenticated submitter/resource checks; trusted APP_URL
notify-session-addPOSTsession/user → participant notificationAuthenticated owner/admin and real target relationship
prompt-testPOSTadvisor-test or refine prompt modeSystem admin; usage recorded
send-invitePOST{invitation_id} → localized invitation emailCompany admin/consultant; rows and URL loaded server-side
shared-sessionGETshare token → allowlisted read-only sessionPublic capability; trusted-IP rate limit; expiry/revocation checks
structure-feedbackPOSTraw feedback → normalized fieldsUser; budget gate; usage recorded
synthesise-company-infoPOSTcompany ID + extracted text → merged factsCompany admin/consultant; budget/rate; history/accounting
tasksPOSTlist, update, dismissUser; active company membership and allowlisted update fields
update-company-factsPOSTupdate/reset facts/learningsCompany admin/consultant; history row; pipeline-safe service write

A local, Git-ignored supabase/config.toml may include declarations for future names without source directories. Treat the 26 checked-in directories above as authoritative; do not document local placeholders as implemented.

The chat Function

Request Format

The browser supplies:

{
session_id: string;
message: string;
hidden?: boolean;
attachment?: {
filename: string;
text: string;
truncated?: boolean;
};
}

The function rejects malformed/oversized input before calling Gemini. It loads the session itself and verifies active access to session.company_id; a client-supplied company ID is not authoritative.

Prompt composition

The v2 prompt combines:

  • active prompt_versions v2 prompt
  • UK-readiness scope
  • up to the bounded recent transcript
  • current company maturity state
  • facts and learnings
  • outstanding tasks
  • focus dimension
  • relevant partner/platform capability context
  • user locale/language directive
  • expected structured response contract

Response Format

The current response shape includes:

{
response: string;
reasoning?: string;
experience_version: 'v2';
token_usage: {
input: number;
output: number;
remaining_input: number | null;
};
metadata: {
processing_time_ms?: number;
maturity_signals?: unknown[];
company_facts?: unknown;
};
maturity_update?: unknown;
maturity_transitions?: unknown[];
tasks?: {
completed?: unknown[];
added?: unknown[];
};
}

Legacy readiness_update and remaining_output_budget examples are not the current contract.

Processing Steps

  1. Method/preflight validation.
  2. Bearer identity verification.
  3. Session and active company authorization.
  4. Monthly input/output budget gate.
  5. Per-user chat rate limit.
  6. Context and prompt load.
  7. Gemini call using GEMINI_TEXT_MODEL.
  8. Structured response parsing.
  9. Message and token audit persistence.
  10. Atomic monthly usage roll-up.
  11. Optimistic maturity persistence with retry.
  12. Company-fact merge/history and task generation.
  13. Controlled response.

Error responses

Known classes include:

StatusMeaning
400Invalid body/input
401Missing/invalid identity
403Cross-tenant or disallowed access
404Session unavailable
405Wrong method
409Inactive/conflicting state
429Rate or token budget exceeded
500Internal/configuration failure with generic client copy
502/503Provider or optional integration unavailable

Raw exception details stay in server logs.

The generate-summary Function

The function claims the session before provider work. It reads the transcript, current and starting maturity states, tasks and company context, then generates:

  • narrative
  • constraints
  • role-based next steps
  • warning flags
  • dimensions advanced
  • recommended tasks

It writes summary, completion status, maturity snapshot, tasks, token audit, and monthly aggregate. Session completion columns are database-guarded against ordinary client writes.

The tasks Function

Actions:

ActionInputBehaviour
listoptional company/status/role/dimension/include-dismissedActive memberships only
updatetask ID and allowlisted status/priority/notesValidates membership and values; stamps completion
dismisstask IDSoft deletes through status='dismissed'

It does not call Gemini or generate tasks. Task generation is an internal step of chat/summary pipelines.

The send-invite Function

The request contains only invitation_id. The function loads and validates:

  • invitation
  • recipient/role/status
  • company
  • inviter
  • caller's current company-admin/consultant access
  • locale
  • trusted application origin

The accept link is built from APP_URL. If APP_URL or RESEND_API_KEY is missing, the function declines delivery rather than accepting request-supplied link data.

The shared-session Function

This is a public capability endpoint:

  • token must map to an unrevoked, unexpired share,
  • response fields are allowlisted,
  • no service-role row dump is returned,
  • trusted-IP rate limiting bounds token guessing,
  • CORS and security response behaviour remain generic.

Do not replace it with a public table SELECT.

The chat-live Function

chat-live is a WebSocket relay to Gemini Live. It:

  • validates the URL token before upgrade,
  • authorizes the session/company,
  • checks budget,
  • injects prompt/history/locale,
  • relays audio and control messages,
  • supports Gemini resumption handles,
  • persists user/assistant transcript turns,
  • runs text-model maturity extraction after assistant turns,
  • records realtime usage.

voice_enabled hides/disables the frontend control but is not currently read by this function.

The prompt-test Function

Modes:

  • advisor test against a draft prompt and isolated conversation
  • AI-assisted prompt refinement

The function is system-admin only. It does not persist a user conversation, but it does insert token audit usage and call record_token_usage.

The mcp Function (remote MCP server)

The mcp function remains the checked-in Streamable HTTP resource server. It serves public discovery/challenge metadata, validates Supabase OAuth tokens containing a client identity for tool calls, scopes reads to active memberships, and restricts company-profile writes to admin/consultant access.

Detailed tools, scopes, consent, discovery and troubleshooting are in MCP and OAuth.

The documents Function

Actions:

ActionResult
statusWhether all R2 secrets are configured
upload_urlFive-minute presigned PUT for one pending document
confirm_uploadHEAD object, enforce measured quotas, atomic commit
download_urlFive-minute presigned GET for a stored company document
delete_objectAdmin/consultant signed delete

Controls include active membership, uploader/admin checks, company retention opt-in, deterministic company-prefixed keys, path validation, type/MIME allowlists, 15 MiB file cap, 500-document cap, and 2 GiB company cap.

See Company Knowledge and Documents.

Guided-demo functions

The gemini-tts Function

Returns base64 PCM16 at 24 kHz for allowed voices/languages. It checks user budget/rate and records usage. The client wraps PCM in WAV or feeds its audio pipeline.

The demo Function

Actions are confined to the authenticated user's deterministic demo ownership namespace. Seed operations use service privileges because maturity fields are pipeline-owned; ownership and company access are checked before every privileged path. Teardown and sweep call SECURITY DEFINER cleanup RPCs.

AI, Budget, and Rate Controls

The shared budget gate is used by:

aida
aida-live
chat
chat-live
chat-multi
competitor-analysis
generate-summary
gemini-tts
lead-hunt
livekit-token
structure-feedback
synthesise-company-info

prompt-test is system-admin-only and records usage but does not use the ordinary tenant budget gate.

Budget enforcement and accounting are separate: every spend must still record usage after it passes a pre-call gate. See AI and Token Accounting.

Gemini Model Configuration

Canonical IDs live in _shared/models.ts:

GEMINI_TEXT_MODEL = 'gemini-3.1-flash-lite'
GEMINI_LIVE_MODEL =
Deno.env.get('GEMINI_LIVE_MODEL') || 'gemini-3.1-flash-live-preview'
GEMINI_LIVE_VOICE_DEFAULT =
Deno.env.get('GEMINI_LIVE_VOICE') || 'Kore'
GEMINI_TTS_MODEL =
Deno.env.get('GEMINI_TTS_MODEL') || 'gemini-3.1-flash-tts-preview'

Text functions use:

import { GoogleGenAI } from "https://esm.sh/@google/genai@2.8.0";

Do not introduce the retired @google/generative-ai client. Import canonical model constants rather than copying IDs into each function.

Deployment

Deploy via CLI

Repository source is authoritative. For every function:

npx supabase functions deploy <name> \
--project-ref bxmwvqnilignrxkitaae \
--no-verify-jwt

Do not deploy through the Supabase MCP tool or a dashboard-only upload. Those paths cannot guarantee the required flag/source synchronization.

Secrets

Common:

GEMINI_API_KEY
APP_URL
RESEND_API_KEY

Optional LiveKit:

LIVEKIT_URL
LIVEKIT_API_KEY
LIVEKIT_API_SECRET

Optional R2:

R2_ACCOUNT_ID
R2_ACCESS_KEY_ID
R2_SECRET_ACCESS_KEY
R2_BUCKET

Supabase provides SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY to functions.

Adding a Function

  1. Create supabase/functions/<slug>/index.ts.
  2. Add [functions.<slug>] verify_jwt = false to config.
  3. Reuse shared HTTP/authz/locale/model helpers.
  4. Define method, body size, response and error contract.
  5. Verify identity or explicitly design/rate-limit a public capability.
  6. Bind every tenant/resource identifier.
  7. Add feature, rate, budget and idempotency controls as needed.
  8. Insert token audit and call record_token_usage for every Gemini spend.
  9. Add local tests/DAST seed coverage.
  10. Deploy with project ref and --no-verify-jwt.
  11. Update this reference.

Troubleshooting

Function works locally but receives hosted gateway 401

Redeploy with --no-verify-jwt.

Browser preflight rejects tracing

Use the exact shared CORS headers including baggage and sentry-trace.

Cross-tenant UUID returns data

Treat as a security defect. Identity verification alone is insufficient when using the service role; add a current company/session/document binding.

AI succeeds but budgets do not change

Confirm both the token_usage insert and record_token_usage RPC execute on all success/partial-success paths.

Model returns prose around JSON

Use _shared/json.ts extraction and bounded retry/failure behaviour. Never eval model output or accept a structurally invalid partial result.