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

# Build a Chatbot

> End-to-end tutorial: build a customer support chatbot with Cuadra AI in 30 minutes. Includes knowledge base, system prompts, and React UI.

## What You'll Build

A customer support chatbot that:

* Answers questions from your documentation
* Maintains conversation context
* Streams responses in real-time
* Displays source citations

**Time:** 30 minutes\
**Prerequisites:** Cuadra AI account, Node.js 18+

***

## Step 1: Create a Model

Create an AI model via the Dashboard or API. First, pick a base model from the catalog, then create your custom model. For API access, authenticate via M2M OAuth 2.0:

<CodeGroup>
  ```bash curl theme={null}
  # Get a parent model ID from the catalog
  curl https://api.cuadra.ai/v1/models/catalog \
    -H "Authorization: Bearer $ACCESS_TOKEN"

  # Create your model
  curl -X POST https://api.cuadra.ai/v1/models \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: create-model-001" \
    -d '{
      "parentModelId": "PARENT_MODEL_ID_FROM_CATALOG",
      "displayName": "Support Bot"
    }'
  ```

  ```python Python theme={null}
  import httpx

  # Get a parent model ID from the catalog
  catalog = httpx.get(
      "https://api.cuadra.ai/v1/models/catalog",
      headers={"Authorization": f"Bearer {ACCESS_TOKEN}"}
  ).json()
  parent_id = catalog["items"][0]["id"]

  # Create your model
  response = httpx.post(
      "https://api.cuadra.ai/v1/models",
      headers={
          "Authorization": f"Bearer {ACCESS_TOKEN}",
          "Idempotency-Key": "create-model-001"
      },
      json={"parentModelId": parent_id, "displayName": "Support Bot"}
  )
  model = response.json()
  print(f"Created model: {model['id']}")
  ```

  ```typescript Node.js theme={null}
  // Get a parent model ID from the catalog
  const catalogRes = await fetch('https://api.cuadra.ai/v1/models/catalog', {
    headers: { 'Authorization': `Bearer ${ACCESS_TOKEN}` }
  });
  const catalog = await catalogRes.json();
  const parentId = catalog.items[0].id;

  // Create your model
  const response = await fetch('https://api.cuadra.ai/v1/models', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${ACCESS_TOKEN}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': 'create-model-001'
    },
    body: JSON.stringify({
      parentModelId: parentId,
      displayName: 'Support Bot'
    })
  });
  const model = await response.json();
  console.log(`Created model: ${model.id}`);
  ```
</CodeGroup>

Save the returned `id` (e.g., `model_abc123`).

***

## Step 2: Create a Knowledge Base

Upload your documentation:

### Create Dataset

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.cuadra.ai/v1/datasets \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: create-dataset-001" \
    -d '{"name": "Product Docs"}'
  ```

  ```python Python theme={null}
  import httpx

  response = httpx.post(
      "https://api.cuadra.ai/v1/datasets",
      headers={
          "Authorization": f"Bearer {ACCESS_TOKEN}",
          "Idempotency-Key": "create-dataset-001"
      },
      json={"name": "Product Docs"}
  )
  dataset = response.json()
  print(f"Created dataset: {dataset['id']}")
  ```

  ```typescript Node.js theme={null}
  const response = await fetch('https://api.cuadra.ai/v1/datasets', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${ACCESS_TOKEN}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': 'create-dataset-001'
    },
    body: JSON.stringify({ name: 'Product Docs' })
  });
  const dataset = await response.json();
  console.log(`Created dataset: ${dataset.id}`);
  ```
</CodeGroup>

### Upload Documents

Upload files and associate them with the dataset:

<CodeGroup>
  ```bash curl theme={null}
  # Step 1: Upload the file
  FILE_RESPONSE=$(curl -X POST https://api.cuadra.ai/v1/files \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Idempotency-Key: upload-doc-001" \
    -F "file=@docs/getting-started.pdf")
  FILE_ID=$(echo $FILE_RESPONSE | jq -r '.id')

  # Step 2: Associate with dataset
  curl -X POST https://api.cuadra.ai/v1/files/$FILE_ID/associations \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"datasetId": "ds_xyz"}'
  ```

  ```python Python theme={null}
  import httpx

  # Step 1: Upload the file
  with open("docs/getting-started.pdf", "rb") as f:
      file_response = httpx.post(
          "https://api.cuadra.ai/v1/files",
          headers={
              "Authorization": f"Bearer {ACCESS_TOKEN}",
              "Idempotency-Key": "upload-doc-001"
          },
          files={"file": f}
      )
  file = file_response.json()
  print(f"Uploaded file: {file['id']}")

  # Step 2: Associate with dataset
  httpx.post(
      f"https://api.cuadra.ai/v1/files/{file['id']}/associations",
      headers={"Authorization": f"Bearer {ACCESS_TOKEN}"},
      json={"datasetId": "ds_xyz"}
  )
  print("File added to dataset")
  ```

  ```typescript Node.js theme={null}
  import fs from 'fs';
  import FormData from 'form-data';

  // Step 1: Upload the file
  const formData = new FormData();
  formData.append('file', fs.createReadStream('docs/getting-started.pdf'));

  const fileResponse = await fetch('https://api.cuadra.ai/v1/files', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${ACCESS_TOKEN}`,
      'Idempotency-Key': 'upload-doc-001'
    },
    body: formData
  });
  const file = await fileResponse.json();
  console.log(`Uploaded file: ${file.id}`);

  // Step 2: Associate with dataset
  await fetch(`https://api.cuadra.ai/v1/files/${file.id}/associations`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${ACCESS_TOKEN}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ datasetId: 'ds_xyz' })
  });
  console.log('File added to dataset');
  ```
</CodeGroup>

***

## Step 3: Link Dataset to Model

Connect your knowledge base:

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.cuadra.ai/v1/models/model_abc123/datasets \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: link-dataset-001" \
    -d '{"datasetId": "ds_xyz", "usageType": "rag"}'
  ```

  ```python Python theme={null}
  import httpx

  response = httpx.post(
      "https://api.cuadra.ai/v1/models/model_abc123/datasets",
      headers={
          "Authorization": f"Bearer {ACCESS_TOKEN}",
          "Idempotency-Key": "link-dataset-001"
      },
      json={"datasetId": "ds_xyz", "usageType": "rag"}
  )
  print("Dataset linked to model")
  ```

  ```typescript Node.js theme={null}
  const response = await fetch('https://api.cuadra.ai/v1/models/model_abc123/datasets', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${ACCESS_TOKEN}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': 'link-dataset-001'
    },
    body: JSON.stringify({ datasetId: 'ds_xyz', usageType: 'rag' })
  });
  console.log('Dataset linked to model');
  ```
</CodeGroup>

***

## Step 4: Add System Prompt

Create a particle for the bot's behavior:

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.cuadra.ai/v1/particles \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: create-particle-001" \
    -d '{
      "name": "Support Role",
      "category": "role",
      "content": "You are a helpful support agent. Answer questions based on the provided documentation. If unsure, say so and suggest contacting support@example.com."
    }'
  ```

  ```python Python theme={null}
  import httpx

  response = httpx.post(
      "https://api.cuadra.ai/v1/particles",
      headers={
          "Authorization": f"Bearer {ACCESS_TOKEN}",
          "Idempotency-Key": "create-particle-001"
      },
      json={
          "name": "Support Role",
          "category": "role",
          "content": "You are a helpful support agent. Answer questions based on the provided documentation. If unsure, say so and suggest contacting support@example.com."
      }
  )
  particle = response.json()
  print(f"Created particle: {particle['id']}")
  ```

  ```typescript Node.js theme={null}
  const response = await fetch('https://api.cuadra.ai/v1/particles', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${ACCESS_TOKEN}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': 'create-particle-001'
    },
    body: JSON.stringify({
      name: 'Support Role',
      category: 'role',
      content: 'You are a helpful support agent. Answer questions based on the provided documentation. If unsure, say so and suggest contacting support@example.com.'
    })
  });
  const particle = await response.json();
  console.log(`Created particle: ${particle.id}`);
  ```
</CodeGroup>

### Create System Prompt

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.cuadra.ai/v1/system-prompts \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: create-sysprompt-001" \
    -d '{
      "name": "Support Prompt",
      "particles": [{"particleId": "particle_xxx", "order": 1}]
    }'
  ```

  ```python Python theme={null}
  import httpx

  response = httpx.post(
      "https://api.cuadra.ai/v1/system-prompts",
      headers={
          "Authorization": f"Bearer {ACCESS_TOKEN}",
          "Idempotency-Key": "create-sysprompt-001"
      },
      json={
          "name": "Support Prompt",
          "particles": [{"particleId": "particle_xxx", "order": 1}]
      }
  )
  system_prompt = response.json()
  print(f"Created system prompt: {system_prompt['id']}")
  ```

  ```typescript Node.js theme={null}
  const response = await fetch('https://api.cuadra.ai/v1/system-prompts', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${ACCESS_TOKEN}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': 'create-sysprompt-001'
    },
    body: JSON.stringify({
      name: 'Support Prompt',
      particles: [{ particleId: 'particle_xxx', order: 1 }]
    })
  });
  const systemPrompt = await response.json();
  console.log(`Created system prompt: ${systemPrompt.id}`);
  ```
</CodeGroup>

### Attach to Model

<CodeGroup>
  ```bash curl theme={null}
  curl -X PATCH https://api.cuadra.ai/v1/models/model_abc123 \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"systemPromptId": "sysprompt_yyy"}'
  ```

  ```python Python theme={null}
  import httpx

  response = httpx.patch(
      "https://api.cuadra.ai/v1/models/model_abc123",
      headers={"Authorization": f"Bearer {ACCESS_TOKEN}"},
      json={"systemPromptId": "sysprompt_yyy"}
  )
  print("System prompt attached to model")
  ```

  ```typescript Node.js theme={null}
  const response = await fetch('https://api.cuadra.ai/v1/models/model_abc123', {
    method: 'PATCH',
    headers: {
      'Authorization': `Bearer ${ACCESS_TOKEN}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ systemPromptId: 'sysprompt_yyy' })
  });
  console.log('System prompt attached to model');
  ```
</CodeGroup>

***

## Step 5: Test via API

Verify everything works:

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.cuadra.ai/v1/chats \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: test-chat-001" \
    -d '{
      "modelId": "model_abc123",
      "messages": [{"role": "user", "content": "How do I get started?"}]
    }'
  ```

  ```python Python theme={null}
  import httpx

  response = httpx.post(
      "https://api.cuadra.ai/v1/chats",
      headers={
          "Authorization": f"Bearer {ACCESS_TOKEN}",
          "Idempotency-Key": "test-chat-001"
      },
      json={
          "modelId": "model_abc123",
          "messages": [{"role": "user", "content": "How do I get started?"}]
      }
  )
  result = response.json()
  print(f"Response: {result['message']['content']}")

  # Display sources if available
  if result.get('sources'):
      print("\nSources:")
      for source in result['sources']:
          print(f"  - {source['filename']} (score: {source['score']:.2f})")
  ```

  ```typescript Node.js theme={null}
  const response = await fetch('https://api.cuadra.ai/v1/chats', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${ACCESS_TOKEN}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': 'test-chat-001'
    },
    body: JSON.stringify({
      modelId: 'model_abc123',
      messages: [{ role: 'user', content: 'How do I get started?' }]
    })
  });
  const result = await response.json();
  console.log(`Response: ${result.message.content}`);

  // Display sources if available
  if (result.sources) {
    console.log('\nSources:');
    result.sources.forEach(source => {
      console.log(`  - ${source.filename} (score: ${source.score.toFixed(2)})`);
    });
  }
  ```
</CodeGroup>

You should see a response with content from your documentation and source citations.

***

## Step 6: Build the React UI

Install the Cuadra AI UI Kit:

```bash theme={null}
npm install @cuadra-ai/uikit
```

Create the chat component:

```tsx theme={null}
// src/components/SupportChat.tsx
import { CuadraChat } from '@cuadra-ai/uikit';

export function SupportChat({ sessionToken }: { sessionToken: string }) {
  return (
    <div style={{ height: '600px', width: '400px' }}>
      <CuadraChat
        connection={{
          baseUrl: "https://api.cuadra.ai",
          sessionToken: sessionToken
        }}
        chat={{
          modelId: "model_abc123",
          mode: "multiChat"
        }}
      />
    </div>
  );
}
```

***

## Step 7: User Authentication

Users authenticate via your Stytch B2B integration. The session token from Stytch is passed to the UI Kit.

### Frontend Integration

```tsx theme={null}
import { useState, useEffect } from 'react';
import { CuadraChat } from '@cuadra-ai/uikit';

export function App() {
  // Session token from your Stytch B2B authentication
  const [sessionToken, setSessionToken] = useState<string | null>(null);

  useEffect(() => {
    // Your auth system provides the session token after user login
    const token = getStytchSessionToken(); // From your auth implementation
    setSessionToken(token);
  }, []);

  if (!sessionToken) {
    return <div>Please log in</div>;
  }

  return (
    <CuadraChat
      connection={{
        baseUrl: "https://api.cuadra.ai",
        sessionToken: sessionToken
      }}
      chat={{
        modelId: "model_abc123",
        mode: "multiChat"
      }}
    />
  );
}
```

### Proxy Mode (Alternative)

If you prefer backend-handled auth, route requests through your backend:

**Frontend:**

```tsx theme={null}
<CuadraChat
  connection={{
    proxyUrl: "/api/chat"  // Your backend handles auth
  }}
  chat={{
    modelId: "model_abc123"
  }}
/>
```

**Backend Proxy:**

<CodeGroup>
  ```python Python (FastAPI) theme={null}
  from fastapi import FastAPI, Request
  import httpx

  app = FastAPI()

  @app.post("/api/chat")
  async def proxy_chat(request: Request):
      body = await request.json()
      
      async with httpx.AsyncClient() as client:
          response = await client.post(
              "https://api.cuadra.ai/v1/chats",
              headers={
                  "Authorization": f"Bearer {M2M_ACCESS_TOKEN}",
                  "Content-Type": "application/json",
              },
              json=body
          )
          return response.json()
  ```

  ```typescript Node.js (Express) theme={null}
  const express = require('express');
  const app = express();

  app.use(express.json());

  app.post('/api/chat', async (req, res) => {
    const response = await fetch('https://api.cuadra.ai/v1/chats', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.M2M_ACCESS_TOKEN}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(req.body)
    });
    
    const data = await response.json();
    res.json(data);
  });
  ```
</CodeGroup>

***

## Next Steps

<CardGroup cols="2">
  <Card title="Knowledge Bases" icon="database" href="/guides/knowledge-bases">
    Add more data sources
  </Card>

  <Card title="System Prompts" icon="sliders" href="/guides/system-prompts">
    Customize behavior
  </Card>

  <Card title="Streaming" icon="bolt" href="/api-reference/chat">
    Real-time responses
  </Card>

  <Card title="Usage" icon="chart-bar" href="/api-reference/usage">
    Track usage
  </Card>
</CardGroup>
