> ## 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.

# How to Migrate from Clay to Claude Code

> Take an existing Clay table and produce equivalent scripts that run locally, on your schedule, with full control. Use it to get through setup and your first.

Clay tables are column-based — each column is a separately configured enrichment or AI step. Deepline is outcome-centric: describe what you're trying to achieve and Claude selects the right tools to get there.

<iframe src="https://www.loom.com/embed/b4529204d53549c1b479e2b1fd94b0a6" frameBorder="0" allowFullScreen style={{ width: '100%', aspectRatio: '16/9' }} />

The video walks through a real Clay table with email waterfalls, Claygent research, and ICP scoring — the full flow from table → phase 1 doc → scripts → output CSV.

***

## What you need

<CardGroup cols={3}>
  <Card title="A Clay table" icon="table">
    The table you want to migrate.
  </Card>

  <Card title="Claude Code" icon="terminal">
    Install from [claude.ai/code](https://claude.ai/code) and log in.
  </Card>

  <Card title="Deepline CLI" icon="bolt">
    Runs the enrichment scripts Claude Code generates. Install below.
  </Card>
</CardGroup>

<Tip>
  You can use this skill just to **document** a Clay table without running
  anything. If that's your goal, skip to [Documentation-Only
  Path](#documentation-only-path) at the bottom.
</Tip>

***

## Install Deepline

Deepline is what runs the enrichment. It wraps providers like LeadMagic, Exa,
Instantly, and Claude into a single command. Ask your coding agent to set it up:

```text theme={null}
Set up Deepline in this environment by following https://code.deepline.com/SKILL.md. Read and follow the skill exactly.
```

If you prefer the terminal:

```bash theme={null}
npm install -g deepline@latest && npm exec --yes --package=deepline@latest -- deepline setup --json
```

Setup verifies the CLI, skills, authentication, and workspace before it
finishes. Use `deepline doctor --json` if setup reports a problem.

<Tip>
  You don't need Deepline if you just want a Phase 1 doc (table summary +
  dependency graph + pass plan). You only need it when you want to actually run
  the output scripts.
</Tip>

***

## The workflow

Four steps. The first two are in Claude Code. The last two are in your terminal.

### Easy path: the Clay Extract bookmarklet

Instead of capturing network logs by hand, use the **Clay Extract bookmarklet**. It runs in your already-logged-in Clay tab, pulls the full table config, schema, sample records, and all cell data, then downloads a single `clay_extract_<table>.json` you can hand straight to `/clay-to-deepline`. Your session cookie is never read or stored, only your browser uses it.

<div
  dangerouslySetInnerHTML={{
__html:
  "<a id=\"clay-extract-bm\" href=\"javascript:(async%20()%20%3D%3E%20%7B%20const%20API%20%3D%20'https%3A%2F%2Fapi.clay.com'%3B%20const%20MAX_RECORDS%20%3D%20500%3B%20const%20BATCH%20%3D%2050%3B%20const%20path%20%3D%20location.pathname%3B%20const%20tableId%20%3D%20(path.match(%2F%5C%2Ftables%5C%2F(t_%5BA-Za-z0-9%5D%2B)%2F)%20%7C%7C%20%5B%5D)%5B1%5D%3B%20let%20viewId%20%3D%20(path.match(%2F%5C%2Fviews%5C%2F(gv_%5BA-Za-z0-9%5D%2B)%2F)%20%7C%7C%20%5B%5D)%5B1%5D%3B%20if%20(!tableId)%20%7B%20alert('Clay%20extract%3A%20no%20table%20id%20in%20URL.%20Open%20a%20%2Ftables%2Ft_...%20page%20first.')%3B%20return%3B%20%7D%20const%20toast%20%3D%20(msg)%20%3D%3E%20%7B%20let%20el%20%3D%20document.getElementById('__clay_extract_toast')%3B%20if%20(!el)%20%7B%20el%20%3D%20document.createElement('div')%3B%20el.id%20%3D%20'__clay_extract_toast'%3B%20el.style.cssText%20%3D%20'position%3Afixed%3Bz-index%3A2147483647%3Bbottom%3A20px%3Bright%3A20px%3Bbackground%3A%23111%3Bcolor%3A%23fff%3B'%20%2B%20'font%3A13px%2F1.4%20-apple-system%2Csystem-ui%2Csans-serif%3Bpadding%3A10px%2014px%3Bborder-radius%3A8px%3B'%20%2B%20'box-shadow%3A0%204px%2016px%20rgba(0%2C0%2C0%2C.3)%3Bmax-width%3A320px'%3B%20document.body.appendChild(el)%3B%20%7D%20el.textContent%20%3D%20'Clay%20extract%3A%20'%20%2B%20msg%3B%20return%20el%3B%20%7D%3B%20const%20getJSON%20%3D%20async%20(url%2C%20opts)%20%3D%3E%20%7B%20const%20r%20%3D%20await%20fetch(url%2C%20%7B%20credentials%3A%20'include'%2C%20headers%3A%20%7B%20accept%3A%20'application%2Fjson%2C%20text%2Fplain%2C%20*%2F*'%2C%20...(opts%20%26%26%20opts.headers)%20%7D%2C%20...opts%2C%20%7D)%3B%20if%20(!r.ok)%20throw%20new%20Error(url.replace(API%2C%20'')%20%2B%20'%20%E2%86%92%20'%20%2B%20r.status)%3B%20return%20r.json()%3B%20%7D%3B%20try%20%7B%20toast('fetching%20table%20config%E2%80%A6')%3B%20const%20table%20%3D%20await%20getJSON(API%20%2B%20'%2Fv3%2Ftables%2F'%20%2B%20tableId)%3B%20viewId%20%3D%20viewId%20%7C%7C%20table%3F.table%3F.firstViewId%20%7C%7C%20table%3F.firstViewId%3B%20if%20(!viewId)%20throw%20new%20Error('could%20not%20resolve%20a%20view%20id%20(firstViewId%20missing)')%3B%20toast('fetching%20schema%20%2B%20sample%20records%E2%80%A6')%3B%20let%20schemaTree%20%3D%20null%3B%20let%20exampleRecords%20%3D%20%5B%5D%3B%20try%20%7B%20const%20sv2%20%3D%20await%20getJSON(%20API%20%2B%20'%2Fv3%2Ftables%2F'%20%2B%20tableId%20%2B%20'%2Fviews%2F'%20%2B%20viewId%20%2B%20'%2Ftable-schema-v2'%2C%20)%3B%20schemaTree%20%3D%20sv2%3F.tableSchema%20%7C%7C%20sv2%20%7C%7C%20null%3B%20exampleRecords%20%3D%20sv2%3F.exampleRecords%20%7C%7C%20sv2%3F.records%20%7C%7C%20sv2%3F.sampleRecords%20%7C%7C%20%5B%5D%3B%20%7D%20catch%20(e)%20%7B%20console.warn('%5Bclay-extract%5D%20table-schema-v2%20failed%3A'%2C%20e.message)%3B%20%7D%20let%20totalRecordCount%20%3D%20null%3B%20try%20%7B%20const%20cnt%20%3D%20await%20getJSON(API%20%2B%20'%2Fv3%2Ftables%2F'%20%2B%20tableId%20%2B%20'%2Fcount')%3B%20totalRecordCount%20%3D%20cnt%3F.tableTotalRecordsCount%20%3F%3F%20cnt%3F.count%20%3F%3F%20null%3B%20%7D%20catch%20(e)%20%7B%20console.warn('%5Bclay-extract%5D%20count%20failed%3A'%2C%20e.message)%3B%20%7D%20toast('fetching%20record%20ids%E2%80%A6')%3B%20let%20recordIds%20%3D%20%5B%5D%3B%20try%20%7B%20const%20ids%20%3D%20await%20getJSON(%20API%20%2B%20'%2Fv3%2Ftables%2F'%20%2B%20tableId%20%2B%20'%2Fviews%2F'%20%2B%20viewId%20%2B%20'%2Frecords%2Fids'%2C%20)%3B%20recordIds%20%3D%20ids%3F.results%20%7C%7C%20ids%3F.recordIds%20%7C%7C%20ids%3F.ids%20%7C%7C%20(Array.isArray(ids)%20%3F%20ids%20%3A%20%5B%5D)%3B%20%7D%20catch%20(e)%20%7B%20console.warn('%5Bclay-extract%5D%20records%2Fids%20failed%3A'%2C%20e.message)%3B%20%7D%20const%20truncated%20%3D%20recordIds.length%20%3E%20MAX_RECORDS%3B%20const%20idsToFetch%20%3D%20recordIds.slice(0%2C%20MAX_RECORDS)%3B%20const%20bulkFetchRecords%20%3D%20%5B%5D%3B%20for%20(let%20i%20%3D%200%3B%20i%20%3C%20idsToFetch.length%3B%20i%20%2B%3D%20BATCH)%20%7B%20const%20chunk%20%3D%20idsToFetch.slice(i%2C%20i%20%2B%20BATCH)%3B%20toast('records%20'%20%2B%20(i%20%2B%20chunk.length)%20%2B%20'%2F'%20%2B%20idsToFetch.length%20%2B%20'%E2%80%A6')%3B%20try%20%7B%20const%20res%20%3D%20await%20getJSON(API%20%2B%20'%2Fv3%2Ftables%2F'%20%2B%20tableId%20%2B%20'%2Fbulk-fetch-records'%2C%20%7B%20method%3A%20'POST'%2C%20headers%3A%20%7B%20'content-type'%3A%20'application%2Fjson'%20%7D%2C%20body%3A%20JSON.stringify(%7B%20recordIds%3A%20chunk%2C%20includeExternalContentFieldIds%3A%20%5B%5D%20%7D)%2C%20%7D)%3B%20if%20(res%3F.results)%20bulkFetchRecords.push(...res.results)%3B%20%7D%20catch%20(e)%20%7B%20console.warn('%5Bclay-extract%5D%20bulk-fetch%20batch%20failed%3A'%2C%20e.message)%3B%20%7D%20%7D%20if%20(truncated)%20%7B%20console.warn(%20'%5Bclay-extract%5D%20record%20pull%20capped%20at%20MAX_RECORDS%3D'%20%2B%20MAX_RECORDS%20%2B%20'%20of%20'%20%2B%20recordIds.length%20%2B%20'%20total.%20Raise%20MAX_RECORDS%20to%20pull%20all.'%2C%20)%3B%20%7D%20const%20extract%20%3D%20%7B%20_meta%3A%20%7B%20extractedAt%3A%20new%20Date().toISOString()%2C%20method%3A%20'bookmarklet'%2C%20tableId%2C%20viewId%2C%20url%3A%20location.href%2C%20totalRecordCount%2C%20idCount%3A%20recordIds.length%2C%20fetchedRecordCount%3A%20bulkFetchRecords.length%2C%20exampleRecordCount%3A%20exampleRecords.length%2C%20truncated%2C%20%7D%2C%20table%3A%20table%3F.table%20%7C%7C%20table%2C%20fields%3A%20table%3F.fields%20%7C%7C%20table%3F.table%3F.fields%20%7C%7C%20%5B%5D%2C%20tableSchema%3A%20schemaTree%2C%20exampleRecords%2C%20recordIds%2C%20bulkFetchRecords%2C%20%7D%3B%20const%20name%20%3D%20(extract.table%3F.name%20%7C%7C%20tableId).replace(%2F%5B%5EA-Za-z0-9_-%5D%2B%2Fg%2C%20'_').slice(0%2C%2060)%3B%20const%20blob%20%3D%20new%20Blob(%5BJSON.stringify(extract%2C%20null%2C%202)%5D%2C%20%7B%20type%3A%20'application%2Fjson'%20%7D)%3B%20const%20a%20%3D%20document.createElement('a')%3B%20a.href%20%3D%20URL.createObjectURL(blob)%3B%20a.download%20%3D%20'clay_extract_'%20%2B%20name%20%2B%20'.json'%3B%20document.body.appendChild(a)%3B%20a.click()%3B%20a.remove()%3B%20setTimeout(()%20%3D%3E%20URL.revokeObjectURL(a.href)%2C%204000)%3B%20window.__clayExtract%20%3D%20extract%3B%20toast(%20'done%3A%20'%20%2B%20(extract.fields%3F.length%20%7C%7C%200)%20%2B%20'%20fields%2C%20'%20%2B%20exampleRecords.length%20%2B%20'%20sample%20rows%2C%20'%20%2B%20bulkFetchRecords.length%20%2B%20'%2F'%20%2B%20(totalRecordCount%20%3F%3F%20recordIds.length)%20%2B%20'%20records'%20%2B%20(truncated%20%3F%20'%20(CAPPED)'%20%3A%20'')%20%2B%20'.%20Downloaded%20JSON.'%2C%20)%3B%20setTimeout(()%20%3D%3E%20document.getElementById('__clay_extract_toast')%3F.remove()%2C%206000)%3B%20%7D%20catch%20(err)%20%7B%20console.error('%5Bclay-extract%5D'%2C%20err)%3B%20toast('ERROR%3A%20'%20%2B%20err.message%20%2B%20'%20(see%20console)')%3B%20%7D%20%7D)()%3B\" onclick=\"event.preventDefault&amp;&amp;event.preventDefault();navigator.clipboard&amp;&amp;navigator.clipboard.writeText(this.href);var h=document.getElementById(&quot;clay-extract-bm-hint&quot;);if(h){h.textContent=&quot;Copied. Create a bookmark and paste as the URL. (Or just drag the button.)&quot;;}return false;\" style=\"display:inline-block;padding:10px 18px;border-radius:8px;background:#6c47ff;color:#fff;font:600 14px/1 -apple-system,system-ui,sans-serif;text-decoration:none;cursor:grab;box-shadow:0 2px 8px rgba(108,71,255,.35)\">Clay Extract, drag me to your bookmarks bar</a><span id=\"clay-extract-bm-hint\" style=\"margin-left:10px;font:13px -apple-system,system-ui,sans-serif;color:#888\"></span>",
}}
/>

<Steps>
  <Step title="Add the bookmarklet">
    **Drag the purple button above to your bookmarks bar.** Or click it to copy the code, create a new bookmark, and paste it as the URL.
  </Step>

  <Step title="Open your Clay table">
    Go to the table view you want, e.g. `app.clay.com/workspaces/.../tables/t_xxx/views/gv_xxx`.
  </Step>

  <Step title="Click the bookmarklet">
    A progress toast appears bottom-right, then `clay_extract_<table>.json` downloads automatically.
  </Step>

  <Step title="Hand it to Claude Code">
    Run `/clay-to-deepline` and attach the downloaded JSON. No cURL, no DevTools.
  </Step>
</Steps>

<Accordion title="View the bookmarklet source (raw)" icon="code">
  This is exactly what runs. It only calls `api.clay.com` v3 read endpoints with your existing browser session and downloads the result locally. Nothing is sent anywhere else.

  ```js theme={null}
  /*
   * Clay Table Extractor — Bookmarklet (source)
   *
   * Run this on any Clay table view URL, e.g.
   *   https://app.clay.com/workspaces/1114258/tables/t_xxx/views/gv_xxx
   *
   * It uses the already-authenticated browser session (credentials: 'include',
   * so the claysession cookie is sent automatically) to pull, from Clay's
   * internal v3 API:
   *   - table config        GET  /v3/tables/{TABLE_ID}            (fields, typeSettings, prompts, action bindings)
   *   - schema + samples     GET  /v3/tables/{TABLE_ID}/views/{VIEW_ID}/table-schema-v2
   *   - all record ids       GET  /v3/tables/{TABLE_ID}/views/{VIEW_ID}/records/ids
   *   - full cell data       POST /v3/tables/{TABLE_ID}/bulk-fetch-records  (batched)
   *
   * Output: downloads a single JSON file shaped to match the clay-extract.py
   * extract the deepline clay-to-deepline skill expects:
   *   { _meta, table, fields, tableSchema, exampleRecords, recordIds, bulkFetchRecords }
   *
   * The cookie is NEVER read, logged, or written — only the browser uses it.
   */
  (async () => {
    const API = 'https://api.clay.com';
    const MAX_RECORDS = 500; // cap full pull; raise if you need every row
    const BATCH = 50; // bulk-fetch page size

    const path = location.pathname;
    const tableId = (path.match(/\/tables\/(t_[A-Za-z0-9]+)/) || [])[1];
    let viewId = (path.match(/\/views\/(gv_[A-Za-z0-9]+)/) || [])[1];

    if (!tableId) {
      alert('Clay extract: no table id in URL. Open a /tables/t_... page first.');
      return;
    }

    const toast = (msg) => {
      let el = document.getElementById('__clay_extract_toast');
      if (!el) {
        el = document.createElement('div');
        el.id = '__clay_extract_toast';
        el.style.cssText =
          'position:fixed;z-index:2147483647;bottom:20px;right:20px;background:#111;color:#fff;' +
          'font:13px/1.4 -apple-system,system-ui,sans-serif;padding:10px 14px;border-radius:8px;' +
          'box-shadow:0 4px 16px rgba(0,0,0,.3);max-width:320px';
        document.body.appendChild(el);
      }
      el.textContent = 'Clay extract: ' + msg;
      return el;
    };

    const getJSON = async (url, opts) => {
      const r = await fetch(url, {
        credentials: 'include',
        headers: {
          accept: 'application/json, text/plain, */*',
          ...(opts && opts.headers),
        },
        ...opts,
      });
      if (!r.ok) throw new Error(url.replace(API, '') + ' → ' + r.status);
      return r.json();
    };

    try {
      toast('fetching table config…');
      const table = await getJSON(API + '/v3/tables/' + tableId);

      // Resolve view id from the table if it wasn't in the URL.
      viewId = viewId || table?.table?.firstViewId || table?.firstViewId;
      if (!viewId)
        throw new Error('could not resolve a view id (firstViewId missing)');

      // table-schema-v2 returns { tableSchema: { f_xxx: {...} }, exampleRecords: [ { f_xxx: <rendered value> } ] }
      // exampleRecords here carry RENDERED formula/action cell values (richest source) — capped by Clay (~2-66 rows).
      toast('fetching schema + sample records…');
      let schemaTree = null;
      let exampleRecords = [];
      try {
        const sv2 = await getJSON(
          API + '/v3/tables/' + tableId + '/views/' + viewId + '/table-schema-v2',
        );
        schemaTree = sv2?.tableSchema || sv2 || null;
        exampleRecords =
          sv2?.exampleRecords || sv2?.records || sv2?.sampleRecords || [];
      } catch (e) {
        console.warn('[clay-extract] table-schema-v2 failed:', e.message);
      }

      // True record count (no pagination on records/ids, but this confirms the cap).
      let totalRecordCount = null;
      try {
        const cnt = await getJSON(API + '/v3/tables/' + tableId + '/count');
        totalRecordCount = cnt?.tableTotalRecordsCount ?? cnt?.count ?? null;
      } catch (e) {
        console.warn('[clay-extract] count failed:', e.message);
      }

      toast('fetching record ids…');
      let recordIds = [];
      try {
        const ids = await getJSON(
          API + '/v3/tables/' + tableId + '/views/' + viewId + '/records/ids',
        );
        // Clay returns { results: [r_xxx, ...] }; tolerate older shapes too.
        recordIds =
          ids?.results ||
          ids?.recordIds ||
          ids?.ids ||
          (Array.isArray(ids) ? ids : []);
      } catch (e) {
        console.warn('[clay-extract] records/ids failed:', e.message);
      }

      const truncated = recordIds.length > MAX_RECORDS;
      const idsToFetch = recordIds.slice(0, MAX_RECORDS);
      const bulkFetchRecords = [];
      for (let i = 0; i < idsToFetch.length; i += BATCH) {
        const chunk = idsToFetch.slice(i, i + BATCH);
        toast('records ' + (i + chunk.length) + '/' + idsToFetch.length + '…');
        try {
          const res = await getJSON(
            API + '/v3/tables/' + tableId + '/bulk-fetch-records',
            {
              method: 'POST',
              headers: { 'content-type': 'application/json' },
              body: JSON.stringify({
                recordIds: chunk,
                includeExternalContentFieldIds: [],
              }),
            },
          );
          if (res?.results) bulkFetchRecords.push(...res.results);
        } catch (e) {
          console.warn('[clay-extract] bulk-fetch batch failed:', e.message);
        }
      }

      if (truncated) {
        console.warn(
          '[clay-extract] record pull capped at MAX_RECORDS=' +
            MAX_RECORDS +
            ' of ' +
            recordIds.length +
            ' total. Raise MAX_RECORDS to pull all.',
        );
      }

      const extract = {
        _meta: {
          extractedAt: new Date().toISOString(),
          method: 'bookmarklet',
          tableId,
          viewId,
          url: location.href,
          totalRecordCount, // true table size from /count
          idCount: recordIds.length, // ids returned
          fetchedRecordCount: bulkFetchRecords.length, // rows pulled via bulk-fetch (may be sparse/unrun)
          exampleRecordCount: exampleRecords.length, // rendered sample rows from table-schema-v2 (richest)
          truncated, // true if MAX_RECORDS capped the pull
        },
        table: table?.table || table,
        fields: table?.fields || table?.table?.fields || [],
        // Schema tree keyed by field id (f_xxx → { type, name, children, ... }).
        tableSchema: schemaTree,
        // Rendered sample rows: flat { f_xxx: value } maps with formula/action outputs. Prefer these for prompts/samples.
        exampleRecords,
        recordIds,
        // Raw per-cell data for ALL records (cells are sparse — only populated fields appear).
        bulkFetchRecords,
      };

      const name = (extract.table?.name || tableId)
        .replace(/[^A-Za-z0-9_-]+/g, '_')
        .slice(0, 60);
      const blob = new Blob([JSON.stringify(extract, null, 2)], {
        type: 'application/json',
      });
      const a = document.createElement('a');
      a.href = URL.createObjectURL(blob);
      a.download = 'clay_extract_' + name + '.json';
      document.body.appendChild(a);
      a.click();
      a.remove();
      setTimeout(() => URL.revokeObjectURL(a.href), 4000);

      window.__clayExtract = extract; // also left on window for console inspection
      toast(
        'done: ' +
          (extract.fields?.length || 0) +
          ' fields, ' +
          exampleRecords.length +
          ' sample rows, ' +
          bulkFetchRecords.length +
          '/' +
          (totalRecordCount ?? recordIds.length) +
          ' records' +
          (truncated ? ' (CAPPED)' : '') +
          '. Downloaded JSON.',
      );
      setTimeout(
        () => document.getElementById('__clay_extract_toast')?.remove(),
        6000,
      );
    } catch (err) {
      console.error('[clay-extract]', err);
      toast('ERROR: ' + err.message + ' (see console)');
    }
  })();
  ```
</Accordion>

<Note>
  Prefer doing it by hand, or the bookmarklet didn't work? The manual
  network-log path still works, step by step below.
</Note>

### Step 1 — Capture your Clay table's network logs

1. Open **app.clay.com** in Chrome and log in
2. Open DevTools: `Cmd+Option+I` on Mac, or `F12` on Windows
3. Click the **Network** tab
4. Reload the page (or click around in your Clay table to trigger requests)
5. In the filter box, type `api.clay.com` to show only Clay API calls
6. Click any request in the list
7. Right-click → **Copy** → **Copy as cURL**

<Note>
  On Mac it's "Copy as cURL (bash)". On Windows it's "Copy as cURL (cmd)" —
  either works, the skill strips what it needs.
</Note>

The copied command will look like:

```bash theme={null}
curl 'https://api.clay.com/v3/tables/t_abc123/...' \
  -H 'accept: application/json' \
  -b 'claysession=eyJhb...; ajs_user_id=abc; ...' \
  ...
```

The part you need is everything between the `-b '...'` quotes — that's your session cookie.

### Step 2 — Run /clay-to-deepline in Claude Code

Open Claude Code and type:

```
/clay-to-deepline [paste URLs]
```

Then give it your Clay table. You can paste:

* A Clay table URL (e.g. `app.clay.com/workbooks/xxx/tables/yyy`)
* A JSON export from the table
* Network logs or HAR file captured from Chrome DevTools

<Warning>
  Network logs contain session cookies. The cookies are **not** shared with
  Deepline, but don't share them externally.
</Warning>

Claude Code reads the table structure and the actual AI prompt templates embedded in each column. The richer your input, the more accurate the output.

**Review the Phase 1 doc** — Claude Code produces three things before writing any scripts:

* **Table summary** — every column, what Clay action it runs, what model, what the output looks like
* **Dependency graph** — which columns feed into which (execution order at a glance)
* **Pass plan** — the ordered list of Deepline tool calls that replicate each Clay action

Check that:

* Prompt templates are recovered verbatim (approximated ones are flagged with ⚠️)
* The pass plan matches what your table actually does
* ICP scoring criteria are correct (the skill can't invent these)

Once you're happy, say "looks good" and Claude Code writes the scripts.

### Step 3 — Run the scripts

Claude Code generates two scripts in a `scripts/` folder:

```bash theme={null}
./scripts/fetch_people.sh           # → seed_people.csv
./scripts/enrich_people.sh          # → output_people.csv
```

Start with a pilot run (3 rows) before doing the full table:

```bash theme={null}
./scripts/fetch_people.sh seed_people.csv pilot
./scripts/enrich_people.sh
```

<Tip>
  For paid tools (Exa, LeadMagic, email waterfalls), the skill adds a pilot gate
  automatically. You'll see a preview before any money is spent.
</Tip>

### Step 4 — Get your output

Run the normalizer to produce clean CSVs:

```bash theme={null}
python3 scripts/normalize.py \
  --contacts output_people.csv \
  --out-contacts output/contacts.csv
```

You get:

* `output/contacts.csv` — every person with enriched fields (email, job function, AI-generated research, ICP score)
* `output/accounts.csv` — every company with verified name, domain, social links, HQ address, CEO

Or push directly to a campaign:

```bash theme={null}
deepline tools execute instantly_add_to_campaign \
  --payload '{"campaign_id":"<id>","contacts":[...]}'
```

***

## What you get

The migration produces a self-contained project folder:

```
my-table/
├── .env.deepline          # your Clay cookie (gitignored)
├── prompts/
│   ├── job_function.txt   # recovered verbatim from Clay, or marked ⚠️ APPROXIMATED
│   └── qualify_person.txt
├── scripts/
│   ├── fetch_people.sh
│   └── enrich_people.sh
└── output/
    ├── contacts.csv
    └── accounts.csv
```

The scripts are plain bash. You can read them, edit them, schedule them with cron, or run them in CI.

***

## How Clay actions map to Deepline

The skill handles these automatically — this is just so you know what's happening:

| Clay Action                      | Deepline Equivalent                               |
| -------------------------------- | ------------------------------------------------- |
| Claygent web research            | 3 parallel Exa searches + Claude Sonnet synthesis |
| chat-gpt-schema-mapper           | `call_ai haiku` — same output, much cheaper       |
| octave-qualify-person            | `call_ai sonnet + json_mode` ICP scorer           |
| add-lead-to-campaign (Instantly) | `instantly_add_to_campaign`                       |
| add-lead-to-campaign (Smartlead) | `smartlead_api_request`                           |

***

## Accuracy

26-row production test against Clay ground truth (March 2026). Small sample size — results are directional, not statistically definitive:

| Metric                                    | Match Rate                         |
| ----------------------------------------- | ---------------------------------- |
| Email                                     | ≥95% match vs Clay                 |
| Job function classification               | ≥95% exact match                   |
| Claygent research (strategy, initiatives) | \~85% specific, actionable content |

The \~15% research gap is mostly name collisions — e.g. `onit.com` resolving to NASDAQ:ONIT instead of the legal-ops software company.

<Note>
  The skill's "confidence" label on research rows doesn't mean what you'd
  expect. "Low" confidence often has great content — it just means the LLM found
  blog posts instead of 10-K filings. Judge by whether the content is specific
  to the company, not by the confidence score.
</Note>

***

## Cost comparison

Same 26-row test (March 2026). Clay cost uses Argon model (3 data credits/row for 10-step research). Credit prices from [Clay pricing](https://university.clay.com/docs/plans-and-billing).

|              | Clay (Growth, \~\$0.05/cr)      | Clay (Launch, \~\$0.07/cr)      | Deepline                                          |
| ------------ | ------------------------------- | ------------------------------- | ------------------------------------------------- |
| Cost per row | \~\$0.15                        | \~\$0.22                        | \~\$0.03                                          |
| Method       | Claygent Argon, 10 search steps | Claygent Argon, 10 search steps | 3 parallel Exa searches + Claude Sonnet synthesis |
| Time per row | \~120-140 seconds               | \~120-140 seconds               | \~15 seconds                                      |

Content quality is equivalent. For large public companies, Deepline can be more current - Exa indexes recent blog and PR content faster than Google indexes 10-Ks.

***

## Documentation-only path

If you just want to document a Clay table — understand what it does, map its dependencies, build a reference for the team — you don't need to run anything.

1. [Install ClayMate Lite from GitHub](https://github.com/GTM-Base/claymate-lite)
2. Open your Clay table in Chrome
3. Click the extension icon → "Export JSON"
4. Drag the downloaded `*.json` file into Claude Code
5. Run `/clay-to-deepline` and ask for Phase 1 docs only

You'll get a full table summary, dependency graph, and pass plan — without running a single script or spending anything.

<Tip>
  ClayMate Lite exports include the table schema + sample rows. This is the
  richest input short of a full HAR file. For most tables it's enough to recover
  the actual AI prompt templates from cell values.
</Tip>

***

## Feedback or issues?

* **Email**: [team@deepline.com](mailto:team@deepline.com)
* **Slack**: [Join Terminal Velocity GTM](https://join.slack.com/t/terminal-velocity-gtm/shared_invite/zt-3tbnkutuq-n~a7fdFcwVmRld8tgebxCw) — this is where most of the live migration discussion happens
* [Book time to chat or debug](https://calendly.com/jptoor/30min)
