> ## Documentation Index
> Fetch the complete documentation index at: https://deepline.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# PostgreSQL Database

> Every Deepline workspace gets a dedicated Neon PostgreSQL project. Covers direct SQL access, schemas, roles, exports, and app integrations.

Every Deepline workspace gets its own dedicated Neon PostgreSQL project. Not a shared multi-tenant database. Not a proprietary data store you need a vendor SDK to query. A real PostgreSQL database with your own schemas, your own roles, and direct SQL access from any client that speaks the Postgres wire protocol.

Every GTM team eventually asks: "Where does my data actually live, and can I get it out?"

Yes. It lives in PostgreSQL. You can get it out with `psql`, DBeaver, Metabase, your ORM, a `pg` driver, or a `COPY TO` command. There is no export queue, no CSV-only download button, no 10,000-row limit.

| Attribute         | Value                                                      |
| ----------------- | ---------------------------------------------------------- |
| Database engine   | PostgreSQL (Neon serverless)                               |
| Isolation         | One dedicated Neon project per tenant                      |
| Schemas           | Managed schemas + agent-writable `storage` schema          |
| Database roles    | Workspace-scoped customer DB and internal runtime roles    |
| Schema migrations | Rolling migrations supported                               |
| Direct SQL access | Any PostgreSQL client (psql, DBeaver, Metabase, pg driver) |
| Data export       | Unrestricted — `pg_dump`, `COPY TO`, direct query          |
| Data sovereignty  | Your data stays in your schema                             |

***

## What an included database means for your GTM data

Most enrichment tools store your data in their cloud and give you a UI to look at it. If you want to do anything real with that data — join it to your CRM, run custom reports, build internal tools, deduplicate across providers — you are either exporting CSVs or paying for an API that rate-limits you against your own records.

Deepline takes a different approach. Your enrichment results, identity graph, manual overrides, and resolved views all live in a PostgreSQL database that you can connect to directly. Here is when that matters and when it does not.

<CardGroup cols={2}>
  <Card title="Data sovereignty" icon="shield-check">
    Your enrichment results live in your schema, not in a vendor's multi-tenant
    database. Query, export, back up, or delete your data at any time with
    standard PostgreSQL tools. No vendor lock-in. No export fees. No "please
    contact sales to download your records." If you leave Deepline, your data is
    a `pg_dump` away.
  </Card>

  <Card title="Custom reporting" icon="chart-mixed">
    Connect Metabase, Looker, Mode, Grafana, or any BI tool directly to your
    database. Write SQL to answer questions the vendor UI never anticipated:
    "Which companies in our ICP had job postings in the last 30 days but no
    enriched contact?" "What is our email verification rate by provider over
    time?" Full `SELECT` access to resolved views -- no API pagination, no row
    limits.
  </Card>

  <Card title="Identity resolution" icon="diagram-project">
    Enrich the same person through Apollo, Hunter, and People Data Labs and you
    get three separate responses with overlapping but inconsistent data. The
    identity graph (`dl_graph`) links enrichment events to resolved entities,
    deduplicates across providers, and produces a single coalesced record in
    `dl_resolved`. Query the resolved view and get the best available data
    without writing merge logic.
  </Card>

  <Card title="Not relevant for ephemeral lookups" icon="forward">
    If you run a single enrichment and pipe the result straight into a CRM or
    spreadsheet, you may never touch the database directly. The CLI and API
    return results inline. The database matters when you build on top of
    enrichment data over time -- running waterfalls, deduplicating contacts,
    tracking history, or building internal tools.
  </Card>
</CardGroup>

***

## Agent access with `query_customer_db`

Agents normally access the workspace database through `query_customer_db`, not
by holding an owner connection string. The tool accepts one SQL statement and
returns structured rows.

Use it for reads:

```bash theme={null}
deepline tools execute query_customer_db --payload '{
  "sql": "SELECT domain, company_name FROM enrichments.companies ORDER BY updated_at DESC LIMIT 20"
}'
```

Use the `storage` schema for agent-created tables and scratch state:

```bash theme={null}
deepline tools execute query_customer_db --payload '{
  "sql": "CREATE TABLE IF NOT EXISTS storage.agent_notes (id text PRIMARY KEY, note text NOT NULL, created_at timestamptz NOT NULL DEFAULT now())"
}'
```

`query_customer_db` can run `SELECT`, `EXPLAIN`, read-only `WITH`, and
storage-scoped writes. Write statements must name the target as
`storage.<table>` or `"storage"."<table>"`. Unqualified DDL such as
`CREATE TABLE agent_notes (...)` is blocked.

## Schema architecture

Each tenant database contains managed schemas plus an agent-writable `storage`
schema. The schemas are designed around a clear data flow: raw enrichment
results land in managed storage, identity resolution links records to resolved
entities, coalesced views surface read models, and platform metadata tracks
schema versioning and tenant configuration.

### The 5 managed schemas

<AccordionGroup>
  <Accordion title="dl_cache — Enrichment cache">
    Stores raw enrichment events from every provider call. Each row represents a single enrichment response keyed by a unique source identifier (provider + operation + input hash). The `doc` column holds the full JSON response from the upstream provider. The `extracted_potential_identifier_keys` column contains parsed identifiers (emails, LinkedIn URLs, domains) used by the identity graph for entity linking.

    **Key table:** `dl_cache.enrichment_event`

    This schema is write-only from the application perspective. The platform runtime role writes enrichment results here; tenant roles have no direct write access. You can read cached enrichment data through the `dl_resolved` views, which merge cache and override data into a single coalesced record.

    **Why it matters:** Every enrichment call you have ever made is stored here. You never re-pay for data you already have. The cache also powers waterfall logic — if Provider A already returned an email for this person, Provider B is only called if the cached result is stale or incomplete.
  </Accordion>

  <Accordion title="dl_override — Manual overrides">
    Stores human-entered corrections and custom enrichment events. When a user corrects an email, updates a job title, or tombstones a stale record, the change lands here. Override records take precedence over cached enrichment data when the `dl_resolved` views are queried.

    **Key table:** `dl_override.custom_enrichment_event`

    This schema is platform-managed. Do not write to it through
    `query_customer_db`; use the supported product/API workflow for corrections
    when one is available.

    **Why it matters:** Enrichment data is never perfect. Overrides give you a clean separation between "what the provider said" and "what we know is actually correct," without mutating the original cached data.
  </Accordion>

  <Accordion title="dl_graph — Identity graph">
    The core of cross-provider deduplication. This schema contains three interconnected tables:

    * **`dl_graph.entities`** — Resolved person and company entities. Each entity has a type (`person` or `company`) and an optional parent link (person → company).
    * **`dl_graph.adoptions`** — Links enrichment event rows (from `dl_cache` or `dl_override`) to their resolved entity. Tracks confidence scores and the reasoning behind each adoption.
    * **`dl_graph.identifier_memberships`** — Maps parsed identifiers (email addresses, LinkedIn URLs, company domains, phone numbers) to entities. This is the join table that enables "find all enrichment results for this person across all providers."

    **Why it matters:** When you enrich the same VP of Sales through three different providers, you get three separate JSON blobs. The identity graph resolves them into a single entity, links all three enrichment events to that entity, and makes the merged result available through `dl_resolved`. Without the graph, you would be writing custom deduplication logic for every provider combination.
  </Accordion>

  <Accordion title="dl_resolved — Coalesced views">
    Read-only views that merge `dl_cache` enrichment events, `dl_override` corrections, and `dl_graph` entity resolution into a single queryable surface. This is the schema you query 90% of the time.

    **Key views:**

    * `dl_resolved.people` — One row per resolved person entity, with the best available data from all linked enrichment events and any override patches applied.
    * `dl_resolved.companies` — Same for companies.
    * `dl_resolved.coalesced_enrichment_event` — Lower-level view showing individual enrichment events with override patches applied.

    **Why it matters:** You never have to write merge logic. Query `dl_resolved.people` and you get a single record per person with the best email, most recent job title, and all linked identifiers — regardless of which provider originally returned each field.
  </Accordion>

  <Accordion title="dl_meta — Migrations and settings">
    Tracks schema version and tenant-specific configuration. The platform uses this to manage rolling migrations across tenant databases.

    **Key tables:**

    * `dl_meta.schema_migrations` — Ordered list of applied migrations with timestamps. Used by the migration runner to determine which migrations need to run on each tenant.
    * `dl_meta.tenant_settings` — Key-value store for tenant-specific configuration (e.g., which schema object names are active after a migration).

    **Why it matters:** Rolling migrations mean your database schema evolves without downtime. The migration runner checks `dl_meta.schema_migrations`, applies any pending migrations in order, and updates `dl_meta.tenant_settings` with the new configuration. You do not need to manage migrations yourself — this is platform-managed.
  </Accordion>
</AccordionGroup>

### The agent-writable schema

In addition to managed schemas, every tenant database includes a `storage`
schema. The customer DB role can create, alter, insert, update, delete, and drop
objects in this schema when statements schema-qualify the target. Use it for
agent-created tables, scratch state, workflow notes, and small application
objects that live alongside enrichment data.

```sql theme={null}
CREATE TABLE IF NOT EXISTS storage.account_segments (
  id text PRIMARY KEY,
  segment text NOT NULL,
  score numeric,
  created_at timestamptz NOT NULL DEFAULT now()
);
```

***

## Database roles

Each tenant database has carefully scoped permissions. You never connect as the
owner role; it is used only during provisioning and migrations.

| Role                                | Purpose              | Permissions                                                                                               |
| ----------------------------------- | -------------------- | --------------------------------------------------------------------------------------------------------- |
| **Owner**                           | Provisioning and DDL | Full access to all schemas. Used by the platform during bootstrap and migrations. Never exposed to users. |
| **Customer DB**                     | Agent and app SQL    | Read access to permitted workspace schemas, plus DDL and DML on schema-qualified `storage.*` tables.      |
| **Runtime** (`dl_platform_runtime`) | Platform operations  | Internal role for enrichment writes, identity graph updates, and managed runtime work.                    |

<Tip>
  **Which path should I use?** Start with `query_customer_db` for agent-driven
  reads and `storage`-scoped writes. Use direct connection URIs only when an app
  or BI tool needs a PostgreSQL connection string.
</Tip>

***

## How to access your database

### 1. Check workspace status

```bash theme={null}
curl -s https://code.deepline.com/api/v2/ingestion/status \
  -H "Authorization: Bearer $DEEPLINE_API_KEY" | jq .
```

If not provisioned, provision first from the dashboard Database Access flow (workspace admin session required):

```bash theme={null}
POST /api/v2/ingestion/provision
```

### 2. Get a connection URI

```bash theme={null}
POST /api/v2/ingestion/connection-uris/reveal
{
  "kind": "customer_db",
  "pooled": true
}
```

The customer DB URI can read permitted workspace schemas and write to
schema-qualified `storage.*` tables.

<Tip>
  `GET /api/v2/ingestion/status` supports API key auth. Provisioning and
  credential reveal/rotation flows are currently intended for the
  dashboard/admin session.
</Tip>

### 3. Connect with any PostgreSQL client

<CodeGroup>
  ```bash psql theme={null}
  psql "postgresql://dl_ingest_customer_db:****@ep-xxxxx.us-east-2.aws.neon.tech/neondb?sslmode=require"
  ```

  ```sql Query resolved people theme={null}
  SELECT *
  FROM dl_resolved.people
  ORDER BY updated_at DESC
  LIMIT 20;
  ```

  ```python Python (psycopg2) theme={null}
  import psycopg2

  conn = psycopg2.connect(os.environ["DEEPLINE_DB_URI"])
  cur = conn.cursor()
  cur.execute("""
      SELECT entity_id, doc->>'full_name', doc->>'email'
      FROM dl_resolved.people
      WHERE doc->>'company_domain' = 'stripe.com'
      ORDER BY updated_at DESC
  """)
  for row in cur.fetchall():
      print(row)
  ```

  ```typescript TypeScript (pg) theme={null}
  import { Client } from 'pg';

  const client = new Client({ connectionString: process.env.DEEPLINE_DB_URI });
  await client.connect();

  const { rows } = await client.query(
    `
    SELECT entity_id, doc->>'full_name' as name, doc->>'email' as email
    FROM dl_resolved.people
    WHERE doc->>'company_domain' = $1
    ORDER BY updated_at DESC
    LIMIT 50
  `,
    ['stripe.com'],
  );

  console.table(rows);
  ```

  ```bash DBeaver / Metabase / Looker theme={null}
  # Connection settings:
  # Host:     ep-xxxxx.us-east-2.aws.neon.tech
  # Port:     5432
  # Database: neondb
  # Username: dl_ingest_customer_db
  # Password: (from connection URI reveal)
  # SSL:      Required
  ```
</CodeGroup>

### 4. Export your data

Full database export:

```bash theme={null}
pg_dump "postgresql://dl_ingest_customer_db:****@ep-xxxxx.us-east-2.aws.neon.tech/neondb?sslmode=require" \
  --schema=dl_resolved \
  --no-owner \
  -f deepline-export.sql
```

Export to CSV:

```bash theme={null}
psql "$DEEPLINE_DB_URI" -c "\copy (SELECT * FROM dl_resolved.people) TO 'people.csv' WITH CSV HEADER"
```

***

## Rolling migrations

Deepline uses rolling migrations to evolve your database schema without downtime. Each migration is versioned, ordered, and idempotent. The migration runner:

1. Reads `dl_meta.schema_migrations` to determine the current schema version
2. Applies any pending migrations in order
3. Updates `dl_meta.tenant_settings` with new configuration (e.g., renamed tables/views)
4. Verifies the migration with SQL checks

You do not need to run migrations yourself. The platform manages migration rollout across all tenant databases. Migrations are designed to be backward-compatible during the rollout window — your queries against `dl_resolved` continue to work while migrations are in progress.

***

## Frequently asked questions

<AccordionGroup>
  <Accordion title="Can I connect a BI tool like Metabase or Looker directly?">
    Yes. Request a `customer_db` connection URI and configure your BI tool with the host, port, database, username, and password from the URI. SSL is required. Use resolved or enrichment views as the primary query surface for reporting.
  </Accordion>

  <Accordion title="Is my data shared with other tenants?">
    No. Each workspace gets a dedicated Neon project. Your data is physically
    isolated — separate compute, separate storage, separate connection endpoints.
    There is no row-level security or shared-schema multi-tenancy.
  </Accordion>

  <Accordion title="Can I run my own DDL (CREATE TABLE, ALTER TABLE)?">
    Yes, through `query_customer_db` or the customer DB role when every write
    target is schema-qualified under `storage`. For example: `CREATE TABLE IF NOT
          EXISTS storage.agent_notes (...)`. Unqualified DDL such as `CREATE TABLE
          agent_notes (...)` is blocked, as are writes to managed schemas.
  </Accordion>

  <Accordion title="How do I rotate database credentials?">
    Call the credential rotation endpoint:

    ```bash theme={null}
    POST /api/v2/ingestion/connection-uris/rotate
    {
      "kind": "customer_db"
    }
    ```

    This generates a new password for the specified role. Existing connections using the old password will be terminated.
  </Accordion>

  <Accordion title="Can I use pg_dump to back up my database?">
    Yes. Connect with the customer DB role and run `pg_dump` against schemas that
    role can read. For a full export of resolved data, dump the read-model schema
    available in your workspace. For agent-created state, dump `storage`.
  </Accordion>

  <Accordion title="What PostgreSQL version is this?">
    Neon runs PostgreSQL 16. All standard PostgreSQL 16 features, extensions
    (pgcrypto is enabled by default), and wire protocol compatibility apply.
  </Accordion>

  <Accordion title="Is there a row limit or storage cap?">
    Neon's serverless architecture scales storage automatically. There is no artificial row limit imposed by Deepline. Storage costs are part of the Neon project provisioned for your workspace — see the pricing page for details.
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="Database Access (Developer Guide)" icon="code" href="/docs/database-access">
    API endpoints, code patterns, and the full schema contract reference for
    building on your database.
  </Card>

  <Card title="Quick Start" icon="bolt" href="/docs/quickstart">
    Install Deepline and run your first enrichment in 60 seconds.
  </Card>

  <Card title="API Reference" icon="terminal" href="/docs/api-reference">
    Command patterns, payloads, and execution flow.
  </Card>

  <Card title="Pricing" icon="credit-card" href="/docs/pricing">
    BYOK free tier, managed credits, and what is included with your database.
  </Card>
</CardGroup>
