Teleperson
Architecture

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

TablePurpose
usersProfile fields (first_name, age_range, signup_reasons, plan, marketing_opt_in, etc.). Mirrors auth.users 1:1.
extension_tokensSHA-256 hashes of tle_… tokens. RLS: user_id = auth.uid().
audit_logAppend-only record of every privileged action.

Companies & catalog

TablePurpose
companiesThe shared catalog. RLS: read-all for authed users, write only for admins.
company_aliasesMultiple domains pointing at one catalog entry.
company_ai_overviewsCached Claude outputs (description, top reasons, similar companies). One row per company.
user_followed_companiesThe personal Hub. RLS: user_id = auth.uid().
call_flows / call_flow_versionsVerified Call Tree data with versioning.

Plaid

TablePurpose
user_plaid_connectionsOne row per linked institution. Holds AES-256-GCM-encrypted access token + IV.
plaid_accountsPer-account data (checking, savings, card).
plaid_transactionsNormalized transactions, mapped to companies via the merchant pipeline.
merchant_review_queueUnresolved merchants awaiting admin review.

Vendor accounts (eBay, Shopify partner stores, etc.)

TablePurpose
user_vendor_connectionsOne row per vendor connection. Encrypted access + refresh tokens.
vendor_ordersProjected, redacted order data. Never raw vendor blobs.
vendor_invoicesSame shape, for invoices.
vendor_shipmentsTracking + delivery.
vendor_oauth_statesShort-lived (10-min TTL) PKCE state for OAuth round-trips.

Voice

TablePurpose
voice_call_logOne 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:

  1. The user_id uuid REFERENCES auth.users column.
  2. ENABLE ROW LEVEL SECURITY.
  3. At minimum a Owners can read policy.

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.

On this page