API Reference
6 Supabase Edge Functions provide the REST API. All endpoints require a Supabase anon key in the Authorization header.
// All requests require this header:
Authorization: Bearer <SUPABASE_ANON_KEY>
Content-Type: application/json
// Base URL:
https://your-project.supabase.co/functions/v1/Rate Limits & Performance SLAs
Rate limits are per organization ID. Contact support for enterprise limits.
HTTP Status Codes & Error Handling
200400401403429500503Error Response Format
{
"error": {
"code": "INVALID_ORGANIZATION_ID",
"message": "Organization ID 'org_123' not found",
"details": {
"field": "organizationId",
"received": "org_123"
}
}
}Retry Strategy (Recommended)
// Exponential backoff with jitter
async function callWithRetry(endpoint, body, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const res = await fetch(endpoint, {
method: 'POST',
headers: {
'Authorization': `Bearer ${SUPABASE_ANON_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
});
if (res.ok) return await res.json();
// Don't retry client errors (4xx except 429)
if (res.status >= 400 && res.status < 500 && res.status !== 429) {
throw new Error(`Client error: ${res.status}`);
}
// Exponential backoff: 1s, 2s, 4s with jitter
const delay = Math.min(1000 * Math.pow(2, i) + Math.random() * 1000, 10000);
await new Promise(resolve => setTimeout(resolve, delay));
} catch (err) {
if (i === maxRetries - 1) throw err;
}
}
}/functions/v1/brainos-queryAI Worker queries with Brain IQ routing. Classifies complexity, routes to System-0/1/2, returns an answer with confidence score and reasoning trace.
Request Body
{
"query": "Why is churn increasing?",
"organizationId": "org_123",
"domain": "cs" // optional: filter by domain
}Response
{
"answer": "Churn is increasing due to payment delays...",
"reasoningTrace": [...],
"confidence": 0.85,
"sources": [...]
}/functions/v1/brainos-ingestSignal ingestion from any source. Batch up to 500 signals per request. Signals are entity-resolved, embedded, and published to the event bus.
Request Body
{
"organizationId": "org_123",
"signals": [
{
"source_domain": "finance",
"signal_type": "payment_delay",
"signal_value": 0.8,
"entity_id": "client_1",
"signal_timestamp": "2026-01-15T00:00:00Z"
}
]
}Response
{
"ingested": 1,
"entities_resolved": 1
}/functions/v1/brainos-webhookWebhook receiver for real-time ingestion from Stripe, HubSpot, Intercom, and Slack. Supports query parameters for source routing.
Request Body
// Query params: ?source=stripe&org=org_123
// Body: raw webhook payload from the source systemResponse
{
"processed": 3,
"source": "stripe"
}/functions/v1/brainos-copilotAgentic copilot with complexity routing. Simple questions get fast answers. Complex questions trigger a multi-step investigation with tool use and self-correction. Supports SSE streaming.
Request Body
{
"query": "Why did revenue drop and how will it cascade?",
"organizationId": "org_123",
"stream": true // optional: SSE streaming
}Response
// SSE stream with tool use events:
data: {"type": "tool_use", "tool": "query_causal_graph", ...}
data: {"type": "tool_use", "tool": "trace_cascade", ...}
data: {"type": "answer", "content": "Revenue dropped because...", ...}/functions/v1/brainos-cronScheduled jobs: causal discovery, prediction verification, threshold optimization, evidence decay, brain consolidation. Run daily via pg_cron or manual trigger.
Request Body
{
"organizationId": "org_123",
"jobs": ["rl-consolidate", "federated-sync", "anti-library", "sleep-cycle"]
}Response
{
"completed": ["rl-consolidate", "federated-sync", "anti-library", "sleep-cycle"],
"signals_processed": 147,
"edges_strengthened": 23
}/functions/v1/brainos-seed-coreAI Worker brain initialization. Seeds the federated knowledge layer with domain-specific starter patterns. Run once during workspace setup.
Request Body
{
"packs": ["macro-economic", "tech-industry", "business-cases"]
}Response
{
"seeded": true,
"packs_loaded": 3,
"signals_created": 450
}Webhook Reliability & Idempotency
Idempotency Guarantees
The brainos-webhook endpoint is idempotent — duplicate webhook deliveries are automatically deduplicated using event IDs. Safe to retry without creating duplicate signals.
Event Deduplication
- Stripe: Uses
event.idfor deduplication (24-hour window) - HubSpot: Uses
objectId + timestampcombination - Intercom: Uses
conversation.id + updated_at - Slack: Uses
event_ts + channelcombination
Webhook Verification
All webhook sources are cryptographically verified before processing:
- Stripe: HMAC-SHA256 signature verification using webhook secret
- HubSpot: X-HubSpot-Signature v3 validation
- Intercom: X-Hub-Signature HMAC verification
- Slack: Request signature validation with timestamp check
Webhook Retry Configuration (Source Systems)
// Stripe automatic retry schedule:
// - Immediately
// - 1 hour later
// - 3 hours later (if still failing)
// - Stops after 3 days
// HubSpot retry behavior:
// - Up to 10 retry attempts
// - Exponential backoff (1min → 10min → 1hr)
// Best Practice: Always return 200 OK quickly
// Process webhook asynchronously to avoid timeoutsImplementing Webhook Receiver (Self-Hosted)
import { createClient } from '@supabase/supabase-js';
import crypto from 'crypto';
export async function POST(req: Request) {
const signature = req.headers.get('stripe-signature');
const body = await req.text();
// 1. Verify webhook signature
const isValid = crypto
.createHmac('sha256', process.env.STRIPE_WEBHOOK_SECRET!)
.update(body)
.digest('hex') === signature;
if (!isValid) {
return new Response('Invalid signature', { status: 401 });
}
const event = JSON.parse(body);
// 2. Check for duplicate using event ID (idempotency)
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_ANON_KEY!
);
const { data: existing } = await supabase
.from('webhook_events')
.select('id')
.eq('external_event_id', event.id)
.single();
if (existing) {
console.log('Duplicate webhook ignored:', event.id);
return new Response('OK', { status: 200 }); // Return 200 to prevent retry
}
// 3. Store event to prevent future duplicates
await supabase.from('webhook_events').insert({
external_event_id: event.id,
source: 'stripe',
event_type: event.type,
received_at: new Date().toISOString()
});
// 4. Process webhook asynchronously (queue recommended)
await fetch(`${process.env.SUPABASE_URL}/functions/v1/brainos-webhook`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SUPABASE_ANON_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
source: 'stripe',
organizationId: 'org_123',
event: event
})
});
// 5. Always return 200 OK quickly (< 5 seconds)
return new Response('OK', { status: 200 });
}- Stripe requires response within 5 seconds
- HubSpot requires response within 30 seconds
- Always return 200 OK immediately, process asynchronously
- Use job queues (BullMQ, Inngest) for heavy processing