Connect your database

Create a read-only Postgres role for ParityRail, plus an optional narrow repair role.

ParityRail reads your app’s own record of who has access — usually your users table — so it can compare that to what Stripe says. Give it a Postgres login that can only read.

Before you start

  • A Postgres (or Supabase) database reachable from ParityRail’s network — allowlist or firewall opened, and TLS reachable.
  • Permission to run CREATE ROLE and GRANT — a superuser connection, or the Supabase SQL editor. You will not hand this powerful credential to ParityRail.
  • The database name and host:port, for building the connection string.
  • Admin role on the ParityRail project — connecting a database requires admin.
  • Optional: to enable repairs, the ability to create a second, column-scoped role.

Connect your database

  1. Run the read-only role SQL against your database. Replace the password placeholder with a strong secret, and adjust the granted tables to the ones ParityRail will read — your users table, plus organizations and members if you bill teams.
    CREATE ROLE parityrail_readonly WITH LOGIN PASSWORD '...';
    
    GRANT CONNECT ON DATABASE app_db TO parityrail_readonly;
    GRANT USAGE ON SCHEMA public TO parityrail_readonly;
    GRANT SELECT ON public.users TO parityrail_readonly;
    GRANT SELECT ON public.organizations TO parityrail_readonly;
    GRANT SELECT ON public.organization_members TO parityrail_readonly;
  2. Assemble a read-only connection string for that role:
    postgres://parityrail_readonly:<password>@<your-db-host>:5432/<your-db-name>
  3. In the ParityRail onboarding wizard, go to step 3 (Database) and stay on the “Postgres / Supabase” tab. Paste the string into the “Read-only connection string” field. The collapsible “How to create a read-only role” holds the same SQL with a copy button, if you need it again.
  4. Optional — to allow repairs, run the repair-role SQL:
    CREATE ROLE parityrail_repair WITH LOGIN PASSWORD '...';
    
    GRANT UPDATE (plan, is_premium, trial_ends_at)
    ON public.users
    TO parityrail_repair;
    Then assemble its connection string and paste it into the “Repair connection string (optional)” field:
    postgres://parityrail_repair:<password>@<your-db-host>:5432/<your-db-name>
    Leave this field empty to run detect-only.
  5. Click “Test connection”. The button reads “Testing connection…” while ParityRail runs a SELECT 1 against the read-only URL, and against the repair URL too if you provided one.
Supabase: either the pooled port 6543 or the direct port 5432works, because ParityRail opens short-lived connections. Don’t reuse the anon or service_role roles.

Verify it worked

On success, a toast reads “Database connected (<host>/<database>)” and the form is replaced by a green “Database connected” card showing <host> / <database>, plus · repair role configured when you supplied a repair string. Continue becomes enabled.

This step doesn’t display a table count or a list of detected tables — the only success signal here is the green connected card with host and database. Table introspection (and any “no tables found” feedback) happens later, on the Identity mapping (step 4) and Access mapping (step 6) steps, once the schema dropdowns populate.

Troubleshooting

SymptomFix
Connection refused — check the host/port and that the database accepts remote connections.Nothing is listening at that host:port, or remote connections are blocked (Postgres ECONNREFUSED).
Connection timed out after 8 seconds — check network access, firewall rules and that the database is reachable from ParityRail.A firewall or allowlist is dropping the packet (ETIMEDOUT). Allowlist ParityRail's egress.
Authentication failed — check the database username and password in your connection string.Wrong role name or password in the string (Postgres 28P01).
Database host not found — check the hostname in your connection string.Typo in the host (ENOTFOUND).
Database not found — check the database name in your connection string.Wrong database name after the last slash (Postgres 3D000).
TLS certificate problem — try sslmode=require or supply the CA certificate.Append ?sslmode=require to the connection string — common on self-signed or managed Postgres.
Connection rejected — the database role is not allowed to connect from here (pg_hba.conf).Add a pg_hba rule for the role/host (Postgres 28000).
Test succeeds, but a later mapping step shows No tables found — check the read-only role has USAGE + SELECT grants.The role can connect but lacks USAGE on the schema or SELECT on the tables — re-run the GRANT lines above. On the demo database, run pnpm seed first.
Connect fails only after you add a repair stringThe repair URL is tested too, so a bad repair credential fails the whole connect. Fix it, or clear the repair field to stay detect-only.
Invalid connection string, e.g. expected postgres://user:password@host:port/database or must start with postgres:// or postgresql://The pasted string isn't a valid postgres URL — check for missing pieces or stray whitespace.

Optional: a repair role

Repairs are opt-in — leave the repair field empty above to stay detect-only. ParityRail will still flag access incidents and suggest fixes; it just won’t write anything.

Even with the repair role connected, ParityRail only ever writes columns that appear in your mappings. The repair role limits what’s possible at the database level; your mappings limit it further.

What ParityRail stores, and what it never stores

ParityRail keeps only what it needs to detect access incidents and keep the Access Ledger — a running record of each customer’s billing promises and access history. It never keeps a copy of your database.

  • Stored: the connection string, encrypted, and small snapshots of just the columns you mapped — plan, premium flag, trial, entitlements. These give any access incident evidence to show you.
  • Never stored: full table copies or bulk exports, plaintext credentials or passwords, or any payment method or card data.