Skip to main content
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.
Try it above or visit 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

Create Next.js Project

npm
npx create-next-app@latest analytics-with-c1
cd analytics-with-c1
When prompted, select:
  • TypeScript: Yes
  • ESLint: Yes
  • Tailwind CSS: Yes
  • App Router: Yes
  • Customize default import alias: No

Install Dependencies

npm
npm install @thesysai/genui-sdk @crayonai/react-ui openai financial-datasets exa-js zod zod-to-json-schema

Environment Variables

Create a .env.local file:
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
Sign up for Financial Datasets at financialdatasets.ai to get real-time stock, crypto, and financial data.

Step 1: Set Up Financial Data Tools

Create tools that fetch financial data. C1 will call these automatically based on user queries:
tools/financial.ts
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",
      })
    ),
  ];
};
The writeThinkingState calls create a better UX by showing users which data sources are being queried in real-time.

Step 2: Add Web Search for Context

Financial data is more useful with context. Add web search to explain market movements:
tools/web-search.ts
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,
        })),
      });
    },
  },
});

Step 3: Create the Analytics Endpoint

Connect your tools with C1 to create the main analytics endpoint:
app/api/chat/route.ts
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",
    },
  });
}

Step 4: Craft the System Prompt for Analytics

The system prompt guides C1 to create appropriate visualizations:
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()}
`;
Being specific about when to use each visualization type (charts vs tables vs cards) leads to consistent, professional-looking outputs.

Step 5: Thread Management

Enable multi-turn analysis by maintaining conversation context:
threads.ts
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);
};
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:
app/page.tsx
"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:
npm
npm run dev
Open 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
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.

Key Concepts

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.
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.
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.
Yes! Use Custom Components to define domain-specific charts like candlestick charts, correlation matrices, or portfolio allocations.

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