/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_promptcontains ‘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.
/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:
ai schema instead of public, add the Accept-Profile (read) or Content-Profile (write) header:
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):| Domain | Tables |
|---|---|
| Sources | sources, indexed_sources |
| Knowledge bases | knowledge_bases, chunks, embeddings, enrichment_configs |
| Strategy-specific item tables | page_index_toc, page_index_nodes, full_documents, doc2json_documents, graph_index_toc, graph_index_nodes |
| Agents | agents, agent_sessions, agent_runs, agent_tools, agent_knowledge_bases, agent_mcp_servers, hooks |
| Orchestrations | orchestrations, orchestration_entities, orchestration_sessions, orchestration_runs |
| Workflows | workflows, workflow_blocks, workflow_edges, workflow_executions, workflow_block_logs |
| Tools | tools |
| Context | context_handlers, tool_call_events, message_citations |
| Copilot | copilot_sessions, copilot_messages |
| Settings | project_settings, ai_provider_keys |
GET /rest/v1/ with Accept-Profile: ai for the live OpenAPI spec PostgREST emits.
RLS posture
Everyai.* 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) viaauth.uid() = user_id.anon— no access. Queryingai.*with the Anon (Publishable) Key returns empty result sets.
| Caller | Reads | Writes |
|---|---|---|
| Service Role (Secret) Key | Everything | Everything |
| Signed-in user JWT (authenticated) | Everything in the project | Most config tables, scoped runs/sessions |
| Anon (Publishable) Key | Nothing | Nothing |
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 / KB | POST /api/{resource} |
| Run an agent | POST /api/agents/{id}/run or /run/stream |
| List/filter/sort/paginate by SQL-style criteria | GET /rest/v1/{table} |
| Bulk-update tags or names | PATCH /rest/v1/{table} with a filter |
| Join across resources in one request | GET /rest/v1/{table}?select=*,relation(*) |
| Aggregate (count, sum, group by) | RPC or Range-Unit headers via PostgREST |
| Subscribe to row changes | Realtime postgres_changes on ai.* |
| Delete a source from a KB | DELETE /api/knowledge-bases/{id}/sources/{indexed_source_id} (NOT direct PostgREST — cascades through 8 tables and revokes Celery tasks) |
| Cancel/cleanup mid-flight indexing | POST /api/knowledge-bases/{id}/sources/{indexed_source_id}/cancel (typed endpoint handles Celery revoke) |
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.