API Reference

6 Supabase Edge Functions provide the REST API. All endpoints require a Supabase anon key in the Authorization header.

authenticationbash
// 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

Endpoint
Rate Limit
Typical Latency
brainos-query
100 req/min
800-1200ms (w/ causal search)
brainos-ingest
500 req/min (max 500 signals/batch)
150-300ms per batch
brainos-webhook
1000 req/min
50-150ms
brainos-copilot
50 req/min (complex queries)
2-5s (streaming)
brainos-cron
10 req/hour
5-30s (depends on data volume)
brainos-seed-core
1 req/day
1-3s

Rate limits are per organization ID. Contact support for enterprise limits.

HTTP Status Codes & Error Handling

200
Success
Request completed successfully
400
Bad Request
Invalid request body, missing required fields, or malformed JSON
401
Unauthorized
Missing or invalid Authorization header (check SUPABASE_ANON_KEY)
403
Forbidden
Valid auth but insufficient permissions for the organization
429
Rate Limited
Too many requests. Retry after delay (see Retry-After header)
500
Internal Server Error
Unexpected error. Check logs and contact support if persistent
503
Service Unavailable
Temporary outage or maintenance. Retry with exponential backoff

Error Response Format

{
  "error": {
    "code": "INVALID_ORGANIZATION_ID",
    "message": "Organization ID 'org_123' not found",
    "details": {
      "field": "organizationId",
      "received": "org_123"
    }
  }
}

Retry Strategy (Recommended)

retry-strategy.tstypescript
// 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;
    }
  }
}
POST/functions/v1/brainos-query

AI 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": [...]
}
POST/functions/v1/brainos-ingest

Signal 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
}
POST/functions/v1/brainos-webhook

Webhook 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 system

Response

{
  "processed": 3,
  "source": "stripe"
}
POST/functions/v1/brainos-copilot

Agentic 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...", ...}
POST/functions/v1/brainos-cron

Scheduled 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
}
POST/functions/v1/brainos-seed-core

AI 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.id for deduplication (24-hour window)
  • HubSpot: Uses objectId + timestamp combination
  • Intercom: Uses conversation.id + updated_at
  • Slack: Uses event_ts + channel combination

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 timeouts

Implementing Webhook Receiver (Self-Hosted)

webhook-receiver.tstypescript
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 });
}
⚠️ Important: Webhook Timeout Requirements
  • 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