Practical examples

Written By Stanislas

Last updated 16 days ago


Complete, ready-to-use examples for integrating the Swiftask Public Bot API into your application. Learn through working implementations that combine authentication, sessions, messaging, and subscriptions.

Overview

This guide provides five complete, production-ready examples that demonstrate different integration patterns. Each example builds upon the previous guides and shows how to combine multiple API features into cohesive applications. You can copy these examples and adapt them to your specific use case.

The examples progress from a simple complete client implementation to React hooks, Vue composition API, simpler quick-start patterns, and robust error handling.


Prerequisites

Before using these examples, ensure you have:

  1. Read the foundational guides β€” Understand Authentication, Sessions, Chat Operations, and Subscriptions

  2. Apollo Client installed β€” npm install @apollo/client graphql

  3. Your client token β€” From your Swiftask agent's Developer section

  4. Basic JavaScript knowledge β€” These examples use async/await and modern JavaScript

Example 1: Complete chat client

A full-featured chat client that handles authentication, session management, message sending, and real-time streaming in one class.

import { ApolloClient, InMemoryCache, createHttpLink, gql, split } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import { WebSocketLink } from '@apollo/client/link/ws';
import { getMainDefinition } from '@apollo/client/utilities';

class SwiftaskChatClient {
  constructor(clientToken) {
    this.clientToken = clientToken;
    this.clientUuid = this.getOrCreateClientUuid();
    this.apiUrl = 'https://graphql.swiftask.ai';
    this.config = null;
    this.client = null;
  }

  getOrCreateClientUuid() {
    let uuid = localStorage.getItem('swiftask_client_uuid');
    if (!uuid) {
      uuid = crypto.randomUUID();
      localStorage.setItem('swiftask_client_uuid', uuid);
    }
    return uuid;
  }

  async authenticate() {
    console.log('Authenticating...');

    const response = await fetch(`${this.apiUrl}/public/widget-bot/${this.clientToken}`, {
      method: 'GET',
      headers: {
        'x-client-uuid': this.clientUuid,
        'Content-Type': 'application/json',
      },
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.error?.message || 'Authentication failed');
    }

    const { data } = await response.json();
    this.config = data;

    console.log('βœ“ Authenticated successfully');
    this.setupApolloClient();

    return this.config;
  }

  setupApolloClient() {
    const httpLink = createHttpLink({
      uri: `${this.apiUrl}/graphql`,
    });

    const authLink = setContext((_, { headers }) => ({
      headers: {
        ...headers,
        authorization: `Bearer ${this.config.accessToken}`,
        'x-workspace-id': this.config.workspaceId,
        'x-client': 'widget',
      },
    }));

    const wsLink = new WebSocketLink({
      uri: 'wss://graphql.swiftask.ai/graphql',
      options: {
        reconnect: true,
        connectionParams: {
          authorization: `Bearer ${this.config.accessToken}`,
          workspaceId: this.config.workspaceId,
        },
      },
    });

    const splitLink = split(
      ({ query }) => {
        const definition = getMainDefinition(query);
        return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
      },
      wsLink,
      authLink.concat(httpLink)
    );

    this.client = new ApolloClient({
      link: splitLink,
      cache: new InMemoryCache(),
    });
  }

  async createSession(title = 'New Chat') {
    console.log('Creating session:', title);

    const CREATE_SESSION = gql`
      mutation CreateTodoChatSession($data: TodoChatSessionInput!) {
        createTodoChatSession(data: $data) {
          id
          title
          createdAt
          defaultBotId
        }
      }
    `;

    const { data } = await this.client.mutate({
      mutation: CREATE_SESSION,
      variables: {
        data: {
          todoId: this.config.todoId,
          title,
        },
      },
    });

    console.log('βœ“ Session created:', data.createTodoChatSession.id);
    return data.createTodoChatSession;
  }

  async sendMessage(sessionId, message) {
    console.log('Sending message:', message);

    const SEND_MESSAGE = gql`
      mutation SendNewMessage($newMessageData: NewMessageInput!) {
        sendNewMessage(newMessageData: $newMessageData) {
          id
          message
          createdAt
          sessionId
        }
      }
    `;

    const { data } = await this.client.mutate({
      mutation: SEND_MESSAGE,
      variables: {
        newMessageData: {
          message,
          sessionId,
          isForAiReply: true,
        },
      },
    });

    console.log('βœ“ Message sent:', data.sendNewMessage.id);
    return data.sendNewMessage;
  }

  subscribeToMessages(sessionId, onMessage) {
    console.log('Subscribing to messages for session:', sessionId);

    const NEW_MESSAGE_SUBSCRIPTION = gql`
      subscription NewMessage($sessionId: Float!) {
        newMessage(sessionId: $sessionId) {
          id
          message
          createdAt
          sentBy {
            firstName
          }
          isBotReply
        }
      }
    `;

    return this.client
      .subscribe({
        query: NEW_MESSAGE_SUBSCRIPTION,
        variables: { sessionId },
      })
      .subscribe({
        next: ({ data }) => {
          console.log('New message:', data.newMessage.message);
          onMessage(data.newMessage);
        },
        error: (error) => {
          console.error('Subscription error:', error);
        },
        complete: () => {
          console.log('Subscription completed');
        },
      });
  }

  subscribeToStream(sessionId, onChunk, onComplete) {
    console.log('Subscribing to message stream for session:', sessionId);

    const STREAM_SUBSCRIPTION = gql`
      subscription OnMessageStream($sessionId: Float!) {
        onMessageStream(sessionId: $sessionId) {
          messageChunk
          botResponseMessageId
          isStoppable
        }
      }
    `;

    let fullMessage = '';

    return this.client
      .subscribe({
        query: STREAM_SUBSCRIPTION,
        variables: { sessionId },
      })
      .subscribe({
        next: ({ data }) => {
          fullMessage += data.onMessageStream.messageChunk;
          onChunk(fullMessage, data.onMessageStream);
        },
        error: (error) => {
          console.error('Stream error:', error);
        },
        complete: () => {
          console.log('Stream completed');
          if (onComplete) onComplete(fullMessage);
        },
      });
  }

  subscribeToToolCalls(sessionId, onToolCall) {
    const TOOL_SUBSCRIPTION = gql`
      subscription OnAgentToolCall($sessionId: Float!) {
        onAgentToolCall(sessionId: $sessionId) {
          tool
          status
          toolImage
        }
      }
    `;

    return this.client
      .subscribe({
        query: TOOL_SUBSCRIPTION,
        variables: { sessionId },
      })
      .subscribe({
        next: ({ data }) => {
          onToolCall(data.onAgentToolCall);
        },
      });
  }

  async stopGeneration(sessionId, botId) {
    const STOP_MUTATION = gql`
      mutation StopAIResponse($sessionId: Float!, $botId: Float!) {
        stopAIResponse(sessionId: $sessionId, botId: $botId)
      }
    `;

    await this.client.mutate({
      mutation: STOP_MUTATION,
      variables: { sessionId, botId },
    });
  }

  getClient() {
    return this.client;
  }

  getConfig() {
    return this.config;
  }
}

// Usage example
async function main() {
  const chat = new SwiftaskChatClient('your_client_token_here');

  try {
    // Step 1: Authenticate
    const config = await chat.authenticate();
    console.log('Bot slug:', config.botSlug);

    // Step 2: Create a new session
    const session = await chat.createSession('My First Chat');

    // Step 3: Subscribe to new messages
    const messageSub = chat.subscribeToMessages(session.id, (message) => {
      const sender = message.isBotReply ? 'πŸ€– Bot' : 'πŸ‘€ You';
      console.log(`${sender}: ${message.message}`);
    });

    // Step 4: Subscribe to streaming
    const streamSub = chat.subscribeToStream(
      session.id,
      (fullMessage) => {
        console.log('Streaming:', fullMessage);
      },
      (finalMessage) => {
        console.log('βœ“ Response complete');
      }
    );

    // Step 5: Subscribe to tool calls
    const toolSub = chat.subscribeToToolCalls(session.id, (toolCall) => {
      if (toolCall.status === 'START') {
        console.log(`πŸ”§ Using ${toolCall.tool}...`);
      } else if (toolCall.status === 'END') {
        console.log(`βœ“ ${toolCall.tool} completed`);
      }
    });

    // Step 6: Send a message
    await chat.sendMessage(session.id, 'Hello! What can you help me with?');

    // The response will come through the subscriptions

    // Cleanup when done
    setTimeout(() => {
      messageSub.unsubscribe();
      streamSub.unsubscribe();
      toolSub.unsubscribe();
    }, 30000); // Cleanup after 30 seconds for demo
  } catch (error) {
    console.error('Error:', error);
  }
}

// Run the example
main();

Example 2: React hook integration

Use a custom React hook to simplify Swiftask integration in React applications.

import { useState, useEffect, useCallback, useRef } from 'react';
import { SwiftaskChatClient } from './SwiftaskChatClient';

export function useSwiftaskChat(clientToken) {
  const [client, setClient] = useState(null);
  const [session, setSession] = useState(null);
  const [messages, setMessages] = useState([]);
  const [streamingMessage, setStreamingMessage] = useState('');
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);
  const subscriptionsRef = useRef([]);

  // Initialize client and create session
  useEffect(() => {
    const initClient = async () => {
      try {
        const chatClient = new SwiftaskChatClient(clientToken);
        await chatClient.authenticate();

        const newSession = await chatClient.createSession('Chat Session');

        setClient(chatClient);
        setSession(newSession);
        setIsLoading(false);
      } catch (err) {
        setError(err.message);
        setIsLoading(false);
      }
    };

    initClient();
  }, [clientToken]);

  // Setup subscriptions
  useEffect(() => {
    if (!client || !session) return;

    // Subscribe to new messages
    const messageSub = client.subscribeToMessages(session.id, (message) => {
      setMessages((prev) => [...prev, message]);
      setStreamingMessage('');
    });

    // Subscribe to streaming
    const streamSub = client.subscribeToStream(
      session.id,
      (fullMessage) => {
        setStreamingMessage(fullMessage);
      },
      () => {
        setStreamingMessage('');
      }
    );

    subscriptionsRef.current = [messageSub, streamSub];

    // Cleanup
    return () => {
      subscriptionsRef.current.forEach((sub) => sub.unsubscribe());
    };
  }, [client, session]);

  const sendMessage = useCallback(
    async (text) => {
      if (!client || !session) return;

      try {
        await client.sendMessage(session.id, text);
      } catch (err) {
        setError(err.message);
      }
    },
    [client, session]
  );

  return {
    messages,
    streamingMessage,
    sendMessage,
    isLoading,
    error,
    session,
  };
}

// React component using the hook
function ChatComponent() {
  const { messages, streamingMessage, sendMessage, isLoading, error } = useSwiftaskChat(
    'your_client_token'
  );
  const [input, setInput] = useState('');

  const handleSend = () => {
    if (input.trim()) {
      sendMessage(input);
      setInput('');
    }
  };

  if (isLoading) return <div className="loading">Loading chat...</div>;

  if (error) return <div className="error">Error: {error}</div>;

  return (
    <div className="chat-container">
      <div className="messages">
        {messages.map((msg) => (
          <div key={msg.id} className={`message ${msg.isBotReply ? 'bot' : 'user'}`}>
            <strong>{msg.isBotReply ? 'Bot' : 'You'}:</strong> {msg.message}
          </div>
        ))}
        {streamingMessage && <div className="message bot streaming">Bot: {streamingMessage}</div>}
      </div>

      <div className="input-area">
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && handleSend()}
          placeholder="Type a message..."
          disabled={isLoading}
        />
        <button onClick={handleSend} disabled={isLoading}>
          Send
        </button>
      </div>
    </div>
  );
}

export default ChatComponent;

Example 3: Vue composition API integration

Integrate Swiftask into Vue 3 applications using the Composition API.

import { ref, onMounted, onUnmounted } from 'vue';
import { SwiftaskChatClient } from './SwiftaskChatClient';

export function useSwiftaskChat(clientToken) {
  const client = ref(null);
  const session = ref(null);
  const messages = ref([]);
  const streamingMessage = ref('');
  const isLoading = ref(true);
  const error = ref(null);
  const subscriptions = [];

  const initialize = async () => {
    try {
      const chatClient = new SwiftaskChatClient(clientToken);
      await chatClient.authenticate();

      const newSession = await chatClient.createSession('Chat Session');

      client.value = chatClient;
      session.value = newSession;

      setupSubscriptions();

      isLoading.value = false;
    } catch (err) {
      error.value = err.message;
      isLoading.value = false;
    }
  };

  const setupSubscriptions = () => {
    const messageSub = client.value.subscribeToMessages(session.value.id, (message) => {
      messages.value.push(message);
      streamingMessage.value = '';
    });

    const streamSub = client.value.subscribeToStream(
      session.value.id,
      (fullMessage) => {
        streamingMessage.value = fullMessage;
      },
      () => {
        streamingMessage.value = '';
      }
    );

    subscriptions.push(messageSub, streamSub);
  };

  const sendMessage = async (text) => {
    if (!client.value || !session.value) return;
    try {
      await client.value.sendMessage(session.value.id, text);
    } catch (err) {
      error.value = err.message;
    }
  };

  onMounted(initialize);

  onUnmounted(() => {
    subscriptions.forEach((sub) => sub.unsubscribe());
  });

  return {
    messages,
    streamingMessage,
    sendMessage,
    isLoading,
    error,
  };
}

// Vue component using the composition function
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'ChatComponent',
  setup() {
    const { messages, streamingMessage, sendMessage, isLoading, error } = useSwiftaskChat(
      'your_client_token'
    );

    const input = ref('');

    const handleSend = () => {
      if (input.value.trim()) {
        sendMessage(input.value);
        input.value = '';
      }
    };

    return {
      messages,
      streamingMessage,
      input,
      handleSend,
      isLoading,
      error,
    };
  },

  template: `
    <div class="chat-container" v-if="!isLoading">
      <div v-if="error" class="error">Error: {{ error }}</div>

      <div class="messages">
        <div v-for="msg in messages" :key="msg.id" :class="['message', msg.isBotReply ? 'bot' : 'user']">
          <strong>{{ msg.isBotReply ? 'Bot' : 'You' }}:</strong> {{ msg.message }}
        </div>
        <div v-if="streamingMessage" class="message bot streaming">
          Bot: {{ streamingMessage }}
        </div>
      </div>

      <div class="input-area">
        <input
          v-model="input"
          @keypress.enter="handleSend"
          placeholder="Type a message..."
          :disabled="isLoading"
        />
        <button @click="handleSend" :disabled="isLoading">Send</button>
      </div>
    </div>

    <div v-else class="loading">Loading chat...</div>
  `,
});

Example 4: Quick start with starter session

For simple use cases, use the automatically created starter session without creating a new one.

class SimpleSwiftaskChat {
  constructor(clientToken) {
    this.clientToken = clientToken;
    this.clientUuid = crypto.randomUUID();
    this.client = null;
    this.sessionId = null;
  }

  async initialize() {
    // Step 1: Authenticate
    const response = await fetch(
      `https://graphql.swiftask.ai/public/widget-bot/${this.clientToken}`,
      {
        headers: { 'x-client-uuid': this.clientUuid },
      }
    );

    const { data } = await response.json();

    // Step 2: Setup GraphQL client
    const httpLink = createHttpLink({
      uri: 'https://graphql.swiftask.ai/graphql',
    });

    const authLink = setContext((_, { headers }) => ({
      headers: {
        ...headers,
        authorization: `Bearer ${data.accessToken}`,
        'x-workspace-id': data.workspaceId,
        'x-client': 'widget',
      },
    }));

    this.client = new ApolloClient({
      link: authLink.concat(httpLink),
      cache: new InMemoryCache(),
    });

    // Step 3: Use the starter session (no need to create)
    this.sessionId = data.starterSessionId;

    console.log('Ready to chat in session:', this.sessionId);
  }

  async sendMessage(messageText) {
    const SEND_MESSAGE = gql`
      mutation SendNewMessage($newMessageData: NewMessageInput!) {
        sendNewMessage(newMessageData: $newMessageData) {
          id
          message
        }
      }
    `;

    const { data } = await this.client.mutate({
      mutation: SEND_MESSAGE,
      variables: {
        newMessageData: {
          message: messageText,
          sessionId: this.sessionId,
          isForAiReply: true,
        },
      },
    });

    return data.sendNewMessage;
  }

  subscribeToStream(onChunk) {
    const STREAM = gql`
      subscription OnMessageStream($sessionId: Float!) {
        onMessageStream(sessionId: $sessionId) {
          messageChunk
        }
      }
    `;

    let fullMessage = '';

    return this.client
      .subscribe({
        query: STREAM,
        variables: { sessionId: this.sessionId },
      })
      .subscribe({
        next: ({ data }) => {
          fullMessage += data.onMessageStream.messageChunk;
          onChunk(fullMessage);
        },
      });
  }
}

// Usage - much simpler!
async function main() {
  const chat = new SimpleSwiftaskChat('your_client_token');
  await chat.initialize();

  // Start chatting immediately
  await chat.sendMessage('Hello!');

  chat.subscribeToStream((fullMessage) => {
    console.log('Response:', fullMessage);
  });
}

Example 5: Robust error handling and retry logic

Production-ready implementation with comprehensive error handling and automatic retries.

class RobustSwiftaskChat {
  constructor(clientToken, options = {}) {
    this.clientToken = clientToken;
    this.clientUuid = crypto.randomUUID();
    this.client = null;
    this.sessionId = null;
    this.maxRetries = options.maxRetries || 3;
    this.retryDelay = options.retryDelay || 1000;
    this.onError = options.onError || console.error;
    this.onSuccess = options.onSuccess || console.log;
  }

  async retryOperation(operation, retries = this.maxRetries) {
    try {
      return await operation();
    } catch (error) {
      if (retries > 0 && this.isRetryableError(error)) {
        console.log(`Retrying... (${this.maxRetries - retries + 1}/${this.maxRetries})`);
        await this.sleep(this.retryDelay);
        return this.retryOperation(operation, retries - 1);
      }
      throw error;
    }
  }

  isRetryableError(error) {
    // Don't retry auth errors
    if (error.message?.includes('Unauthorized') || error.message?.includes('Authentication failed')) {
      return false;
    }
    // Retry network errors
    if (error instanceof TypeError || error.message?.includes('network')) {
      return true;
    }
    // Retry server errors (5xx)
    if (error.status >= 500) {
      return true;
    }
    return true;
  }

  sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  async initialize() {
    try {
      await this.retryOperation(async () => {
        const response = await fetch(
          `https://graphql.swiftask.ai/public/widget-bot/${this.clientToken}`,
          {
            headers: { 'x-client-uuid': this.clientUuid },
          }
        );

        if (!response.ok) {
          throw new Error(`Authentication failed: ${response.status}`);
        }

        const { data } = await response.json();

        // Setup client
        const httpLink = createHttpLink({
          uri: 'https://graphql.swiftask.ai/graphql',
        });

        const authLink = setContext((_, { headers }) => ({
          headers: {
            ...headers,
            authorization: `Bearer ${data.accessToken}`,
            'x-workspace-id': data.workspaceId,
            'x-client': 'widget',
          },
        }));

        this.client = new ApolloClient({
          link: authLink.concat(httpLink),
          cache: new InMemoryCache(),
        });

        this.sessionId = data.starterSessionId;
        this.onSuccess('Chat initialized successfully');
      });
    } catch (error) {
      this.onError('Failed to initialize chat:', error);
      throw error;
    }
  }

  async sendMessage(messageText) {
    try {
      return await this.retryOperation(async () => {
        const SEND_MESSAGE = gql`
          mutation SendNewMessage($newMessageData: NewMessageInput!) {
            sendNewMessage(newMessageData: $newMessageData) {
              id
              message
            }
          }
        `;

        const { data } = await this.client.mutate({
          mutation: SEND_MESSAGE,
          variables: {
            newMessageData: {
              message: messageText,
              sessionId: this.sessionId,
              isForAiReply: true,
            },
          },
        });

        return data.sendNewMessage;
      });
    } catch (error) {
      this.onError('Failed to send message:', error);
      throw error;
    }
  }

  subscribeToStreamWithErrorHandling(onChunk, onError) {
    const STREAM = gql`
      subscription OnMessageStream($sessionId: Float!) {
        onMessageStream(sessionId: $sessionId) {
          messageChunk
        }
      }
    `;

    let fullMessage = '';
    let retryCount = 0;

    const subscribe = () => {
      return this.client
        .subscribe({
          query: STREAM,
          variables: { sessionId: this.sessionId },
        })
        .subscribe({
          next: ({ data }) => {
            fullMessage += data.onMessageStream.messageChunk;
            onChunk(fullMessage);
            retryCount = 0; // Reset retry count on success
          },
          error: (error) => {
            console.error('Stream error:', error);

            if (error.message?.includes('authorization')) {
              onError('Authentication expired. Please refresh.');
            } else if (error.message?.includes('network')) {
              retryCount++;
              if (retryCount < 3) {
                console.log(`Reconnecting... (${retryCount}/3)`);
                setTimeout(() => {
                  subscribe();
                }, 5000);
              } else {
                onError('Connection lost. Please try again.');
              }
            } else {
              onError('Stream error: ' + error.message);
            }
          },
          complete: () => {
            this.onSuccess('Stream completed');
          },
        });
    };

    return subscribe();
  }
}

// Usage with error handling
async function main() {
  const chat = new RobustSwiftaskChat('your_client_token', {
    maxRetries: 3,
    retryDelay: 1000,
    onError: (message, error) => {
      console.error(message, error);
      showErrorToUser(message);
    },
    onSuccess: (message) => {
      console.log(message);
    },
  });

  try {
    await chat.initialize();

    await chat.sendMessage('Hello!');

    chat.subscribeToStreamWithErrorHandling(
      (fullMessage) => {
        updateUI(fullMessage);
      },
      (errorMessage) => {
        showErrorToUser(errorMessage);
      }
    );
  } catch (error) {
    console.error('Fatal error:', error);
  }
}

function showErrorToUser(message) {
  // Show error in UI
  const errorElement = document.createElement('div');
  errorElement.className = 'error-message';
  errorElement.textContent = message;
  document.body.appendChild(errorElement);
}

function updateUI(message) {
  const messageElement = document.getElementById('response');
  messageElement.textContent = message;
}

Common patterns

Handling multiple messages

// Send multiple messages in sequence
async function sendMultipleMessages(chat, sessionId, messages) {
  for (const msg of messages) {
    await chat.sendMessage(sessionId, msg);
    await new Promise((resolve) => setTimeout(resolve, 500)); // Delay between messages
  }
}

// Usage
await sendMultipleMessages(chat, sessionId, [
  'What is your pricing?',
  'Do you offer discounts?',
  'Can I try for free?',
]);

Handling session persistence

// Save session ID to localStorage
function saveSession(sessionId) {
  localStorage.setItem('swiftask_session_id', sessionId);
}

// Retrieve session on page reload
function getPersistedSession() {
  return localStorage.getItem('swiftask_session_id');
}

// Usage
const persistedSessionId = getPersistedSession();
if (persistedSessionId) {
  // Continue with existing session
  await chat.sendMessage(persistedSessionId, 'Hello again!');
} else {
  // Create new session
  const session = await chat.createSession();
  saveSession(session.id);
}

Handling user input validation

function validateMessage(message) {
  if (!message || message.trim().length === 0) {
    throw new Error('Message cannot be empty');
  }

  if (message.length > 5000) {
    throw new Error('Message is too long (max 5000 characters)');
  }

  return message.trim();
}

// Usage
const handleSend = async (input) => {
  try {
    const validatedMessage = validateMessage(input);
    await chat.sendMessage(sessionId, validatedMessage);
  } catch (error) {
    showErrorToUser(error.message);
  }
};