Database Replica

:::tip Enterprise Feature Access to a fully isolated, daily-updated database replica is available for enterprise clients. This is not included with standard API access.

The replica enables you to fully ingest and customize data for your own use cases. It is powered by logical replication through NeonDB Postgres.

Contact sales@midpage.ai to learn more about enterprise access. :::

Connect to the Midpage read replica using PostgreSQL.

Connection String

postgresql://user:password@host:port/database

Contact us to obtain your connection credentials.

Connecting with psql

psql "postgresql://user:password@host:port/database"

Connecting with Python

import psycopg2

conn = psycopg2.connect(
    host="host",
    port="port",
    database="database",
    user="user",
    password="password"
)

cursor = conn.cursor()
cursor.execute("SELECT * FROM opinions.courts LIMIT 10")
rows = cursor.fetchall()

Connecting with Node.js

import { Pool } from 'pg'

const pool = new Pool({
  connectionString: process.env.DATABASE_URL
})

const result = await pool.query('SELECT * FROM opinions.courts LIMIT 10')
console.log(result.rows)

Schema Access

The database contains two schemas:

  • opinions — Court opinions and caselaw
  • laws — Statutes and regulations
-- List all tables in opinions schema
SELECT table_name 
FROM information_schema.tables 
WHERE table_schema = 'opinions';

-- List all tables in laws schema
SELECT table_name 
FROM information_schema.tables 
WHERE table_schema = 'laws';

See the Schema Reference for complete table documentation.

Change Tracking

All tables include an updated_at column with automatic triggers. This timestamp is updated whenever a row is modified, helping you track changes made on our end.

-- Find recently updated docket entries
SELECT id, description, updated_at
FROM cases.docket_entries
WHERE updated_at > NOW() - INTERVAL '7 days'
ORDER BY updated_at DESC;

This is useful for incremental syncs—you can query for rows where updated_at is greater than your last sync timestamp.

One case updated_at does not cover

Incremental sync on updated_at catches inserts, edits and deletions — but not withdrawals.

  • A deletion sets deleted_at and bumps updated_at. Your next incremental query sees the row and its tombstone, and you can mark it deleted on your side.
  • A withdrawal — where a case or document stops being part of the published corpus, for example because it was removed at a court's request or found to be a duplicate — removes the row outright. It leaves no tombstone, and because the row is gone there is nothing for an updated_at query to return.

So a sync built only on updated_at will accumulate rows that are no longer in our corpus. Withdrawals are uncommon, but they are not rare enough to ignore: plan on a periodic full-key reconciliation — pull the complete set of IDs for a table and drop anything on your side that is no longer present.

-- Reconciliation key set: every live case we currently publish
SELECT id FROM cases.cases WHERE deleted_at IS NULL;

Monthly is enough for most consumers. If you need withdrawals reflected faster than that, talk to us — we can tell you which tables actually move.