Skip to main content

Quick start

The Data Agent exposes a simple REST API. You can integrate it into any application that can make HTTP requests.
curl -X POST https://your-data-agent.com/v1/agent/analyze \
  -H "Content-Type: application/json" \
  -d '{
    "query": "How many conversations did I have today?",
    "tenant_id": "your-tenant-id"
  }'

Request format

POST /v1/agent/analyze

Performs a complete analysis and returns when finished.
{
  "query": "What are my top 5 clusters?",
  "tenant_id": "your-tenant-id",
  "max_iterations": 30
}
FieldTypeRequiredDescription
querystringYesNatural language question
tenant_idstringYesYour Moda tenant ID
max_iterationsintegerNoMaximum reasoning steps (default: 30)

Response format

{
  "answer": "Based on your data, here are your top 5 clusters:\n\n1. Support requests (1,234 conversations)\n2. Product questions (987 conversations)\n...",
  "path": "deep",
  "iterations": 4,
  "execution_time": 12.5,
  "success": true,
  "error": null
}
FieldTypeDescription
answerstringNatural language response
pathstringAlways “deep” for this endpoint
iterationsintegerNumber of reasoning steps taken
execution_timefloatTotal processing time in seconds
successbooleanWhether analysis completed successfully
errorstringError message if success is false

Streaming integration

For real-time status updates, use the streaming endpoint. This is recommended for user-facing interfaces where you want to show progress.

POST /v1/agent/stream

Returns Server-Sent Events (SSE) as the agent processes your query.
async function streamQuery(query: string, tenantId: string) {
  const response = await fetch('https://your-data-agent.com/v1/agent/stream', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ query, tenant_id: tenantId }),
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const text = decoder.decode(value);
    const lines = text.split('\n');

    for (const line of lines) {
      if (line.startsWith('data: ')) {
        const event = JSON.parse(line.slice(6));

        if (event.type === 'status') {
          // Show progress to user
          console.log(`Step ${event.data.step}: ${event.data.title}`);
          console.log(event.data.description);
        } else if (event.type === 'final') {
          // Display final answer
          console.log('Answer:', event.data.answer);
          return event.data;
        } else if (event.type === 'error') {
          throw new Error(event.data.message);
        }
      }
    }
  }
}

// Usage
const result = await streamQuery('What are my top clusters?', 'tenant-123');

SSE event types

Event TypeDescriptionData Fields
statusProgress updatetitle, description, actions, step
finalComplete answeranswer, iterations, execution_time, success
errorError occurredmessage

Status event structure

{
  "type": "status",
  "data": {
    "title": "Querying cluster data",
    "description": "Fetching cluster statistics from the database",
    "actions": [
      {
        "type": "tool_call",
        "name": "cluster_stats",
        "detail": "Getting top clusters by size"
      }
    ],
    "step": 2
  }
}

React integration example

Here’s a complete React component for integrating the Data Agent:
import { useState, useCallback } from 'react';

interface Message {
  role: 'user' | 'assistant';
  content: string;
  status?: string;
}

export function DataAgentChat({ tenantId }: { tenantId: string }) {
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState('');
  const [isLoading, setIsLoading] = useState(false);

  const sendQuery = useCallback(async (query: string) => {
    setMessages(prev => [...prev, { role: 'user', content: query }]);
    setIsLoading(true);

    // Add placeholder for assistant response
    const assistantIndex = messages.length + 1;
    setMessages(prev => [...prev, { role: 'assistant', content: '', status: 'Thinking...' }]);

    try {
      const response = await fetch('/api/agent/stream', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ query, tenant_id: tenantId }),
      });

      const reader = response.body?.getReader();
      const decoder = new TextDecoder();

      while (reader) {
        const { done, value } = await reader.read();
        if (done) break;

        const lines = decoder.decode(value).split('\n');
        for (const line of lines) {
          if (line.startsWith('data: ')) {
            const event = JSON.parse(line.slice(6));

            if (event.type === 'status') {
              setMessages(prev => prev.map((msg, i) =>
                i === assistantIndex
                  ? { ...msg, status: event.data.title }
                  : msg
              ));
            } else if (event.type === 'final') {
              setMessages(prev => prev.map((msg, i) =>
                i === assistantIndex
                  ? { ...msg, content: event.data.answer, status: undefined }
                  : msg
              ));
            }
          }
        }
      }
    } catch (error) {
      setMessages(prev => prev.map((msg, i) =>
        i === assistantIndex
          ? { ...msg, content: 'An error occurred', status: undefined }
          : msg
      ));
    } finally {
      setIsLoading(false);
    }
  }, [tenantId, messages.length]);

  return (
    <div className="flex flex-col h-full">
      <div className="flex-1 overflow-y-auto p-4 space-y-4">
        {messages.map((msg, i) => (
          <div key={i} className={msg.role === 'user' ? 'text-right' : 'text-left'}>
            {msg.status && <div className="text-sm text-gray-500">{msg.status}</div>}
            <div className="inline-block p-3 rounded-lg bg-gray-100">
              {msg.content}
            </div>
          </div>
        ))}
      </div>
      <form onSubmit={(e) => { e.preventDefault(); sendQuery(input); setInput(''); }}>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Ask about your data..."
          disabled={isLoading}
          className="w-full p-3 border rounded"
        />
      </form>
    </div>
  );
}

Rate limiting

There are no built-in rate limits, but each query can take 5-30 seconds depending on complexity. Consider:
  • Showing loading states to users
  • Implementing client-side debouncing
  • Caching common queries if appropriate