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

# Build an AI Financial Data Copilot

> Learn how to build an AI-powered data copilot with generative UI and real-time financial data

Build an intelligent financial data copilot that generates custom visualizations and insights based on natural language queries. This guide teaches you how to combine financial APIs with C1 to create dynamic charts, tables, and insights.

<iframe src="https://analytics-with-c1.vercel.app" width="100%" height="600px" style={{ border: "1px solid #e5e7eb", borderRadius: "8px" }} title="AI Data Copilot Demo" />

**Try it above** or visit [analytics-with-c1.vercel.app](https://analytics-with-c1.vercel.app)

## What You'll Learn

* Connecting C1 with financial data APIs using tool calling
* Building a suite of analytics tools (stocks, crypto, financials)
* Designing system prompts for data visualization
* Automatic chart generation based on data type
* Adding web search for market context
* Thread management for multi-turn conversations
* Setting up C1Chat for financial analysis UI

## Architecture Overview

AI data copilots work differently from traditional dashboards:

```
User Query → LLM (with data tools) → Financial APIs → LLM analyzes data → C1 generates visualizations → Stream to user
```

Instead of pre-built dashboards, the copilot generates the perfect visualization for each query - charts for trends, tables for comparisons, cards for summaries.

## Setup

### Prerequisites

* Node.js 18+
* Thesys API key from [console.thesys.dev](https://console.thesys.dev)
* Financial Datasets API key from [financialdatasets.ai](https://financialdatasets.ai)
* (Optional) Exa API key from [exa.ai](https://exa.ai) for web search

### Create Next.js Project

<CodeGroup dropdown>
  ```bash npm theme={null}
  npx create-next-app@latest analytics-with-c1
  cd analytics-with-c1
  ```

  ```bash pnpm theme={null}
  pnpm create next-app analytics-with-c1
  cd analytics-with-c1
  ```

  ```bash yarn theme={null}
  yarn create next-app analytics-with-c1
  cd analytics-with-c1
  ```
</CodeGroup>

When prompted, select:

* TypeScript: Yes
* ESLint: Yes
* Tailwind CSS: Yes
* App Router: Yes
* Customize default import alias: No

### Install Dependencies

<CodeGroup dropdown>
  ```bash npm theme={null}
  npm install @thesysai/genui-sdk @crayonai/react-ui openai financial-datasets exa-js zod zod-to-json-schema
  ```

  ```bash pnpm theme={null}
  pnpm add @thesysai/genui-sdk @crayonai/react-ui openai financial-datasets exa-js zod zod-to-json-schema
  ```

  ```bash yarn theme={null}
  yarn add @thesysai/genui-sdk @crayonai/react-ui openai financial-datasets exa-js zod zod-to-json-schema
  ```
</CodeGroup>

### Environment Variables

Create a `.env.local` file:

```bash theme={null}
THESYS_API_KEY=your_thesys_api_key
FINANCIAL_DATASETS_API_KEY=your_financial_datasets_key

# Optional: For web search context
EXA_API_KEY=your_exa_api_key
```

<Tip>
  Sign up for Financial Datasets at [financialdatasets.ai](https://financialdatasets.ai) to get real-time stock, crypto, and financial data.
</Tip>

## Step 1: Set Up Financial Data Tools

Create tools that fetch financial data. C1 will call these automatically based on user queries:

<CodeGroup dropdown>
  ```typescript tools/financial.ts theme={null}
  import { z } from "zod";
  import { zodToJsonSchema } from "zod-to-json-schema";

  // Define your financial API client
  const financialAPI = new FinancialDatasets({
    apiKey: process.env.FINANCIAL_DATASETS_API_KEY,
  });

  export const getFinancialTools = (writeThinkingState) => {
    const createTool = (name, description, schema, fn, thinkingState) => ({
      type: "function",
      function: {
        name,
        description,
        parameters: zodToJsonSchema(schema),
        function: async (args: string) => {
          const parsedArgs = JSON.parse(args);

          // Show user what's happening
          writeThinkingState(
            typeof thinkingState === "function"
              ? thinkingState(parsedArgs)
              : thinkingState
          );

          const result = await fn(parsedArgs);
          return JSON.stringify(result);
        },
      },
    });

    return [
      // Stock price tool
      createTool(
        "get_current_stock_price",
        "Get the current/latest price of a company",
        z.object({ ticker: z.string() }),
        async ({ ticker }) => {
          return await financialAPI.getStockPrice(ticker);
        },
        (args) => ({
          title: `Fetching Stock Price for ${args.ticker}`,
          description: "Getting the latest market price",
        })
      ),

      // Historical stock data tool
      createTool(
        "get_historical_stock_prices",
        "Gets historical stock prices for charting trends",
        z.object({
          ticker: z.string(),
          start_date: z.string(),
          end_date: z.string(),
        }),
        async ({ ticker, start_date, end_date }) => {
          return await financialAPI.getHistoricalPrices(
            ticker,
            start_date,
            end_date
          );
        },
        (args) => ({
          title: `Charting Historical Prices for ${args.ticker}`,
          description: "Plotting past stock performance to identify trends",
        })
      ),

      // Company financials tool
      createTool(
        "get_income_statements",
        "Get income statements to analyze profitability",
        z.object({ ticker: z.string() }),
        async ({ ticker }) => {
          return await financialAPI.getIncomeStatements(ticker);
        },
        (args) => ({
          title: `Analyzing Income Statements for ${args.ticker}`,
          description: "Reviewing profitability and financial performance",
        })
      ),

      // Crypto price tool
      createTool(
        "get_current_crypto_price",
        "Get current cryptocurrency price",
        z.object({ ticker: z.string() }),
        async ({ ticker }) => {
          return await financialAPI.getCryptoPrice(ticker);
        },
        (args) => ({
          title: `Getting Crypto Price for ${args.ticker}`,
          description: "Fetching current market value",
        })
      ),

      // Company news tool
      createTool(
        "get_company_news",
        "Get latest news for a company",
        z.object({ ticker: z.string() }),
        async ({ ticker }) => {
          return await financialAPI.getCompanyNews(ticker);
        },
        (args) => ({
          title: `Scanning News for ${args.ticker}`,
          description: "Catching up on latest announcements",
        })
      ),
    ];
  };
  ```

  ```python tools/financial.py theme={null}
  from typing import Callable, Dict, Any
  from pydantic import BaseModel
  import json

  # Define your financial API client
  financial_api = FinancialDatasets(
      api_key=os.environ["FINANCIAL_DATASETS_API_KEY"]
  )

  def get_financial_tools(write_thinking_state: Callable):
      def create_tool(name: str, description: str, schema: Dict, fn: Callable, thinking_state):
          async def tool_function(args: str) -> str:
              parsed_args = json.loads(args)

              # Show user what's happening
              state = (
                  thinking_state(parsed_args)
                  if callable(thinking_state)
                  else thinking_state
              )
              write_thinking_state(state)

              result = await fn(parsed_args)
              return json.dumps(result)

          return {
              "type": "function",
              "function": {
                  "name": name,
                  "description": description,
                  "parameters": schema,
              },
          }

      return [
          create_tool(
              "get_current_stock_price",
              "Get the current/latest price of a company",
              {
                  "type": "object",
                  "properties": {
                      "ticker": {"type": "string"}
                  },
                  "required": ["ticker"],
              },
              lambda args: financial_api.get_stock_price(args["ticker"]),
              lambda args: {
                  "title": f"Fetching Stock Price for {args['ticker']}",
                  "description": "Getting the latest market price",
              },
          ),
          # Add more tools similarly...
      ]
  ```
</CodeGroup>

<Tip>
  The `writeThinkingState` calls create a better UX by showing users which data sources are being queried in real-time.
</Tip>

## Step 2: Add Web Search for Context

Financial data is more useful with context. Add web search to explain market movements:

<CodeGroup dropdown>
  ```typescript tools/web-search.ts theme={null}
  import Exa from "exa-js";

  const exa = new Exa(process.env.EXA_API_KEY);

  export const createWebSearchTool = (writeThinkingState) => ({
    type: "function",
    function: {
      name: "webSearch",
      description: "Search the web for latest market information and context",
      parameters: {
        type: "object",
        properties: {
          query: { type: "string" },
        },
        required: ["query"],
      },
      function: async ({ query }: { query: string }) => {
        writeThinkingState({
          title: "Searching the web",
          description: "Collecting live insights for broader context",
        });

        // Exa's answer mode returns direct answers with citations
        const results = await exa.answer(query);

        return JSON.stringify({
          answer: results.answer,
          citations: results.citations.map(({ title, text }) => ({
            text: text ?? title,
          })),
        });
      },
    },
  });
  ```

  ```python tools/web_search.py theme={null}
  from exa_py import Exa

  exa = Exa(api_key=os.environ["EXA_API_KEY"])

  def create_web_search_tool(write_thinking_state):
      async def web_search(query: str) -> str:
          write_thinking_state({
              "title": "Searching the web",
              "description": "Collecting live insights for broader context",
          })

          # Exa's answer mode returns direct answers
          results = await exa.answer(query)

          return json.dumps({
              "answer": results.answer,
              "citations": [
                  {"text": c.text or c.title}
                  for c in results.citations
              ],
          })

      return {
          "type": "function",
          "function": {
              "name": "webSearch",
              "description": "Search the web for latest market information",
              "parameters": {
                  "type": "object",
                  "properties": {
                      "query": {"type": "string"},
                  },
                  "required": ["query"],
              },
          },
      }
  ```
</CodeGroup>

## Step 3: Create the Analytics Endpoint

Connect your tools with C1 to create the main analytics endpoint:

<CodeGroup dropdown>
  ```typescript app/api/chat/route.ts theme={null}
  import { makeC1Response } from "@thesysai/genui-sdk/server";
  import OpenAI from "openai";
  import { getFinancialTools } from "@/tools/financial";
  import { createWebSearchTool } from "@/tools/web-search";

  const client = new OpenAI({
    baseURL: "https://api.thesys.dev/v1/embed",
    apiKey: process.env.THESYS_API_KEY,
  });

  export async function POST(req: NextRequest) {
    const { prompt, threadId } = await req.json();
    const c1Response = makeC1Response();

    c1Response.writeThinkItem({
      title: "Analyzing Prompt",
      description: "Interpreting your query and preparing data sources",
    });

    // Combine all tools
    const financialTools = getFinancialTools(c1Response.writeThinkItem);
    const webSearchTool = createWebSearchTool(c1Response.writeThinkItem);
    const allTools = [...financialTools, webSearchTool];

    // Call C1 with automatic tool execution
    const runToolsResponse = client.beta.chat.completions.runTools({
      model: "c1/anthropic/claude-sonnet-4/v-20250930",
      messages: [
        {
          role: "system",
          content: SYSTEM_PROMPT,
        },
        ...(await getThreadHistory(threadId)),
        {
          role: "user",
          content: prompt,
        },
      ],
      stream: true,
      tools: allTools,
    });

    // Stream content as it's generated
    runToolsResponse.on("content", c1Response.writeContent);

    // Clean up when done
    runToolsResponse.on("end", async () => {
      c1Response.end();
      await saveToThread(threadId, prompt, c1Response.getAssistantMessage());
    });

    return new Response(c1Response.responseStream, {
      headers: {
        "Content-Type": "text/event-stream",
        "Cache-Control": "no-cache, no-transform",
        Connection: "keep-alive",
      },
    });
  }
  ```

  ```python main.py theme={null}
  from fastapi import FastAPI
  from openai import OpenAI
  from thesys_genui_sdk.fast_api import with_c1_response
  from thesys_genui_sdk.context import write_content, write_think_item

  client = OpenAI(
      api_key=os.environ["THESYS_API_KEY"],
      base_url="https://api.thesys.dev/v1/embed",
  )

  @app.post("/api/chat")
  @with_c1_response()
  async def chat(request: ChatRequest):
      await write_think_item({
          "title": "Analyzing Prompt",
          "description": "Interpreting your query and preparing data sources",
      })

      # Combine all tools
      financial_tools = get_financial_tools(write_think_item)
      web_search_tool = create_web_search_tool(write_think_item)
      all_tools = [*financial_tools, web_search_tool]

      # Call C1 with automatic tool execution
      stream = client.chat.completions.create(
          model="c1/anthropic/claude-sonnet-4/v-20250930",
          messages=[
              {"role": "system", "content": SYSTEM_PROMPT},
              *await get_thread_history(request.threadId),
              {"role": "user", "content": request.prompt},
          ],
          stream=True,
          tools=all_tools,
      )

      # Stream response
      for chunk in stream:
          content = chunk.choices[0].delta.content
          if content:
              await write_content(content)

      await save_to_thread(
          request.threadId,
          request.prompt,
          get_assistant_message()
      )
  ```
</CodeGroup>

## Step 4: Craft the System Prompt for Analytics

The system prompt guides C1 to create appropriate visualizations:

```typescript theme={null}
const SYSTEM_PROMPT = `You are a financial data copilot. Given the user's prompt, generate visualizations and insights to answer their financial questions.

**Visualization Rules:**

1. **Use charts for trends and time series:**
   - Line charts for stock prices over time
   - Bar charts for comparing metrics across periods
   - Area charts for cumulative values

2. **Use tables for detailed financial data:**
   - Income statements, balance sheets, cash flows
   - Show key metrics with proper formatting (currency, percentages)

3. **Use cards for summaries and key metrics:**
   - Current stock price with change indicators
   - Key financial ratios
   - Quick facts and highlights

4. **Add context with web search:**
   - If stock price dropped, search for "why did [TICKER] stock fall"
   - For earnings data, search for latest analyst opinions
   - Provide market context for significant changes

5. **Structure responses clearly:**
   - Start with the answer to the user's question
   - Show relevant visualizations
   - Add supporting data and context
   - No follow-up questions in responses

Current date: ${new Date().toISOString()}
`;
```

<Note>
  Being specific about **when** to use each visualization type (charts vs tables vs cards) leads to consistent, professional-looking outputs.
</Note>

## Step 5: Thread Management

Enable multi-turn analysis by maintaining conversation context:

<CodeGroup dropdown>
  ```typescript threads.ts theme={null}
  interface Message {
    role: "user" | "assistant";
    content: string;
  }

  const threads = new Map<string, Message[]>();

  export const getThreadHistory = async (threadId: string) => {
    return threads.get(threadId) || [];
  };

  export const saveToThread = async (
    threadId: string,
    userMessage: string,
    assistantMessage: string
  ) => {
    const history = threads.get(threadId) || [];
    history.push(
      { role: "user", content: userMessage },
      { role: "assistant", content: assistantMessage }
    );
    threads.set(threadId, history);
  };
  ```

  ```python threads.py theme={null}
  from typing import List, Dict

  threads: Dict[str, List[Dict[str, str]]] = {}

  async def get_thread_history(thread_id: str) -> List[Dict[str, str]]:
      return threads.get(thread_id, [])

  async def save_to_thread(
      thread_id: str,
      user_message: str,
      assistant_message: str
  ):
      history = threads.get(thread_id, [])
      history.extend([
          {"role": "user", "content": user_message},
          {"role": "assistant", "content": assistant_message},
      ])
      threads[thread_id] = history
  ```
</CodeGroup>

**Why threads matter:**

```
User: "Show me Apple's stock price"
AI: [Shows current price chart]

User: "How does that compare to last quarter?"
AI: [References Apple from context, shows quarterly comparison]
```

## Step 6: Set Up the Frontend UI

Create the conversational analytics interface using C1Chat:

```typescript app/page.tsx theme={null}
"use client";

import { C1Chat } from "@crayonai/react-ui";
import "@crayonai/react-ui/styles/index.css";
import { useState } from "react";

export default function Page() {
  const [threadId] = useState(() => crypto.randomUUID());

  return (
    <div className="min-h-screen bg-gray-50">
      <C1Chat
        apiUrl="/api/chat"
        threadId={threadId}
        placeholder="Ask about stocks, crypto, or financials..."
        title="Financial Data Copilot"
      />
    </div>
  );
}
```

C1Chat provides the complete conversational analytics UI with:

* Message history
* Streaming responses with live chart generation
* Thinking states showing which APIs are being queried
* Automatic thread management for follow-ups
* Responsive design for charts and tables

## Step 7: Run Your Analytics Copilot

Start the development server:

<CodeGroup dropdown>
  ```bash npm theme={null}
  npm run dev
  ```

  ```bash pnpm theme={null}
  pnpm dev
  ```

  ```bash yarn theme={null}
  yarn dev
  ```
</CodeGroup>

Open [http://localhost:3000](http://localhost:3000) and try these queries:

1. **"Show me Tesla's stock price"** - See current price with change indicators
2. **"What's Apple's revenue trend over the last 4 quarters?"** - Get financial statements with charts
3. **"Compare Bitcoin and Ethereum prices"** - See multi-asset comparison
4. Follow up: **"Which one performed better this month?"** - Test thread continuity

<Note>
  The first query might take a few seconds as financial data is fetched. The copilot will show thinking states like "Fetching Stock Price for TSLA" so users know what's happening.
</Note>

## Key Concepts

<AccordionGroup>
  <Accordion title="Why use multiple financial tools instead of one general API?">
    Separate tools let the LLM understand what data is needed. Instead of you parsing "show me Apple's revenue", the LLM sees this needs `get_income_statements` with ticker "AAPL". The LLM becomes your query planner.
  </Accordion>

  <Accordion title="How does C1 choose which chart type to generate?">
    C1 analyzes the data structure and your system prompt. If data has timestamps, it generates time-series charts. If comparing multiple values, it creates comparison charts. Your prompt guides these decisions.
  </Accordion>

  <Accordion title="Should I use Anthropic's Claude directly or C1's wrapper?">
    Use C1 (`c1/anthropic/claude-sonnet-4/v-20250930`) instead of direct Claude API. C1 adds UI generation capabilities on top of Claude's analysis. Direct Claude only returns text.
  </Accordion>

  <Accordion title="Can I add custom visualizations?">
    Yes! Use [Custom Components](/guides/custom-components) to define domain-specific charts like candlestick charts, correlation matrices, or portfolio allocations.
  </Accordion>
</AccordionGroup>

## Going to Production

Before deploying:

1. **Add rate limiting** to prevent API quota exhaustion
2. **Implement error handling** for failed API calls
3. **Monitor API costs** - financial data APIs can get expensive
4. **Add authentication** if serving multiple users

## Full Example & Source Code

<CardGroup cols={2}>
  <Card title="Try Live Demo" icon="play" href="https://analytics-with-c1.vercel.app">
    Experience the full data copilot. Try asking about stocks, crypto, or company financials to see C1 generate custom visualizations.

    **Try it now →**
  </Card>

  <Card title="View Source Code" icon="github" href="https://github.com/thesysdev/analytics-with-c1">
    Complete implementation with all financial tools, thread management, and deployment config.

    **Star on GitHub →**
  </Card>
</CardGroup>
