Skip to content
StudyFetch API

API References

Chat

Overview

The Chat component creates an AI-powered assistant that answers questions from your study materials. It searches your materials (RAG), can search the web, cites its sources, and suggests follow-up questions.

There are two ways to use it:

  • Embed it. Generate an embed URL for a learner and render it in an iframe, or use the React Native components. The embed handles history, voice input, feedback, and streaming for you.
  • Call the API from your server. Stream answers with POST /api/v1/chat/stream and build your own interface.

Either way you can push context about what the learner is looking at, read feedback, and export the questions learners asked.

Creating a Chat Component

JavaScript

import StudyfetchSDK from '@studyfetch/sdk';

const client = new StudyfetchSDK({
  apiKey: 'your-api-key',
  baseURL: 'https://studyfetchapi.com',
});

const chatComponent = await client.v1.components.create({
  name: 'Biology Study Assistant',
  type: 'chat',
  config: {
    model: 'gemini-3-flash-preview',
    materials: ['mat-123', 'mat-456'],
    folders: ['folder-789'],
    systemPrompt: 'You are a helpful biology tutor. Answer questions based on the provided materials.',
    enableWebSearch: false,
    enableRAGSearch: true,
    maxSteps: 5,
    enableReferenceMode: false,
  },
});

console.log('Chat component created:', chatComponent._id, chatComponent.componentId);

Python

from studyfetch_sdk import StudyfetchSDK

client = StudyfetchSDK(
    api_key="your-api-key",
    base_url="https://studyfetchapi.com",
)

chat_component = client.v1.components.create(
    name="Biology Study Assistant",
    type="chat",
    config={
        "model": "gemini-3-flash-preview",
        "materials": ["mat-123", "mat-456"],
        "folders": ["folder-789"],
        "system_prompt": "You are a helpful biology tutor. Answer questions based on the provided materials.",
        "enable_web_search": False,
        "enable_rag_search": True,
        "max_steps": 5,
        "enable_reference_mode": False,
    },
)

print(f"Chat component created: {chat_component.api_id} {chat_component.component_id}")

Java

import com.studyfetch.javasdk.client.StudyfetchSdkClient;
import com.studyfetch.javasdk.client.okhttp.StudyfetchSdkOkHttpClient;
import com.studyfetch.javasdk.models.v1.components.ComponentResponse;
import com.studyfetch.javasdk.models.v1.components.ComponentCreateParams;

import java.util.List;

public class CreateChatComponent {
    public static void main(String[] args) {
        StudyfetchSdkClient client = StudyfetchSdkOkHttpClient.builder()
            .fromEnv()
            .baseUrl("https://studyfetchapi.com")
            .build();

        ComponentCreateParams params = ComponentCreateParams.builder()
            .name("Biology Study Assistant")
            .type(ComponentCreateParams.Type.CHAT)
            .config(ComponentCreateParams.Config.ChatConfigDto.builder()
                .model("gemini-3-flash-preview")
                .materials(List.of("mat-123", "mat-456"))
                .folders(List.of("folder-789"))
                .systemPrompt("You are a helpful biology tutor. Answer questions based on the provided materials.")
                .enableWebSearch(false)
                .enableRagSearch(true)
                .maxSteps(5.0)
                .enableReferenceMode(false)
                .build())
            .build();

        ComponentResponse component = client.v1().components().create(params);
        System.out.println("Chat component created: " + component._id());
    }
}

C#

using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Components;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

public class CreateChatComponent
{
    public static async Task CreateChat()
    {
        var client = new StudyfetchSDKClient()
        {
            APIKey = Environment.GetEnvironmentVariable("STUDYFETCH_API_KEY"),
            BaseUrl = new Uri("https://studyfetchapi.com")
        };

        var chatComponent = await client.V1.Components.Create(new()
        {
            Name = "Biology Study Assistant",
            Type = StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.Type.Chat,
            Config = new StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.ConfigProperties.ChatConfigDto()
            {
                Model = "gemini-3-flash-preview",
                Materials = new List<string> { "mat-123", "mat-456" },
                Folders = new List<string> { "folder-789" },
                SystemPrompt = "You are a helpful biology tutor. Answer questions based on the provided materials.",
                EnableWebSearch = false,
                EnableRagSearch = true,
                MaxSteps = 5,
                EnableReferenceMode = false,
            }
        });

        Console.WriteLine($"Chat component created: {chatComponent._ID}");
    }
}

Configuration Parameters

name

string · required

Name of the chat component

type

string · required

Must be "chat"

config

object · required

Chat configuration object. model is required, and you need at least one material or folder. Unknown keys are rejected with 400.

Configuration Properties

These properties go inside config.

model

string · required

Model key for answers. For example:

  • gemini-3-flash-preview
  • gemini-2.5-flash
  • gemini-2.5-pro
  • gpt-4.1-2025-04-14
  • gpt-4o-mini-2024-07-18
  • auto, currently Gemini 2.5 Flash

The key is checked when a message is sent, not when the component is created, so an unrecognized key makes chat requests fail.

materials

array

Material IDs the assistant answers from. Provide materials, folders, or both.

folders

array

Folder IDs whose materials the assistant answers from

systemPrompt

string

Instructions for the assistant's behavior and personality. Defaults to a general study-assistant prompt.

enableRAGSearch

boolean · default: true

Search your materials to answer questions

enableWebSearch

boolean · default: true

Allow the assistant to search the web. Requests sent through /chat/stream use web search unless this is false. In the embed, the learner's web search toggle decides per message; its starting state comes from features.enableWebSearch, which defaults to off.

enableComponentCreation

boolean · default: true

Allow the assistant to create study components (flashcards, tests, and so on) during the conversation

maxSteps

number · default: 3

Maximum tool-call steps per answer (for example, a material search, then a web search, then the answer)

enableReferenceMode

boolean · default: false

Cite sources by reference title and URL instead of source content

enableGuardrails

boolean · default: false

Apply guardrailRules to every answer. See Adding Guardrails.

guardrailRules

array

Rules the assistant must follow when enableGuardrails is true

Guardrail Rule Properties

These properties go inside guardrailRules.

id

string · required

Unique identifier for the rule

description

string · required

Brief description of the rule's purpose

condition

string · required

Natural-language description of when the rule applies

action

string · required

What to do when the rule applies. Lowercase:

  • block, reply with only the rule's message
  • warn, answer normally, optionally starting with a short warning
  • modify, adjust the answer to comply, without mentioning the rule

message

string

Reply to show when a block rule applies. Defaults to "This request cannot be processed." Not used by warn or modify rules.

temperature

number

Accepted and stored (0–2), but chat streaming currently uses a fixed temperature and does not read this value.

maxTokens

number

Accepted and stored, but chat streaming currently uses a fixed output limit and does not read this value.

enableHistory

boolean

Stored on the component. The embed reads history, voice, follow-up, feedback, and display settings from the embed token instead; set them with features when you generate the embed.

enableVoice

boolean

Stored on the component. Use features.enableVoice to control the embed.

enableFollowUps

boolean

Stored on the component. Use features.enableFollowUps to control the embed.

enableFeedback

boolean

Stored on the component. Use features.enableFeedback to control the embed.

hideTitle

boolean

Stored on the component. Use features.hideTitle to control the embed.

hideEmptyState

boolean

Stored on the component. Use features.hideEmptyState to control the embed.

emptyStateHtml

string

Stored on the component. Use features.emptyStateHtml to control the embed. Note the casing: emptyStateHTML is rejected.

enableMessageGrading

boolean

Accepted and stored, but not currently read. Prompting and responsibility scores in the embed are controlled by features.enablePromptingScore and features.enableResponsibilityScore.

Response

{
  "_id": "6650f1c2a9b3e4d5f6a7b8c9",
  "componentId": "chat_V1StGXR8_Z",
  "name": "Biology Study Assistant",
  "type": "chat",
  "status": "active",
  "origin": "api",
  "organization": "64f0a1b2c3d4e5f6a7b8c9d0",
  "materials": ["mat-123", "mat-456"],
  "folders": ["folder-789"],
  "config": {
    "model": "gemini-3-flash-preview",
    "materials": ["mat-123", "mat-456"],
    "folders": ["folder-789"],
    "systemPrompt": "You are a helpful biology tutor...",
    "enableWebSearch": false,
    "enableRAGSearch": true,
    "maxSteps": 5,
    "enableReferenceMode": false
  },
  "usageCount": 0,
  "createdAt": "2026-10-06T10:00:00.000Z",
  "updatedAt": "2026-10-06T10:00:00.000Z"
}

_id is the component's database ID; componentId (chat_ plus 10 characters) is its public ID. Chat endpoints accept either.

Adding Guardrails

Guardrail rules are added to the assistant's instructions, and the assistant checks every rule before it answers. Set enableGuardrails: true and list your rules in guardrailRules. When a block rule applies, the reply is only the rule's message, and no citations, images, or follow-ups are sent with it.

Example: Academic Integrity Guardrails

JavaScript

const chatWithGuardrails = await client.v1.components.create({
  name: 'Math Tutor with Guardrails',
  type: 'chat',
  config: {
    model: 'gemini-3-flash-preview',
    materials: ['mat-789'],
    enableGuardrails: true,
    guardrailRules: [
      {
        id: 'no-direct-answers',
        action: 'modify',
        condition: 'Response provides direct answers to homework problems without explanation',
        description: 'Guide students to solve problems themselves',
      },
      {
        id: 'no-exam-answers',
        action: 'block',
        condition: 'User asks for answers to a test or exam',
        description: 'Protect academic integrity',
        message: 'Remember: This tool is for learning, not for completing graded assignments.',
      },
    ],
  },
});

Python

chat_with_guardrails = client.v1.components.create(
    name="Math Tutor with Guardrails",
    type="chat",
    config={
        "model": "gemini-3-flash-preview",
        "materials": ["mat-789"],
        "enable_guardrails": True,
        "guardrail_rules": [
            {
                "id": "no-direct-answers",
                "action": "modify",
                "condition": "Response provides direct answers to homework problems without explanation",
                "description": "Guide students to solve problems themselves",
            },
            {
                "id": "no-exam-answers",
                "action": "block",
                "condition": "User asks for answers to a test or exam",
                "description": "Protect academic integrity",
                "message": "Remember: This tool is for learning, not for completing graded assignments.",
            },
        ],
    },
)

Java

ComponentCreateParams guardrailParams = ComponentCreateParams.builder()
    .name("Math Tutor with Guardrails")
    .type(ComponentCreateParams.Type.CHAT)
    .config(ComponentCreateParams.Config.ChatConfigDto.builder()
        .model("gemini-3-flash-preview")
        .materials(List.of("mat-789"))
        .enableGuardrails(true)
        .guardrailRules(List.of(
            ComponentCreateParams.Config.ChatConfigDto.GuardrailRule.builder()
                .id("no-direct-answers")
                .action(ComponentCreateParams.Config.ChatConfigDto.GuardrailRule.Action.MODIFY)
                .condition("Response provides direct answers to homework problems without explanation")
                .description("Guide students to solve problems themselves")
                .build(),
            ComponentCreateParams.Config.ChatConfigDto.GuardrailRule.builder()
                .id("no-exam-answers")
                .action(ComponentCreateParams.Config.ChatConfigDto.GuardrailRule.Action.BLOCK)
                .condition("User asks for answers to a test or exam")
                .description("Protect academic integrity")
                .message("Remember: This tool is for learning, not for completing graded assignments.")
                .build()
        ))
        .build())
    .build();

ComponentResponse component = client.v1().components().create(guardrailParams);

C#

using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Components;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using GuardrailRule = StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.ConfigProperties.ChatConfigDtoProperties.GuardrailRule;
using GuardrailAction = StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.ConfigProperties.ChatConfigDtoProperties.GuardrailRuleProperties.Action;

public class AcademicIntegrityGuardrails
{
    public static async Task CreateChatWithGuardrails()
    {
        var client = new StudyfetchSDKClient()
        {
            APIKey = Environment.GetEnvironmentVariable("STUDYFETCH_API_KEY"),
            BaseUrl = new Uri("https://studyfetchapi.com")
        };

        var guardrailComponent = await client.V1.Components.Create(new()
        {
            Name = "Math Tutor with Guardrails",
            Type = StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.Type.Chat,
            Config = new StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.ConfigProperties.ChatConfigDto()
            {
                Model = "gemini-3-flash-preview",
                Materials = new List<string> { "mat-789" },
                EnableGuardrails = true,
                GuardrailRules = new List<GuardrailRule>
                {
                    new GuardrailRule()
                    {
                        ID = "no-direct-answers",
                        Action = GuardrailAction.Modify,
                        Condition = "Response provides direct answers to homework problems without explanation",
                        Description = "Guide students to solve problems themselves",
                    },
                    new GuardrailRule()
                    {
                        ID = "no-exam-answers",
                        Action = GuardrailAction.Block,
                        Condition = "User asks for answers to a test or exam",
                        Description = "Protect academic integrity",
                        Message = "Remember: This tool is for learning, not for completing graded assignments.",
                    },
                }
            }
        });

        Console.WriteLine($"Guardrail component created: {guardrailComponent._ID}");
    }
}

Example: Safety and Content Moderation

JavaScript

const safeChat = await client.v1.components.create({
  name: 'K-12 Science Assistant',
  type: 'chat',
  config: {
    model: 'gemini-3-flash-preview',
    materials: ['mat-science-k12'],
    enableGuardrails: true,
    guardrailRules: [
      {
        id: 'no-medical-advice',
        action: 'block',
        condition: 'Response contains medical diagnosis or treatment advice',
        description: 'Prevent medical advice',
        message: 'I cannot provide medical advice. Please consult a healthcare professional.',
      },
      {
        id: 'age-appropriate',
        action: 'modify',
        condition: 'Response contains content not suitable for K-12 students',
        description: 'Ensure age-appropriate content',
      },
      {
        id: 'no-dangerous-experiments',
        action: 'block',
        condition: 'Response describes dangerous chemical reactions or experiments',
        description: 'Prevent dangerous activities',
        message: 'For safety reasons, I cannot provide instructions for this type of experiment.',
      },
    ],
  },
});

Python

safe_chat = client.v1.components.create(
    name="K-12 Science Assistant",
    type="chat",
    config={
        "model": "gemini-3-flash-preview",
        "materials": ["mat-science-k12"],
        "enable_guardrails": True,
        "guardrail_rules": [
            {
                "id": "no-medical-advice",
                "action": "block",
                "condition": "Response contains medical diagnosis or treatment advice",
                "description": "Prevent medical advice",
                "message": "I cannot provide medical advice. Please consult a healthcare professional.",
            },
            {
                "id": "age-appropriate",
                "action": "modify",
                "condition": "Response contains content not suitable for K-12 students",
                "description": "Ensure age-appropriate content",
            },
            {
                "id": "no-dangerous-experiments",
                "action": "block",
                "condition": "Response describes dangerous chemical reactions or experiments",
                "description": "Prevent dangerous activities",
                "message": "For safety reasons, I cannot provide instructions for this type of experiment.",
            },
        ],
    },
)

Java

ComponentCreateParams safeParams = ComponentCreateParams.builder()
    .name("K-12 Science Assistant")
    .type(ComponentCreateParams.Type.CHAT)
    .config(ComponentCreateParams.Config.ChatConfigDto.builder()
        .model("gemini-3-flash-preview")
        .materials(List.of("mat-science-k12"))
        .enableGuardrails(true)
        .guardrailRules(List.of(
            ComponentCreateParams.Config.ChatConfigDto.GuardrailRule.builder()
                .id("no-medical-advice")
                .action(ComponentCreateParams.Config.ChatConfigDto.GuardrailRule.Action.BLOCK)
                .condition("Response contains medical diagnosis or treatment advice")
                .description("Prevent medical advice")
                .message("I cannot provide medical advice. Please consult a healthcare professional.")
                .build(),
            ComponentCreateParams.Config.ChatConfigDto.GuardrailRule.builder()
                .id("age-appropriate")
                .action(ComponentCreateParams.Config.ChatConfigDto.GuardrailRule.Action.MODIFY)
                .condition("Response contains content not suitable for K-12 students")
                .description("Ensure age-appropriate content")
                .build(),
            ComponentCreateParams.Config.ChatConfigDto.GuardrailRule.builder()
                .id("no-dangerous-experiments")
                .action(ComponentCreateParams.Config.ChatConfigDto.GuardrailRule.Action.BLOCK)
                .condition("Response describes dangerous chemical reactions or experiments")
                .description("Prevent dangerous activities")
                .message("For safety reasons, I cannot provide instructions for this type of experiment.")
                .build()
        ))
        .build())
    .build();

ComponentResponse safeChat = client.v1().components().create(safeParams);

C#

using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Components;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using GuardrailRule = StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.ConfigProperties.ChatConfigDtoProperties.GuardrailRule;
using GuardrailAction = StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.ConfigProperties.ChatConfigDtoProperties.GuardrailRuleProperties.Action;

public class SafetyAndContentModeration
{
    public static async Task CreateSafeChatComponent()
    {
        var client = new StudyfetchSDKClient()
        {
            APIKey = Environment.GetEnvironmentVariable("STUDYFETCH_API_KEY"),
            BaseUrl = new Uri("https://studyfetchapi.com")
        };

        var safeChat = await client.V1.Components.Create(new()
        {
            Name = "K-12 Science Assistant",
            Type = StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.Type.Chat,
            Config = new StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.ConfigProperties.ChatConfigDto()
            {
                Model = "gemini-3-flash-preview",
                Materials = new List<string> { "mat-science-k12" },
                EnableGuardrails = true,
                GuardrailRules = new List<GuardrailRule>
                {
                    new GuardrailRule()
                    {
                        ID = "no-medical-advice",
                        Action = GuardrailAction.Block,
                        Condition = "Response contains medical diagnosis or treatment advice",
                        Description = "Prevent medical advice",
                        Message = "I cannot provide medical advice. Please consult a healthcare professional."
                    },
                    new GuardrailRule()
                    {
                        ID = "age-appropriate",
                        Action = GuardrailAction.Modify,
                        Condition = "Response contains content not suitable for K-12 students",
                        Description = "Ensure age-appropriate content"
                    },
                    new GuardrailRule()
                    {
                        ID = "no-dangerous-experiments",
                        Action = GuardrailAction.Block,
                        Condition = "Response describes dangerous chemical reactions or experiments",
                        Description = "Prevent dangerous activities",
                        Message = "For safety reasons, I cannot provide instructions for this type of experiment."
                    }
                }
            }
        });

        Console.WriteLine($"Safe chat component created: {safeChat._ID}");
    }
}

Guardrail Actions Explained

  • block: The reply is only the rule's message (or "This request cannot be processed." if none is set), with no further explanation.
  • warn: The assistant answers normally and may start with a brief warning based on the rule's description.
  • modify: The assistant adjusts its answer to follow the rule without mentioning the rule.

Rule actions must be lowercase; "BLOCK" is rejected with 400. Rules are enforced by the model following instructions, so test them against the questions your learners are likely to ask.

Embedding This Component

Once you've created a Chat component, you can embed it on your website using the embedding API. The component must be active.

Generate Embed URL

JavaScript

const embedResponse = await client.v1.components.generateEmbed(chatComponent._id, {
  // User tracking
  userId: 'user-456',
  studentName: 'Jane Smith',  // Student name for display
  groupIds: ['class-101', 'class-102'],
  sessionId: 'session-789',

  // Chat-specific features
  features: {
    enableBadWordsFilter: true,  // Required by the SDK type
    enableWebSearch: true,
    enableHistory: true,
    enableVoice: true,
    enableFollowUps: true,
    enableComponentCreation: false,
    enableFeedback: true,
    enablePromptingScore: true,
    enableResponsibilityScore: true,
    enableReferenceMode: false,
    placeholderText: 'Ask me anything about biology...',
    hideTitle: false,
    hideEmptyState: false,
    emptyStateHtml: '<div style="text-align: center; padding: 2rem;"><h3>Welcome to Biology Assistant!</h3><p>Ask me anything about your course materials.</p></div>',
  },

  // Dimensions
  width: '100%',
  height: '600px',
});

console.log(embedResponse.embedUrl, embedResponse.token);

Python

embed_response = client.v1.components.generate_embed(
    chat_component.api_id,
    user_id="user-456",
    student_name="Jane Smith",  # Student name for display
    group_ids=["class-101", "class-102"],
    session_id="session-789",
    features={
        "enable_bad_words_filter": True,  # Required by the SDK type
        "enable_web_search": True,
        "enable_history": True,
        "enable_voice": True,
        "enable_follow_ups": True,
        "enable_component_creation": False,
        "enable_feedback": True,
        "enable_prompting_score": True,
        "enable_responsibility_score": True,
        "enable_reference_mode": False,
        "placeholder_text": "Ask me anything about biology...",
        "hide_title": False,
        "hide_empty_state": False,
        "empty_state_html": '<div style="text-align: center; padding: 2rem;"><h3>Welcome to Biology Assistant!</h3><p>Ask me anything about your course materials.</p></div>',
    },
    width="100%",
    height="600px",
)

print(embed_response.embed_url, embed_response.token)

Java

import com.studyfetch.javasdk.models.v1.components.ComponentGenerateEmbedParams;
import com.studyfetch.javasdk.models.v1.components.ComponentGenerateEmbedResponse;

ComponentGenerateEmbedParams params = ComponentGenerateEmbedParams.builder()
    .id(chatComponent._id())
    // User tracking
    .userId("user-456")
    .studentName("Jane Smith")  // Student name for display
    .groupIds(List.of("class-101", "class-102"))
    .sessionId("session-789")

    // Chat-specific features
    .features(ComponentGenerateEmbedParams.Features.builder()
        .enableBadWordsFilter(true)  // Required: build() throws without it
        .enableWebSearch(true)
        .enableHistory(true)
        .enableVoice(true)
        .enableFollowUps(true)
        .enableComponentCreation(false)
        .enableFeedback(true)
        .enablePromptingScore(true)
        .enableResponsibilityScore(true)
        .enableReferenceMode(false)
        .placeholderText("Ask me anything about biology...")
        .hideTitle(false)
        .hideEmptyState(false)
        .emptyStateHtml("<div style='text-align: center; padding: 2rem;'><h3>Welcome to Biology Assistant!</h3><p>Ask me anything about your course materials.</p></div>")
        .build())

    // Dimensions
    .width("100%")
    .height("600px")
    .build();

ComponentGenerateEmbedResponse embedResponse = client.v1().components()
    .generateEmbed(params);

System.out.println(embedResponse.embedUrl());

C#

using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Components;
using StudyfetchSDK.Models.V1.Components.ComponentGenerateEmbedParamsProperties;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

public class GenerateChatEmbed
{
    public static async Task GenerateEmbed(string chatComponentId)
    {
        var client = new StudyfetchSDKClient()
        {
            APIKey = Environment.GetEnvironmentVariable("STUDYFETCH_API_KEY"),
            BaseUrl = new Uri("https://studyfetchapi.com")
        };

        var embedResponse = await client.V1.Components.GenerateEmbed(new()
        {
            ID = chatComponentId,

            // User tracking
            UserID = "user-456",
            StudentName = "Jane Smith",  // Student name for display
            GroupIDs = new List<string> { "class-101", "class-102" },
            SessionID = "session-789",

            // Chat-specific features
            Features = new Features(enableBadWordsFilter: true)
            {
                EnableWebSearch = true,
                EnableHistory = true,
                EnableVoice = true,
                EnableFollowUps = true,
                EnableComponentCreation = false,
                EnableFeedback = true,
                EnablePromptingScore = true,
                EnableResponsibilityScore = true,
                EnableReferenceMode = false,
                PlaceholderText = "Ask me anything about biology...",
                HideTitle = false,
                HideEmptyState = false,
                EmptyStateHTML = "<div style='text-align: center; padding: 2rem;'><h3>Welcome to Biology Assistant!</h3><p>Ask me anything about your course materials.</p></div>"
            },

            // Dimensions
            Width = "100%",
            Height = "600px"
        });

        Console.WriteLine($"Embed URL: {embedResponse.EmbedURL}");
        Console.WriteLine($"Token: {embedResponse.Token}");
    }
}

The response contains embedUrl, token, expiresAt, componentId (the component's _id), componentType, and options (width and height, defaulting to 100% and 600px).

Chat-Specific Embedding Features

The chat embed reads these settings from the embed token. Defaults apply when a feature is omitted. features keys not listed here (enableGuardrails, enableOutline, enableTranscript, enableWebSearchSources, enableImageSources) do not affect Chat; guardrails come from the component's config.

features.enableBadWordsFilter

boolean · required

Required by all SDK types (the API accepts requests without it). The chat embed does not currently change behavior based on this value.

features.enableWebSearch

boolean · default: false

Starting state of the learner's web search toggle

features.enableHistory

boolean · default: true

Show past conversations and let learners continue them. History is stored per userId.

features.enableVoice

boolean · default: true

Enable voice input. Your iframe needs allow="microphone".

features.enableFollowUps

boolean · default: true

Show suggested follow-up questions after answers

features.enableComponentCreation

boolean · default: true

Allow learners to create other components (flashcards, tests) from chat

features.enableFeedback

boolean · default: true

Show thumbs up/down buttons with an optional reason. Read the results with Retrieving Chat Feedback.

features.enablePromptingScore

boolean · default: true

Score the quality of the learner's questions (1–4 scale)

features.enableResponsibilityScore

boolean · default: true

Score how responsibly the learner is using AI (1–4 scale)

features.enableReferenceMode

boolean · default: false

Show reference titles and URLs instead of source content in citations

features.placeholderText

string · default: Ask your AI Assistant about anything

Placeholder text for the chat input

features.hideTitle

boolean · default: false

Hide the chat title and icon

features.hideEmptyState

boolean · default: false

Hide the default empty state (icon and text) when there are no messages

features.emptyStateHtml

string

Custom HTML to show instead of the default icon and message when the chat is empty

Embed in Your HTML

Use the embedUrl from the response as the iframe source. The host can differ per organization, so don't build the URL yourself.

<iframe
  src="EMBED_URL_FROM_RESPONSE"
  width="100%"
  height="600px"
  frameborder="0"
  allow="microphone; clipboard-write"
  style="border: 1px solid #e5e5e5; border-radius: 8px;">
</iframe>

Streaming Chat Responses

POST /api/v1/chat/stream streams an answer from your server using your API key. It returns HTTP 200 and streams the answer as it is generated.

Choose the component with the x-component-id header (the component's componentId or _id). A componentId in the request body does not select the component; without the header, the request uses your organization's first chat component, or creates a default one if you have none.

The endpoint accepts two request formats:

Format Send Conversation history Session ID field
Single message message: { text, images? } Loaded by the server from the stored session sessionId
AI SDK messages: [...] (the last one is the new question) Taken from the messages array id

In both formats, pass userId and groupIds to attribute the conversation, and context for per-request context.

Stream Chat Response

The SDKs send the request, but they do not parse the stream for you. Read the raw response body as shown below. These examples use the single-message format because the SDKs type messages as an array of strings; send the AI SDK format with cURL or any HTTP client.

JavaScript

const response = await client.v1.chat
  .stream({
    'x-component-id': 'chat_V1StGXR8_Z',
    sessionId: 'session-789',
    userId: 'user-456',
    groupIds: ['class-101'],
    message: { text: 'Explain photosynthesis in simple terms' },
  })
  .asResponse();

const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
let buffer = '';
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  buffer += value;
  const lines = buffer.split('\n');
  buffer = lines.pop();
  for (const line of lines) {
    if (!line.startsWith('data: ') || line === 'data: [DONE]') continue;
    const part = JSON.parse(line.slice(6));
    if (part.type === 'text-delta') process.stdout.write(part.delta);
    if (part.type === 'data-citations') console.log('\nSources:', part.data.citations);
  }
}

Python

import json

with client.v1.chat.with_streaming_response.stream(
    x_component_id="chat_V1StGXR8_Z",
    session_id="session-789",
    user_id="user-456",
    group_ids=["class-101"],
    message={"text": "Explain photosynthesis in simple terms"},
) as response:
    for line in response.iter_lines():
        if not line.startswith("data: ") or line == "data: [DONE]":
            continue
        part = json.loads(line[len("data: "):])
        if part["type"] == "text-delta":
            print(part["delta"], end="", flush=True)
        elif part["type"] == "data-citations":
            print("\nSources:", part["data"]["citations"])

Java

import com.studyfetch.javasdk.core.http.HttpResponse;
import com.studyfetch.javasdk.models.v1.chat.ChatStreamParams;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

ChatStreamParams params = ChatStreamParams.builder()
    .xComponentId("chat_V1StGXR8_Z")
    .sessionId("session-789")
    .userId("user-456")
    .addGroupId("class-101")
    .message(ChatStreamParams.Message.builder()
        .text("Explain photosynthesis in simple terms")
        .build())
    .build();

try (HttpResponse response = client.v1().chat().withRawResponse().stream(params);
     BufferedReader reader = new BufferedReader(
         new InputStreamReader(response.body(), StandardCharsets.UTF_8))) {
    String line;
    while ((line = reader.readLine()) != null) {
        if (line.startsWith("data: ") && !line.equals("data: [DONE]")) {
            System.out.println(line.substring(6)); // One JSON stream part per line
        }
    }
}

C#

// The C# SDK's Chat.Stream() sends the request but discards the response body,
// so it cannot read a streamed answer. Use HttpClient (or the cURL request) instead.
using System.IO;
using System.Net.Http;
using System.Text;

using var http = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://studyfetchapi.com/api/v1/chat/stream")
{
    Content = new StringContent(
        "{\"sessionId\":\"session-789\",\"userId\":\"user-456\",\"message\":{\"text\":\"Explain photosynthesis in simple terms\"}}",
        Encoding.UTF8,
        "application/json"),
};
request.Headers.Add("x-api-key", Environment.GetEnvironmentVariable("STUDYFETCH_API_KEY"));
request.Headers.Add("x-component-id", "chat_V1StGXR8_Z");

using var response = await http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
using var reader = new StreamReader(await response.Content.ReadAsStreamAsync());
string? line;
while ((line = await reader.ReadLineAsync()) != null)
{
    if (line.StartsWith("data: ") && line != "data: [DONE]")
        Console.WriteLine(line.Substring(6)); // One JSON stream part per line
}

cURL

# Single-message format
curl -N "https://studyfetchapi.com/api/v1/chat/stream" \
  -H "x-api-key: your-api-key" \
  -H "x-component-id: chat_V1StGXR8_Z" \
  -H "Content-Type: application/json" \
  -d '{
    "sessionId": "session-789",
    "userId": "user-456",
    "message": { "text": "Explain photosynthesis in simple terms" }
  }'

# AI SDK format: history comes from the messages array, the session from "id"
curl -N "https://studyfetchapi.com/api/v1/chat/stream" \
  -H "x-api-key: your-api-key" \
  -H "x-component-id: chat_V1StGXR8_Z" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "session-789",
    "userId": "user-456",
    "messages": [
      { "role": "user", "content": "What is photosynthesis?" },
      { "role": "assistant", "content": "Photosynthesis is how plants make food from light..." },
      { "role": "user", "content": "Which part happens in the thylakoid?" }
    ]
  }'

Stream Parameters

x-component-id

string

The chat component to use: its componentId or _id. Strongly recommended; see above.

message

object

The new question, in single-message format

Message Properties

These properties go inside message.

text

string

Text of the question

images

array

Images attached to the question. Each has url or base64, plus optional caption and mimeType (for example image/png). A base64 image without a caption is described by the model and the description is added to the question.

messages

array

The conversation in AI SDK format. Each message has a role (user, assistant, or system) and either content (a string, or an array of text and image parts) or AI SDK v5 parts. The last message is the new question.

sessionId

string

Session ID for the single-message format. The server stores the conversation under this ID and userId, and continues it on later requests. A new ID is generated if omitted.

id

string

Session ID for the AI SDK format

userId

string

The learner's ID, used for history, feedback, and exports

groupIds

array

Group or class IDs to attach to the conversation. Used by the groupId filters on feedback and exports.

context

object

Per-request settings and context. Recognized keys:

  • materialIds, search only these materials
  • enableWebSearch, enableComponentCreation, override the component setting for this request
  • language, language to respond in
  • difficulty, level to pitch explanations at
  • focusArea, topic to focus on

trigger

string

Passed through from AI SDK clients; not needed otherwise

timezone

string

IANA timezone string (for example America/New_York). Accepted by the endpoint but not used by Chat components. Not in the SDKs.

Stream Response Format

The body is a Vercel AI SDK UI message stream: one data: {json} line per event, a blank line between events, and data: [DONE] at the end. The Content-Type header is text/plain, not text/event-stream, so read the body line by line rather than with an EventSource. Clients built on the AI SDK's useChat can read it directly.

An abridged example:

data: {"type":"start","messageId":"..."}

data: {"type":"text-start","id":"..."}

data: {"type":"text-delta","id":"...","delta":"Photosynthesis is the process "}

data: {"type":"text-delta","id":"...","delta":"plants use to turn light into energy [1]."}

data: {"type":"text-end","id":"..."}

data: {"type":"data-citations","data":{"type":"citations","citations":[...]}}

data: {"type":"data-followups","data":{"type":"followups","followUps":["..."]}}

data: {"type":"finish"}

data: [DONE]
Event type Meaning
text-delta The next piece of the answer, in delta. Concatenate them.
data-citations Sources for the [n] markers in the answer, in data.citations
data-images Images from your materials referenced in the answer, in data.images (id, url, description)
data-followups Suggested follow-up questions, in data.followUps
data-progress Status notices, such as a warning that processing is taking longer than expected
error The answer failed. The reason is in errorText (or error, if the request failed after streaming had started).

Other AI SDK event types (such as start-step and finish-step) can appear; ignore any you don't need.

Transcribing Voice Input

POST /api/v1/chat/transcribe converts a recorded question to text, for example to send it to the stream endpoint. Upload the audio as multipart form data in a field named audio. Speech is transcribed as English. The response is { "text": "..." }.

The SDKs don't have a method for this endpoint yet.

curl "https://studyfetchapi.com/api/v1/chat/transcribe" \
  -H "x-api-key: your-api-key" \
  -F "[email protected]"

Managing Chat Embed Context

The context API tells an embedded chat what the learner is looking at, so the chat stays in place while the surrounding content changes:

  • Practice tests: update context as learners move between questions
  • Multi-page tutorials: give page-specific context without resetting chat history
  • Dynamic content: keep the assistant informed about the current view

Context belongs to one embed session: the component, userId, and sessionId of the embed token you pass. Generate each learner's embed with a userId (and a sessionId if they can have several open) so their context stays separate.

Push Context

JavaScript

await client.v1.embed.context.push({
  token: 'embed-token-123',
  context: 'The user is now on Question 2, which covers cellular respiration and the Krebs cycle.',
});

Python

client.v1.embed.context.push(
    token="embed-token-123",
    context="The user is now on Question 2, which covers cellular respiration and the Krebs cycle.",
)

Java

import com.studyfetch.javasdk.models.v1.embed.context.ContextPushParams;

client.v1().embed().context().push(ContextPushParams.builder()
    .token("embed-token-123")
    .context("The user is now on Question 2, which covers cellular respiration and the Krebs cycle.")
    .build());

C#

using StudyfetchSDK.Models.V1.Embed.Context;

await client.V1.Embed.Context.Push(new ContextPushParams()
{
    Token = "embed-token-123",
    Context = "The user is now on Question 2, which covers cellular respiration and the Krebs cycle."
});

cURL

curl -X POST "https://studyfetchapi.com/api/v1/embed/context/push?token=embed-token-123" \
  -H "Content-Type: application/json" \
  -d '{ "context": "The user is now on Question 2, which covers cellular respiration and the Krebs cycle." }'

The response is { "success": true, "message": "Context pushed successfully", "currentContext": ["..."] }.

Retrieve Context

JavaScript

await client.v1.embed.context.retrieve({ token: 'embed-token-123' });

Python

client.v1.embed.context.retrieve(token="embed-token-123")

Java

import com.studyfetch.javasdk.models.v1.embed.context.ContextRetrieveParams;

client.v1().embed().context().retrieve(ContextRetrieveParams.builder()
    .token("embed-token-123")
    .build());

C#

using StudyfetchSDK.Models.V1.Embed.Context;

await client.V1.Embed.Context.Retrieve(new ContextRetrieveParams()
{
    Token = "embed-token-123"
});

cURL

curl "https://studyfetchapi.com/api/v1/embed/context?token=embed-token-123"

The response is { "success": true, "context": ["..."], "contextString": "..." }. The SDK methods don't return the response body; use cURL or an HTTP client to read it.

Clear Context

JavaScript

await client.v1.embed.context.clear({ token: 'embed-token-123' });

Python

client.v1.embed.context.clear(token="embed-token-123")

Java

import com.studyfetch.javasdk.models.v1.embed.context.ContextClearParams;

client.v1().embed().context().clear(ContextClearParams.builder()
    .token("embed-token-123")
    .build());

C#

using StudyfetchSDK.Models.V1.Embed.Context;

await client.V1.Embed.Context.Clear(new ContextClearParams()
{
    Token = "embed-token-123"
});

cURL

curl -X POST "https://studyfetchapi.com/api/v1/embed/context/clear?token=embed-token-123"

Complete Example: Practice Test with Context

Push the current question whenever the learner navigates. Each push replaces the previous question's context.

JavaScript

async function showQuestion(client, embedToken, number, question) {
  await client.v1.embed.context.push({
    token: embedToken,
    context: `The user is now on Question ${number}: ${question.title}.
Topic: ${question.topic}
Content: ${question.content}`,
  });
}

await showQuestion(client, 'embed-token-123', 1, {
  title: 'Photosynthesis Process',
  topic: 'Biology - Cellular Processes',
  content: 'Explain the light-dependent reactions of photosynthesis...',
});

// Later, when the learner moves on
await showQuestion(client, 'embed-token-123', 2, {
  title: 'Cellular Respiration',
  topic: 'Biology - Cellular Processes',
  content: 'Describe the steps of the Krebs cycle...',
});

Python

def show_question(client, embed_token, number, question):
    client.v1.embed.context.push(
        token=embed_token,
        context=(
            f"The user is now on Question {number}: {question['title']}.\n"
            f"Topic: {question['topic']}\n"
            f"Content: {question['content']}"
        ),
    )

show_question(client, "embed-token-123", 1, {
    "title": "Photosynthesis Process",
    "topic": "Biology - Cellular Processes",
    "content": "Explain the light-dependent reactions of photosynthesis...",
})

# Later, when the learner moves on
show_question(client, "embed-token-123", 2, {
    "title": "Cellular Respiration",
    "topic": "Biology - Cellular Processes",
    "content": "Describe the steps of the Krebs cycle...",
})

Java

import com.studyfetch.javasdk.client.StudyfetchSdkClient;
import com.studyfetch.javasdk.models.v1.embed.context.ContextPushParams;

static void showQuestion(StudyfetchSdkClient client, String embedToken, int number,
                         String title, String topic, String content) {
    client.v1().embed().context().push(ContextPushParams.builder()
        .token(embedToken)
        .context(String.format("The user is now on Question %d: %s.%nTopic: %s%nContent: %s",
            number, title, topic, content))
        .build());
}

showQuestion(client, "embed-token-123", 1, "Photosynthesis Process",
    "Biology - Cellular Processes", "Explain the light-dependent reactions of photosynthesis...");

// Later, when the learner moves on
showQuestion(client, "embed-token-123", 2, "Cellular Respiration",
    "Biology - Cellular Processes", "Describe the steps of the Krebs cycle...");

C#

using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Embed.Context;

static async Task ShowQuestion(StudyfetchSDKClient client, string embedToken, int number,
    string title, string topic, string content)
{
    await client.V1.Embed.Context.Push(new ContextPushParams()
    {
        Token = embedToken,
        Context = $"The user is now on Question {number}: {title}.\nTopic: {topic}\nContent: {content}"
    });
}

await ShowQuestion(client, "embed-token-123", 1, "Photosynthesis Process",
    "Biology - Cellular Processes", "Explain the light-dependent reactions of photosynthesis...");

// Later, when the learner moves on
await ShowQuestion(client, "embed-token-123", 2, "Cellular Respiration",
    "Biology - Cellular Processes", "Describe the steps of the Krebs cycle...");

Context API Parameters

token

string · required

The embed token for the chat instance, from the generateEmbed() response

context

string · required

The context to give the chat (push only). Describe what the learner is currently viewing or doing.

Retrieving Chat Feedback

When features.enableFeedback is on, learners can rate answers with a thumbs up or down and an optional reason. GET /api/v1/chat/feedback lists feedback for your organization, newest first.

The SDKs send this request but type the response as empty, so read the body from the raw response as shown. The C# SDK cannot return the body; use cURL or an HTTP client.

JavaScript

const response = await client.v1.chat
  .retrieveFeedback({
    componentId: 'chat_V1StGXR8_Z',
    groupId: 'class-101',
    feedbackType: 'thumbsDown',
    startDate: '2026-09-01T00:00:00Z',
    endDate: '2026-09-30T23:59:59Z',
    limit: '100',
    skip: '0',
  })
  .asResponse();

const feedback = await response.json();
console.log(feedback.total, feedback.stats);

Python

response = client.v1.chat.with_raw_response.retrieve_feedback(
    component_id="chat_V1StGXR8_Z",
    group_id="class-101",
    feedback_type="thumbsDown",
    start_date="2026-09-01T00:00:00Z",
    end_date="2026-09-30T23:59:59Z",
    limit="100",
    skip="0",
)

feedback = response.json()
print(feedback["total"], feedback["stats"])

Java

import com.studyfetch.javasdk.core.http.HttpResponse;
import com.studyfetch.javasdk.models.v1.chat.ChatRetrieveFeedbackParams;
import java.nio.charset.StandardCharsets;

ChatRetrieveFeedbackParams params = ChatRetrieveFeedbackParams.builder()
    .componentId("chat_V1StGXR8_Z")
    .groupId("class-101")
    .feedbackType(ChatRetrieveFeedbackParams.FeedbackType.THUMBS_DOWN)
    .startDate("2026-09-01T00:00:00Z")
    .endDate("2026-09-30T23:59:59Z")
    .limit("100")
    .skip("0")
    .build();

try (HttpResponse response = client.v1().chat().withRawResponse().retrieveFeedback(params)) {
    String json = new String(response.body().readAllBytes(), StandardCharsets.UTF_8);
    System.out.println(json);
}

cURL

curl "https://studyfetchapi.com/api/v1/chat/feedback?componentId=chat_V1StGXR8_Z&groupId=class-101&feedbackType=thumbsDown" \
  -H "x-api-key: your-api-key"

Feedback Parameters

componentId

string

Filter by the chat component's public componentId

userId

string

Filter by user ID

groupId

string

Filter by a group ID from the embed token's groupIds

startDate

string

Start of the date range (ISO 8601)

endDate

string

End of the date range (ISO 8601)

feedbackType

string

Filter by feedback type:

  • thumbsUp - Positive feedback
  • thumbsDown - Negative feedback

limit

string · default: 100

Number of records to return

skip

string · default: 0

Number of records to skip (for pagination)

Feedback Response

{
  "feedback": [
    {
      "_id": "6651a2b3c4d5e6f7a8b9c0d1",
      "componentId": "chat_V1StGXR8_Z",
      "componentName": "Biology Study Assistant",
      "messageId": "msg_abc123",
      "sessionId": "session-789",
      "feedback": "thumbsDown",
      "reason": "The answer skipped the Calvin cycle.",
      "userId": "user-456",
      "timestamp": "2026-09-14T15:20:45.577Z",
      "groupIds": ["class-101"]
    }
  ],
  "total": 1,
  "groups": ["class-101"],
  "stats": { "thumbsUp": 0, "thumbsDown": 1, "withReason": 1 }
}

total, groups, and stats cover every record that matches your filters, not only the current page.

Retrieving Feedback Context

GET /api/v1/chat/feedback/{feedbackId}/context returns a feedback item with the rated message and the full conversation it came from. Use the _id of a feedback item.

JavaScript

const response = await client.v1.chat.retrieveFeedbackContext('6651a2b3c4d5e6f7a8b9c0d1').asResponse();
const { feedback, message, conversation } = await response.json();

Python

response = client.v1.chat.with_raw_response.retrieve_feedback_context("6651a2b3c4d5e6f7a8b9c0d1")
context = response.json()
print(context["message"], len(context["conversation"]))

Java

import com.studyfetch.javasdk.core.http.HttpResponse;
import java.nio.charset.StandardCharsets;

try (HttpResponse response = client.v1().chat().withRawResponse()
        .retrieveFeedbackContext("6651a2b3c4d5e6f7a8b9c0d1")) {
    String json = new String(response.body().readAllBytes(), StandardCharsets.UTF_8);
    System.out.println(json);
}

cURL

curl "https://studyfetchapi.com/api/v1/chat/feedback/6651a2b3c4d5e6f7a8b9c0d1/context" \
  -H "x-api-key: your-api-key"

The response has feedback (the same fields as in the list, without groupIds), message (the rated message: id, role, content, createdAt), and conversation (every message in the session, in order).

Exporting User Prompts

Two endpoints download the questions learners asked as CSV. Each row is one question, with the columns Session ID, Turn #, Date (UTC), User ID, Group IDs, and User Prompt. Send Accept-Encoding: gzip to receive a compressed download.

The SDKs don't have methods for these endpoints yet.

Export Prompts Across Conversations

GET /api/v1/chat/prompts/export exports questions in date order. Filter with componentId, sessionId, userId, groupId, startDate, and endDate (ISO 8601). componentId accepts the component's _id or its componentId.

curl "https://studyfetchapi.com/api/v1/chat/prompts/export?componentId=6650f1c2a9b3e4d5f6a7b8c9&startDate=2026-09-01T00:00:00Z" \
  -H "x-api-key: your-api-key" \
  -o chat-prompts.csv

Export One Conversation

GET /api/v1/chat/session/{sessionId}/export exports the questions from one conversation, read from the stored transcript, so it also works for conversations from before prompt capture. Pass the conversation owner's userId as a query parameter. It returns 404 if the conversation is not found.

curl "https://studyfetchapi.com/api/v1/chat/session/session-789/export?userId=user-456" \
  -H "x-api-key: your-api-key" \
  -o chat-session.csv

React Native

The React Native SDK (@studyfetch/react-native-sdk) renders Chat natively with embed tokens instead of an iframe. Import it from @studyfetch/react-native-sdk/chat and wrap your app in EmbedTokenProvider with a getToken function that asks your backend to mint an embed token for a component. Chat streams answers by default and needs no extra native dependencies.

import { EmbedTokenProvider } from '@studyfetch/react-native-sdk';
import { StudyFetchChat, useChat, useChatSessions } from '@studyfetch/react-native-sdk/chat';

// Full chat UI: streaming, citations, follow-ups, feedback, stop and regenerate
<EmbedTokenProvider getToken={fetchToken}>
  <StudyFetchChat
    componentId="chat_V1StGXR8_Z"
    userId="user-456"
    materialIds={['mat-123']}
    // Live context for every message, like the context API
    getContext={() => ({ embedContext: `The user is viewing question ${currentQuestion}.` })}
  />
</EmbedTokenProvider>

// Headless: build your own UI
const { messages, status, followUps, sendMessage, stop, regenerate, sendFeedback } = useChat({
  componentId: 'chat_V1StGXR8_Z',
  userId: 'user-456',
});

// Past conversations, for a history screen
const { sessions, getSession } = useChatSessions({
  componentId: 'chat_V1StGXR8_Z',
  userId: 'user-456',
});
  • status is idle, submitted, streaming, or error. stop() keeps the text that has already arrived.
  • Pass stream={false} to receive each answer in one response on networks where streaming is unreliable.
  • History is stored per user, so pass a userId to use useChatSessions.