Skip to main content
The typed /api/* surface gives you opinionated, RPC-style access to the AI features. Underneath, all the state those endpoints manage lives in a single Postgres schema — ai — and PostgREST exposes that schema as a standard REST CRUD surface at /rest/v1/*. That means everything you can do with /api/agents, /api/sessions, /api/runs, /api/knowledge-bases, etc., you can also do directly against the underlying tables. SQL-style filters, joins, JSONB selectors, pagination via Content-Range, embeds — all the PostgREST machinery that powers public.* access works for ai.* too. This is the right tool when:
  • You need filters or joins the typed endpoint doesn’t expose (e.g., “all runs from the last 7 days where the agent’s system_prompt contains ‘refund’”).
  • You’re building a dashboard and want one query for usage analytics instead of N round-trips.
  • You’re bulk-tagging or migrating sources and don’t want to call PATCH /api/sources/{id} in a loop.
  • You want Realtime subscriptions on a state change the typed API doesn’t broadcast.
It’s not the right tool for state changes the platform’s own services manage (run lifecycle, indexing dispatch, workflow execution). Those endpoints aren’t just “a CRUD with extra steps” — they coordinate Celery tasks, RAG pipelines, and external LLM calls. Use the typed /api/* for state-changing operations on dynamic state; use PostgREST for reads and for writes on static, user-owned configuration.

How ai.* is exposed

PostgREST is configured to serve four schemas: public, storage, graphql_public, ai. The ai schema sits alongside your own public schema as a fully-routed PostgREST surface. Every table follows the standard PostgREST URL pattern:
GET    /rest/v1/{table}                # list
GET    /rest/v1/{table}?id=eq.{id}     # filter
POST   /rest/v1/{table}                # insert
PATCH  /rest/v1/{table}?id=eq.{id}     # update
DELETE /rest/v1/{table}?id=eq.{id}     # delete
To target the ai schema instead of public, add the Accept-Profile (read) or Content-Profile (write) header:
curl '{BASE_URL}/rest/v1/agent_runs?limit=10' \
  -H "Accept-Profile: ai" \
  -H "apikey: {API_KEY}" \
  -H "Authorization: Bearer {API_KEY}"
Without Accept-Profile, PostgREST looks in the default public schema and 404s.

Tables in the ai schema

Every state-bearing entity in the AI surface has a corresponding table. The full list (35 tables):
DomainTables
Sourcessources, indexed_sources
Knowledge basesknowledge_bases, chunks, embeddings, enrichment_configs
Strategy-specific item tablespage_index_toc, page_index_nodes, full_documents, doc2json_documents, graph_index_toc, graph_index_nodes
Agentsagents, agent_sessions, agent_runs, agent_tools, agent_knowledge_bases, agent_mcp_servers, hooks
Orchestrationsorchestrations, orchestration_entities, orchestration_sessions, orchestration_runs
Workflowsworkflows, workflow_blocks, workflow_edges, workflow_executions, workflow_block_logs
Toolstools
Contextcontext_handlers, tool_call_events, message_citations
Copilotcopilot_sessions, copilot_messages
Settingsproject_settings, ai_provider_keys
Schemas can grow; consult GET /rest/v1/ with Accept-Profile: ai for the live OpenAPI spec PostgREST emits.

RLS posture

Every ai.* table has Row Level Security enabled. The default policies are:
  • service_role — full access on every table (FOR ALL USING (true) WITH CHECK (true)). This is what the platform’s own backend uses.
  • authenticated (any signed-in GoTrue user) — read access on every table. Write access on most user-configurable tables (agents, workflows, tools, knowledge_bases, sources, etc.). Per-user filtering on session-shaped tables (agent_sessions, agent_runs, orchestration_sessions, orchestration_runs) via auth.uid() = user_id.
  • anonno access. Querying ai.* with the Anon (Publishable) Key returns empty result sets.
What this means in practice:
CallerReadsWrites
Service Role (Secret) KeyEverythingEverything
Signed-in user JWT (authenticated)Everything in the projectMost config tables, scoped runs/sessions
Anon (Publishable) KeyNothingNothing
Multi-user projects: authenticated is project-wide, not per-user.The default policies grant authenticated blanket read access to every record in the project, including other users’ agents, workflows, and knowledge bases. Per-user scoping only applies to agent_sessions, agent_runs, orchestration_sessions, and orchestration_runs (where the user_id column lets auth.uid() filter).If your project has multiple end-users and you don’t want them seeing each other’s data, you must either (a) tighten the default policies, (b) gate ai.* reads behind your own backend with the Service Role key, or (c) only let end users hit /api/* (the typed surface enforces ownership where it matters).
See the RLS Model page for how auth.uid(), auth.jwt(), and the role mapping work, and the RLS Cookbook for concrete tightening patterns.

When to use PostgREST vs /api/*

You want to…Use…
Create an agent / workflow / KBPOST /api/{resource}
Run an agentPOST /api/agents/{id}/run or /run/stream
List/filter/sort/paginate by SQL-style criteriaGET /rest/v1/{table}
Bulk-update tags or namesPATCH /rest/v1/{table} with a filter
Join across resources in one requestGET /rest/v1/{table}?select=*,relation(*)
Aggregate (count, sum, group by)RPC or Range-Unit headers via PostgREST
Subscribe to row changesRealtime postgres_changes on ai.*
Delete a source from a KBDELETE /api/knowledge-bases/{id}/sources/{indexed_source_id} (NOT direct PostgREST — cascades through 8 tables and revokes Celery tasks)
Cancel/cleanup mid-flight indexingPOST /api/knowledge-bases/{id}/sources/{indexed_source_id}/cancel (typed endpoint handles Celery revoke)
Anything where the platform runs side-effects (Celery dispatch, LLM calls, file processing) belongs on the typed surface. Everything read-only or static-config belongs anywhere.

Next steps

ai-schema recipes

Four worked examples: hybrid search on chunks, usage analytics on runs, bulk source tagging, cross-KB joins.

RLS Model

How roles, JWT claims, and auth.uid() compose to gate ai.* and public.* access.

PostgREST Reference

Filter operators, embeds, headers, error responses.

Database Access

The three ways into your project’s Postgres — typed API, PostgREST, direct connection.