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/streamand 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-previewgemini-2.5-flashgemini-2.5-progpt-4.1-2025-04-14gpt-4o-mini-2024-07-18auto, 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'smessagewarn, answer normally, optionally starting with a short warningmodify, 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'smessage(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'sdescription.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 materialsenableWebSearch,enableComponentCreation, override the component setting for this requestlanguage, language to respond indifficulty, level to pitch explanations atfocusArea, 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 feedbackthumbsDown- 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',
});
statusisidle,submitted,streaming, orerror.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
userIdto useuseChatSessions.
