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

# Synthesize Multiple Documents into Visual Findings

> Upload PDFs, vectorize them, extract themes with AI, and map findings to sources. 12 minutes.

## What you'll build

A research synthesis board that organizes multiple documents, vectorizes extracted text, finds cross-cutting themes, and creates a visual map linking findings to source material.

* Upload and vectorize 5+ PDFs
* Ask AI to identify themes across all sources
* Create finding nodes connected to source documents
* Search for specific topics across your entire document set

## Prerequisites

* ChatGrid API key ([get one here](/authentication))
* Node.js 18+ or Python 3.10+
* PDF files or document URLs to analyze

## Step 1: Create a research board

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.chatgrid.ai/v1/boards \
    -H "Authorization: Bearer cgk_live_..." \
    -H "Content-Type: application/json" \
    -d '{"name": "Q1 Market Research Synthesis"}'
  ```

  ```typescript Node.js theme={null}
  const res = await fetch("https://api.chatgrid.ai/v1/boards", {
    method: "POST",
    headers: { Authorization: "Bearer cgk_live_...", "Content-Type": "application/json" },
    body: JSON.stringify({ name: "Q1 Market Research Synthesis" }),
  });
  const { data: board } = await res.json();
  const boardId = board.id;
  ```

  ```python Python theme={null}
  import requests
  API_KEY = "cgk_live_..."
  BASE = "https://api.chatgrid.ai/v1"
  headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
  res = requests.post(f"{BASE}/boards", headers=headers, json={"name": "Q1 Market Research Synthesis"})
  board_id = res.json()["data"]["id"]
  ```
</CodeGroup>

## Step 2: Upload PDFs and create document nodes

Upload each PDF as an asset, then create a node on the canvas.

```bash cURL theme={null}
# Upload the file
curl -X POST https://api.chatgrid.ai/v1/boards/{boardId}/assets \
  -H "Authorization: Bearer cgk_live_..." \
  -F "file=@gartner-report-2026.pdf"

# Create a PDF node using fields from the asset upload response
curl -X POST https://api.chatgrid.ai/v1/boards/{boardId}/nodes \
  -H "Authorization: Bearer cgk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"type": "pdf", "position": {"x": 0, "y": 0}, "data": {"label": "Gartner Report 2026", "publicUrl": "{assetUrl}", "storagePath": "{storagePath}"}}'
```

Repeat for all documents. Here is how to batch it in Python:

```python Python theme={null}
documents = [
    {"file": "gartner-report-2026.pdf", "label": "Gartner Report", "x": 0},
    {"file": "forrester-wave-q1.pdf", "label": "Forrester Wave Q1", "x": 400},
    {"file": "internal-survey.pdf", "label": "Customer Survey", "x": 800},
    {"file": "competitor-teardown.pdf", "label": "Competitor Teardown", "x": 0, "y": 350},
    {"file": "win-loss-analysis.pdf", "label": "Win/Loss Analysis", "x": 400, "y": 350},
]
node_ids = []
for doc in documents:
    with open(doc["file"], "rb") as f:
        upload = requests.post(f"{BASE}/boards/{board_id}/assets",
            headers={"Authorization": f"Bearer {API_KEY}"}, files={"file": f})
    asset = upload.json()["data"]
    node = requests.post(f"{BASE}/boards/{board_id}/nodes", headers=headers, json={
        "type": "pdf", "position": {"x": doc["x"], "y": doc.get("y", 0)},
        "data": {
            "label": doc["label"],
            "publicUrl": asset["url"],
            "storagePath": asset["storage_path"],
            "fileSize": asset["size"],
        },
    })
    node_ids.append({"id": node.json()["data"]["id"], "label": doc["label"]})
```

## Step 3: Vectorize all documents

Vectorize extracted document text, linking chunks to the source node.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.chatgrid.ai/v1/boards/{boardId}/documents/vectorize \
    -H "Authorization: Bearer cgk_live_..." \
    -H "Content-Type: application/json" \
    -d '{
      "content": "Paste extracted text from the report here. Include the sections you want ChatGrid to search and cite later.",
      "node_id": "{nodeId}",
      "metadata": {"source": "gartner-report-2026.pdf"}
    }'
  # Repeat for each document
  ```

  ```typescript Node.js theme={null}
  const vectorizePromises = documents.map((doc) =>
    fetch(`https://api.chatgrid.ai/v1/boards/${boardId}/documents/vectorize`, {
      method: "POST",
      headers: { Authorization: "Bearer cgk_live_...", "Content-Type": "application/json" },
      body: JSON.stringify({
        content: doc.extractedText,
        node_id: doc.nodeId,
        metadata: { source: doc.fileName },
      }),
    })
  );
  await Promise.all(vectorizePromises);
  ```
</CodeGroup>

<Note>
  The REST API vectorizes text you provide. Use your own extraction pipeline, then send the extracted text to `/documents/vectorize`.
</Note>

## Step 4: Ask AI to find themes

Create a chat and ask the AI to synthesize across all documents.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.chatgrid.ai/v1/boards/{boardId}/chats \
    -H "Authorization: Bearer cgk_live_..." \
    -H "Content-Type: application/json" \
    -d '{"title": "Cross-Document Synthesis"}'

  curl -X POST https://api.chatgrid.ai/v1/boards/{boardId}/chats/{chatId}/messages \
    -H "Authorization: Bearer cgk_live_..." \
    -H "Content-Type: application/json" \
    -d '{
      "content": "Analyze all documents on this board. Identify the top 5 recurring themes. For each theme, cite which documents mention it and summarize the key points.",
      "stream": false
    }'
  ```

  ```python Python theme={null}
  chat = requests.post(f"{BASE}/boards/{board_id}/chats", headers=headers,
      json={"title": "Cross-Document Synthesis"})
  chat_id = chat.json()["data"]["id"]

  response = requests.post(f"{BASE}/boards/{board_id}/chats/{chat_id}/messages", headers=headers, json={
      "content": "Analyze all documents on this board. Identify the top 5 recurring themes. For each theme, cite which documents mention it and summarize the key points.",
      "stream": False,
  })
  analysis = response.json()["data"]["content"]
  ```
</CodeGroup>

## Step 5: Create finding nodes and connect to sources

Turn each theme into a sticky note and draw edges to the documents it references.

```python Python theme={null}
themes = [
    {"label": "AI adoption accelerating", "content": "3 of 5 reports cite >40% YoY growth in AI tool adoption.", "sources": [node_ids[0], node_ids[1]], "x": 200},
    {"label": "Buyer consolidation", "content": "Teams reducing vendor count from 8 to 3.", "sources": [node_ids[1], node_ids[4]], "x": 600},
    {"label": "Self-serve preferred", "content": "72% prefer self-serve onboarding.", "sources": [node_ids[2], node_ids[4]], "x": 1000},
]
for theme in themes:
    node = requests.post(f"{BASE}/boards/{board_id}/nodes", headers=headers, json={
        "type": "note", "position": {"x": theme["x"], "y": 700},
        "data": {"label": f"Theme: {theme['label']}", "text": theme["content"]},
    })
    finding_id = node.json()["data"]["id"]
    for source in theme["sources"]:
        requests.post(f"{BASE}/boards/{board_id}/edges", headers=headers, json={
            "source_node_id": finding_id, "target_node_id": source["id"], "data": {"label": "cited in"},
        })
```

## Step 6: Search for specific topics

Query across all documents without re-reading them.

```bash cURL theme={null}
curl -X POST https://api.chatgrid.ai/v1/boards/{boardId}/documents/search \
  -H "Authorization: Bearer cgk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"query": "what do analysts say about pricing pressure in 2026?", "limit": 5}'
```

Results include matching text, similarity score, and source metadata so you can trace every finding back to its document.

## What's happening under the hood

Each vectorize call chunks submitted text and embeds it via pgvector. The `node_id` parameter ties chunks to their visual node, so edges between finding nodes and source nodes are meaningful. When AI answers a synthesis question, it performs semantic search across all chunks on the board and reasons over the top matches. The metadata you attach appears in search results, making claims easy to verify.

## Next steps

<CardGroup cols={2}>
  <Card title="Meeting Knowledge" icon="microphone" href="/cookbooks/meeting-knowledge">
    Turn meeting notes into searchable knowledge
  </Card>

  <Card title="Competitive Intel" icon="chart-line" href="/cookbooks/competitive-intel">
    Track competitor websites and compare positioning
  </Card>
</CardGroup>
