Supabase data model & RLS
The 60+ tables that back Teleperson, organized by concern, all RLS-enforced.
Teleperson's Postgres database has 60+ tables, organized by concern. Every table that holds user-scoped data is protected by row-level security (RLS). A compromised credential can only ever access rows belonging to its own user.
Tables by concern
Identity & access
| Table | Purpose |
|---|---|
users | Profile fields (first_name, age_range, signup_reasons, plan, marketing_opt_in, etc.). Mirrors auth.users 1:1. |
extension_tokens | SHA-256 hashes of tle_… tokens. RLS: user_id = auth.uid(). |
audit_log | Append-only record of every privileged action. |
Companies & catalog
| Table | Purpose |
|---|---|
companies | The shared catalog. RLS: read-all for authed users, write only for admins. |
company_aliases | Multiple domains pointing at one catalog entry. |
company_ai_overviews | Cached Claude outputs (description, top reasons, similar companies). One row per company. |
user_followed_companies | The personal Hub. RLS: user_id = auth.uid(). |
call_flows / call_flow_versions | Verified Call Tree data with versioning. |
Plaid
| Table | Purpose |
|---|---|
user_plaid_connections | One row per linked institution. Holds AES-256-GCM-encrypted access token + IV. |
plaid_accounts | Per-account data (checking, savings, card). |
plaid_transactions | Normalized transactions, mapped to companies via the merchant pipeline. |
merchant_review_queue | Unresolved merchants awaiting admin review. |
Vendor accounts (eBay, Shopify partner stores, etc.)
| Table | Purpose |
|---|---|
user_vendor_connections | One row per vendor connection. Encrypted access + refresh tokens. |
vendor_orders | Projected, redacted order data. Never raw vendor blobs. |
vendor_invoices | Same shape, for invoices. |
vendor_shipments | Tracking + delivery. |
vendor_oauth_states | Short-lived (10-min TTL) PKCE state for OAuth round-trips. |
Voice
| Table | Purpose |
|---|---|
voice_call_log | One row per Twilio Voice or ElevenLabs Concierge session. user_id_hash, to_number_hash, duration_seconds, outcome. |
RLS policies — the standard pattern
Every user-scoped table follows the same shape:
ALTER TABLE user_plaid_connections ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Owners can read"
ON user_plaid_connections FOR SELECT
USING (user_id = auth.uid());
CREATE POLICY "Owners can insert"
ON user_plaid_connections FOR INSERT
WITH CHECK (user_id = auth.uid());For X-TLE-Token-authed paths, Edge Functions resolve the token to a
user_id and SET LOCAL request.jwt.claim.sub = ... so the same RLS
policies apply.
Migrations
Migrations live in supabase/migrations/ and run in lexical order. Naming
convention: YYYYMMDDHHMMSS_short-description.sql (Supabase's default).
Creating a new table that holds user data must include:
- The
user_id uuid REFERENCES auth.userscolumn. ENABLE ROW LEVEL SECURITY.- At minimum a
Owners can readpolicy.
CI lints for tables without RLS — adding one without a policy fails the PR check.
Indexes
Hot-path queries have explicit indexes:
user_followed_companies (user_id)— Hub render.companies (lower(domain))— vendor identification on every panel open.plaid_transactions (user_id, posted_at DESC)— Purchases tab.extension_tokens (token_hash)— every authenticated request.
Related
- Edge Functions → — what runs against this data.
- Vendor accounts security → — the detailed model for vendor data.