# StudyFetch API — full documentation

Published guides in site navigation order.

Index: https://studyfetch-api.honen.com/llms.txt

## Assignment Grader

Source: https://studyfetch-api.honen.com/docs/6acd32359de34cae55b272db

The Assignment Grader API allows you to submit student work (text or uploaded materials), define grading criteria via a rubric, and receive AI-generated scores and feedback for every criterion. It supports inline rubrics, reusable rubric templates, assignment content for source-based grading, and aggregate educator reports.

[Watch the demo video](https://studyfetchcdn.studyfetch.com/videos/features/assignment_grader.mp4)

## Key Features

- **Custom Rubrics**: Define grading criteria with specific point values
- **Flexible Input**: Grade from uploaded materials or direct text
- **Consistent Grading**: AI ensures uniform application of rubric criteria
- **Objective Grading**: Get numerical grades based on your rubric
- **Assignment Content**: Provide source material (reading passages, case studies, prompts) that the AI uses as the authoritative reference when grading
- **User Tracking**: Optional user ID for tracking grading history
- **Assignment Groups**: Group submissions by assignment ID
- **Student Identification**: Track submissions by student email or ID
- **Rubric Templates**: Save and reuse common rubrics

## Base URL

All endpoints are relative to your API prefix (defaults to `api/v1`):

```
https://<your-host>/api/v1/assignment-grader
```

For local development:

```
http://localhost:3001/api/v1/assignment-grader
```

## Authentication

Every endpoint is protected by `FlexibleAuthGuard`, which accepts **either**:

| Method | Header | Notes |
| --- | --- | --- |
| API key | `x-api-key: <YOUR_API_KEY>` | Server-to-server. Resolves to the owning organization. |
| JWT | `Authorization: Bearer <JWT>` | User session. Resolves to both organization and user. |

If neither is present the request returns `401 Unauthorized`.

Note

Every record (graded assignment, rubric template) is scoped to the authenticated `organizationId`. You can only read/delete records that belong to your org.

## Common Headers

| Header | Required | Value |
| --- | --- | --- |
| `Content-Type` | On `POST` requests | `application/json` |
| `x-api-key` | Yes (or `Authorization`) | Your API key |
| `Authorization` | Yes (or `x-api-key`) | `Bearer <jwt>` |

## Endpoint Summary

| # | Method | Path | Purpose |
| --- | --- | --- | --- |
| 1 | `POST` | `/assignment-grader/create` | Grade a new assignment |
| 2 | `GET` | `/assignment-grader/get` | List all graded assignments for the org |
| 3 | `GET` | `/assignment-grader/get/:id` | Fetch one graded assignment by ID |
| 4 | `DELETE` | `/assignment-grader/delete/:id` | Delete a graded assignment |
| 5 | `POST` | `/assignment-grader/rubric-templates` | Create a rubric template |
| 6 | `GET` | `/assignment-grader/rubric-templates` | List all rubric templates |
| 7 | `GET` | `/assignment-grader/rubric-templates/:id` | Fetch one rubric template |
| 8 | `DELETE` | `/assignment-grader/rubric-templates/:id` | Delete a rubric template |
| 9 | `GET` | `/assignment-grader/educator-report/:assignmentId` | Aggregate report across all submissions for an `assignmentId` |

## Creating a Rubric Definition

Before grading assignments, you need to define a rubric with specific criteria. Each criterion has:

- **pointsPossible**: The maximum points for this criterion (required)
- **title**: The name of the criterion (required)
- **description**: Detailed explanation of what's being evaluated (optional but recommended)

JavaScript

```javascript
// Define a comprehensive rubric
const rubricDefinition = {
  criteria: [
    {
      pointsPossible: 25,
      title: "Thesis Statement",
      description: "Clear, arguable thesis that addresses the prompt"
    },
    {
      pointsPossible: 30,
      title: "Evidence and Analysis",
      description: "Strong evidence with thorough analysis"
    },
    {
      pointsPossible: 25,
      title: "Organization",
      description: "Logical structure with smooth transitions"
    },
    {
      pointsPossible: 20,
      title: "Writing Mechanics",
      description: "Grammar, spelling, and citation format"
    }
  ],
  assignmentContent: "Optional — source material or reading passage" // Optional
};

// Total points: 100
```

Python

```python
# Define a comprehensive rubric
rubric_definition = {
    "criteria": [
        {
            "pointsPossible": 25,
            "title": "Thesis Statement",
            "description": "Clear, arguable thesis that addresses the prompt"
        },
        {
            "pointsPossible": 30,
            "title": "Evidence and Analysis",
            "description": "Strong evidence with thorough analysis"
        },
        {
            "pointsPossible": 25,
            "title": "Organization",
            "description": "Logical structure with smooth transitions"
        },
        {
            "pointsPossible": 20,
            "title": "Writing Mechanics",
            "description": "Grammar, spelling, and citation format"
        }
    ],
    "assignmentContent": "Optional — source material or reading passage"  # Optional
}

# Total points: 100
```

Java

```java
import com.studyfetch.javasdk.models.v1.assignmentgrader.AssignmentGraderCreateParams;
import com.studyfetch.javasdk.models.v1.assignmentgrader.rubrictemplates.RubricCriterion;

// Define a comprehensive rubric
AssignmentGraderCreateParams.Rubric rubricDefinition = AssignmentGraderCreateParams.Rubric.builder()
    .addCriterion(RubricCriterion.builder()
        .pointsPossible(25.0)
        .title("Thesis Statement")
        .description("Clear, arguable thesis that addresses the prompt")
        .build())
    .addCriterion(RubricCriterion.builder()
        .pointsPossible(30.0)
        .title("Evidence and Analysis")
        .description("Strong evidence with thorough analysis")
        .build())
    .addCriterion(RubricCriterion.builder()
        .pointsPossible(25.0)
        .title("Organization")
        .description("Logical structure with smooth transitions")
        .build())
    .addCriterion(RubricCriterion.builder()
        .pointsPossible(20.0)
        .title("Writing Mechanics")
        .description("Grammar, spelling, and citation format")
        .build())
    .assignmentContent("Optional — source material or reading passage") // Optional
    .build();

// Total points: 100
```

C#

```csharp
using StudyfetchSDK.Models.V1.AssignmentGrader.AssignmentGraderCreateParamsProperties;
using StudyfetchSDK.Models.V1.AssignmentGrader.RubricTemplates;
using System.Collections.Generic;

// Define a comprehensive rubric
var rubricDefinition = new Rubric()
{
    Criteria = new List<RubricCriterion>
    {
        new RubricCriterion()
        {
            PointsPossible = 25,
            Title = "Thesis Statement",
            Description = "Clear, arguable thesis that addresses the prompt"
        },
        new RubricCriterion()
        {
            PointsPossible = 30,
            Title = "Evidence and Analysis",
            Description = "Strong evidence with thorough analysis"
        },
        new RubricCriterion()
        {
            PointsPossible = 25,
            Title = "Organization",
            Description = "Logical structure with smooth transitions"
        },
        new RubricCriterion()
        {
            PointsPossible = 20,
            Title = "Writing Mechanics",
            Description = "Grammar, spelling, and citation format"
        }
    },
    AssignmentContent = "Optional — source material or reading passage" // Optional
};

// Total points: 100
```

## Create Assignment Grader

```
POST /assignment-grader/create
```

Grade a new assignment by providing the rubric and content to grade. Returns the saved graded assignment with AI-generated scores and feedback.

### Request Body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `title` | `string` | Yes | Title of the assignment. |
| `materialId` | `string` | Conditional\* | ID of an uploaded material (PDF, doc, EPUB, etc.) to grade. |
| `textToGrade` | `string` | Conditional\* | Raw text content to grade. |
| `rubric` | `Rubric` | Conditional\*\* | Inline rubric. |
| `rubricTemplateId` | `string` | Conditional\*\* | ID of a saved rubric template to use. |
| `assignmentContent` | `string` | No | Source material / passage / instructions students were given. The AI uses this as the authoritative reference. See Assignment Content. |
| `assignmentId` | `string` | No | Tag to group multiple submissions under one logical assignment (used by educator reports). |
| `studentIdentifier` | `string` | No | Student email or ID. |
| `userId` | `string` | No | User ID for usage tracking (defaults to authenticated user, or `anonymous`). |
| `model` | `string` | No | AI model key (defaults to `gpt-4o-mini-2024-07-18`). |

> \* You **must provide exactly one** of `materialId` or `textToGrade`. Providing both or neither returns `400 Bad Request`.

> \*\* You **must provide at least one** of `rubric` or `rubricTemplateId`. An inline `rubric` must contain at least one criterion.

### Example Request (curl)

```bash
curl -X POST https://api.yourapp.com/api/v1/assignment-grader/create \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Pop Quiz — Chapter 3",
    "textToGrade": "1. Photosynthesis turns sunlight into sugar.\n2. Plants give off oxygen.",
    "assignmentContent": "Chapter 3 covers photosynthesis, the process by which plants use sunlight, water, and carbon dioxide to produce glucose and oxygen...",
    "rubric": {
      "criteria": [
        { "title": "Defines photosynthesis", "description": "Mentions sunlight + sugar/glucose", "pointsPossible": 5 },
        { "title": "Mentions oxygen", "description": "Identifies oxygen as a byproduct", "pointsPossible": 5 }
      ]
    },
    "assignmentId": "BIO-CH3-QUIZ",
    "studentIdentifier": "jane.doe@school.edu"
  }'
```

### Response, `201 Created`

```json
{
  "success": true,
  "gradedAssignment": {
    "_id": "665a1c0e8f3b1a0012abcdef",
    "title": "Pop Quiz — Chapter 3",
    "grade": 90,
    "rubric": {
      "attemptFeedback": "Strong response. You captured the core concepts...",
      "criteria": [
        {
          "title": "Defines photosynthesis",
          "description": "Mentions sunlight + sugar/glucose",
          "pointsAwarded": 5,
          "pointsPossible": 5,
          "feedback": "Correct — you mentioned sunlight and sugar."
        },
        {
          "title": "Mentions oxygen",
          "description": "Identifies oxygen as a byproduct",
          "pointsAwarded": 4,
          "pointsPossible": 5,
          "feedback": "Good. For full marks, mention that oxygen is released as a byproduct of the reaction."
        }
      ]
    },
    "organizationId": "664f...",
    "userId": "665b...",
    "textToGrade": "1. Photosynthesis turns sunlight...",
    "assignmentContent": "Chapter 3 covers photosynthesis...",
    "assignmentId": "BIO-CH3-QUIZ",
    "studentIdentifier": "jane.doe@school.edu",
    "createdAt": "2026-04-28T07:14:00.000Z",
    "updatedAt": "2026-04-28T07:14:00.000Z"
  }
}
```

### SDK Examples

JavaScript

```javascript
import StudyfetchSDK from '@studyfetch/sdk';

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

// Use the rubric definition from above
const params = {
  title: "Essay on Climate Change",
  textToGrade: "Student essay content here...",
  rubric: rubricDefinition,           // Optional: Custom rubric definition
  rubricTemplateId: "template-123",   // Optional: Use existing rubric template
  assignmentContent: "The source material or reading passage students were given...", // Optional: Reference material for grading
  assignmentId: "assign-456",         // Optional: Group submissions
  studentIdentifier: "[john.doe@example.com](mailto:john.doe@example.com)", // Optional: Student email or ID
  userId: "student123",               // Optional: For tracking
  model: "gpt-4.1-2025-04-14"         // Optional: AI model to use
};

const result = await client.v1.assignmentGrader.create(params);

console.log(`Success: ${result.success}`);
console.log(`Grade: ${result.gradedAssignment?.grade || 0}/100`);
console.log(`Assignment ID: ${result.gradedAssignment?._id}`);
```

Python

```python
from studyfetch_sdk import StudyfetchSDK

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

# Use the rubric definition from above
params = {
    "title": "Essay on Climate Change",
    "textToGrade": "Student essay content here...",
    "rubric": rubric_definition,           # Optional: Custom rubric definition
    "rubricTemplateId": "template-123",    # Optional: Use existing rubric template
    "assignmentContent": "The source material or reading passage students were given...",  # Optional: Reference material for grading
    "assignmentId": "assign-456",          # Optional: Group submissions
    "studentIdentifier": "john.doe@example.com",  # Optional: Student email or ID
    "userId": "student123",                # Optional: For tracking
    "model": "gpt-4.1-2025-04-14"          # Optional: AI model to use
}

result = client.v1.assignment_grader.create(params)

print(f"Grade: {result.grade}/100")
```

Java

```java
import com.studyfetch.javasdk.client.StudyfetchSdkClient;
import com.studyfetch.javasdk.client.okhttp.StudyfetchSdkOkHttpClient;
import com.studyfetch.javasdk.models.v1.assignmentgrader.AssignmentGraderCreateParams;
import com.studyfetch.javasdk.models.v1.assignmentgrader.AssignmentGraderCreateResponse;

StudyfetchSdkClient client = StudyfetchSdkOkHttpClient.builder()
    .apiKey("your-api-key")
    .baseUrl("https://studyfetchapi.com")
    .build();

// Use the rubric definition from above
AssignmentGraderCreateParams params = AssignmentGraderCreateParams.builder()
    .title("Essay on Climate Change")
    .textToGrade("Student essay content here...")
    .rubric(rubricDefinition)           // Optional: Custom rubric definition
    .rubricTemplateId("template-123")   // Optional: Use existing rubric template
    .assignmentContent("The source material or reading passage students were given...") // Optional: Reference material for grading
    .assignmentId("assign-456")         // Optional: Group submissions
    .studentIdentifier("john.doe@example.com") // Optional: Student email or ID
    .userId("student123")               // Optional: For tracking
    .model("gpt-4.1-2025-04-14")        // Optional: AI model to use
    .build();

AssignmentGraderCreateResponse result = client.v1().assignmentGrader().create(params);

System.out.println("Success: " + result.success());
if (result.gradedAssignment().isPresent()) {
    System.out.println("Grade: " + result.gradedAssignment().get().grade() + "/100");
    System.out.println("Assignment ID: " + result.gradedAssignment().get()._id());
}
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.AssignmentGrader;
using System;
using System.Threading.Tasks;

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

        // Use the rubric definition from above
        var params_ = new AssignmentGraderCreateParams()
        {
            Title = "Essay on Climate Change",
            TextToGrade = "Student essay content here...",
            Rubric = rubricDefinition,           // Optional: Custom rubric definition
            RubricTemplateID = "template-123",   // Optional: Use existing rubric template
            AssignmentContent = "The source material or reading passage students were given...", // Optional: Reference material for grading
            AssignmentID = "assign-456",         // Optional: Group submissions
            StudentIdentifier = "john.doe@example.com", // Optional: Student email or ID
            UserID = "student123",               // Optional: For tracking
            Model = "gpt-4.1-2025-04-14"         // Optional: AI model to use
        };

        var result = await client.V1.AssignmentGrader.Create(params_);

        Console.WriteLine($"Success: {result.Success}");
        Console.WriteLine($"Grade: {result.GradedAssignment?.Grade ?? 0}/100");
        Console.WriteLine($"Assignment ID: {result.GradedAssignment?._ID}");
    }
}
```

## Get All Assignment Graders

```
GET /assignment-grader/get
```

Returns every graded assignment for the authenticated organization.

### Example Request (curl)

```bash
curl -X GET https://api.yourapp.com/api/v1/assignment-grader/get \
  -H "x-api-key: YOUR_API_KEY"
```

### Response, `200 OK`

An array of `AssignmentGrader` objects.

### SDK Examples

JavaScript

```javascript
const graders = await client.v1.assignmentGrader.getAll();

graders.forEach(grader => {
  console.log(`ID: ${grader._id}`);
  console.log(`Title: ${grader.title}`);
  console.log(`Grade: ${grader.grade}`);
  console.log(`Created: ${grader.createdAt}`);
  console.log(`Assignment ID: ${grader.assignmentId}`);
  console.log(`Student: ${grader.studentIdentifier}`);
});
```

Python

```python
graders = client.v1.assignment_grader.get_all()

for grader in graders:
    print(f"Title: {grader.title}")
    print(f"Grade: {grader.grade}")
    print(f"Created: {grader.created_at}")
    print(f"Assignment ID: {grader.assignment_id}")
    print(f"Student: {grader.student_identifier}")
```

Java

```java
import com.studyfetch.javasdk.models.v1.assignmentgrader.AssignmentGraderResponse;

List<AssignmentGraderResponse> graders = client.v1().assignmentGrader().getAll();

for (AssignmentGraderResponse grader : graders) {
    System.out.println("ID: " + grader._id());
    System.out.println("Title: " + grader.title());
    System.out.println("Grade: " + grader.grade());
    System.out.println("Created: " + grader.createdAt());
    System.out.println("Assignment ID: " + grader.assignmentId());
    System.out.println("Student: " + grader.studentIdentifier());
}
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.AssignmentGrader;
using System;
using System.Threading.Tasks;

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

        var graders = await client.V1.AssignmentGrader.GetAll(new());

        foreach (var grader in graders)
        {
            Console.WriteLine($"ID: {grader._ID}");
            Console.WriteLine($"Title: {grader.Title}");
            Console.WriteLine($"Grade: {grader.Grade}");
            Console.WriteLine($"Created: {grader.CreatedAt}");
            Console.WriteLine($"Assignment ID: {grader.AssignmentID}");
            Console.WriteLine($"Student: {grader.StudentIdentifier}");
        }
    }
}
```

## Get Assignment Grader by ID

```
GET /assignment-grader/get/:id
```

Retrieve a specific graded assignment by its ID.

| Path param | Description |
| --- | --- |
| `id` | Assignment grader document ID (`_id`). |

### Example Request (curl)

```bash
curl -X GET https://api.yourapp.com/api/v1/assignment-grader/get/665a1c0e8f3b1a0012abcdef \
  -H "x-api-key: YOUR_API_KEY"
```

### Response, `200 OK`

A single `AssignmentGrader` object. Returns `404 Not Found` if no assignment matches the ID.

### SDK Examples

JavaScript

```javascript
const graderId = "grader_123456";
const grader = await client.v1.assignmentGrader.getByID(graderId);

console.log(`ID: ${grader._id}`);
console.log(`Assignment: ${grader.title}`);
console.log(`Grade: ${grader.grade}/100`);
console.log(`Student: ${grader.studentIdentifier}`);
console.log(`Assignment ID: ${grader.assignmentId}`);
```

Python

```python
grader_id = "grader_123456"
grader = client.v1.assignment_grader.get_by_id(grader_id)

print(f"Assignment: {grader.title}")
print(f"Grade: {grader.grade}/100")
print(f"Student: {grader.student_identifier}")
print(f"Assignment ID: {grader.assignment_id}")
```

Java

```java
import com.studyfetch.javasdk.models.v1.assignmentgrader.AssignmentGraderGetByIdParams;
import com.studyfetch.javasdk.models.v1.assignmentgrader.AssignmentGraderResponse;

String graderId = "grader_123456";
AssignmentGraderResponse grader = client.v1().assignmentGrader().getById(
    AssignmentGraderGetByIdParams.builder()
        .id(graderId)
        .build()
);

System.out.println("ID: " + grader._id());
System.out.println("Assignment: " + grader.title());
System.out.println("Grade: " + grader.grade() + "/100");
System.out.println("Student: " + grader.studentIdentifier());
System.out.println("Assignment ID: " + grader.assignmentId());
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.AssignmentGrader;
using System;
using System.Threading.Tasks;

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

        string graderID = "grader_123456";
        var grader = await client.V1.AssignmentGrader.GetByID(new()
        {
            ID = graderID
        });

        Console.WriteLine($"ID: {grader._ID}");
        Console.WriteLine($"Assignment: {grader.Title}");
        Console.WriteLine($"Grade: {grader.Grade}/100");
        Console.WriteLine($"Student: {grader.StudentIdentifier}");
        Console.WriteLine($"Assignment ID: {grader.AssignmentID}");
    }
}
```

## Delete Assignment Grader

```
DELETE /assignment-grader/delete/:id
```

Delete a specific graded assignment by its ID.

| Path param | Description |
| --- | --- |
| `id` | Assignment grader document ID. |

### Example Request (curl)

```bash
curl -X DELETE https://api.yourapp.com/api/v1/assignment-grader/delete/665a1c0e8f3b1a0012abcdef \
  -H "x-api-key: YOUR_API_KEY"
```

### Response, `200 OK`

```json
{ "success": true, "message": "Assignment deleted successfully" }
```

Returns `404 Not Found` if the assignment doesn't exist or belongs to a different org.

### SDK Examples

JavaScript

```javascript
const graderId = "grader_123456";
await client.v1.assignmentGrader.delete(graderId);
console.log("Assignment grader deleted successfully");
```

Python

```python
grader_id = "grader_123456"
client.v1.assignment_grader.delete(grader_id)
print("Assignment grader deleted successfully")
```

Java

```java
String graderId = "grader_123456";
client.v1().assignmentGrader().delete(graderId);
System.out.println("Assignment grader deleted successfully");
```

C#

```csharp
using StudyfetchSDK;
using System;
using System.Threading.Tasks;

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

        string graderID = "grader_123456";
        await client.V1.AssignmentGrader.Delete(new()
        {
            ID = graderID
        });
        Console.WriteLine("Assignment grader deleted successfully");
    }
}
```

## Generate Educator Report

```
GET /assignment-grader/educator-report/:assignmentId
```

Aggregates statistics across **every graded submission** in your organization that shares the given `assignmentId`. Use this after grading multiple students for the same logical assignment.

| Path param | Description |
| --- | --- |
| `assignmentId` | The `assignmentId` you tagged on each submission when calling `POST /create`. |

### Example Request (curl)

```bash
curl -X GET https://api.yourapp.com/api/v1/assignment-grader/educator-report/WATER-CYCLE-2026 \
  -H "x-api-key: YOUR_API_KEY"
```

### Response, `200 OK`

```json
{
  "assignmentId": "WATER-CYCLE-2026",
  "title": "Water Cycle Reading Comprehension",
  "totalSubmissions": 27,
  "statistics": {
    "averageGrade": "82.4",
    "minGrade": "55.0",
    "maxGrade": "100.0",
    "standardDeviation": "11.7"
  },
  "gradeDistribution": { "A": 9, "B": 11, "C": 5, "D": 1, "F": 1 },
  "criteriaAnalysis": [
    { "title": "Q1 — Evaporation",  "avgScore": 4.6, "maxPossible": 5, "submissionCount": 27 },
    { "title": "Q4 — Groundwater",  "avgScore": 2.9, "maxPossible": 5, "submissionCount": 27 }
  ],
  "strengths": [
    { "title": "Q1 — Evaporation", "avgScore": "4.6", "maxPossible": 5, "performanceRatio": "92.0" }
  ],
  "weaknesses": [
    { "title": "Q4 — Groundwater", "avgScore": "2.9", "maxPossible": 5, "performanceRatio": "58.0" }
  ],
  "submissions": [
    {
      "id": "665a1c0e8f3b1a0012abcdef",
      "studentIdentifier": "jane.doe@school.edu",
      "grade": 90,
      "createdAt": "2026-04-28T07:14:00.000Z"
    }
  ]
}
```

### Field Semantics

| Field | Meaning |
| --- | --- |
| `statistics.averageGrade` | Mean of `grade` across all matching submissions, formatted to 1 decimal. |
| `statistics.standardDeviation` | Population stddev of `grade`, 1 decimal. |
| `gradeDistribution` | Letter-grade buckets, A: 90–100, B: 80–89, C: 70–79, D: 60–69, F: below 60. |
| `criteriaAnalysis` | Per-criterion average score across all submissions. |
| `strengths` | Criteria where `avgScore / maxPossible >= 0.80`. |
| `weaknesses` | Criteria where `avgScore / maxPossible < 0.70`. |
| `submissions` | Flat list of every submission feeding the report. |

Returns `404 Not Found` if no submissions match the `assignmentId` in your organization.

### SDK Examples

JavaScript

```javascript
const assignmentId = "assign-456";
const report = await client.v1.assignmentGrader.generateReport(assignmentId);

console.log("Assignment Report:");
console.log(`Title: ${report.title}`);
console.log(`Total Submissions: ${report.totalSubmissions}`);
console.log(`Average Grade: ${report.statistics.averageGrade}`);
console.log(`Max Grade: ${report.statistics.maxGrade}`);
console.log(`Min Grade: ${report.statistics.minGrade}`);
console.log(`Standard Deviation: ${report.statistics.standardDeviation}`);

// Grade distribution
console.log("Grade Distribution:");
console.log(`  A (90-100): ${report.gradeDistribution.A}`);
console.log(`  B (80-89): ${report.gradeDistribution.B}`);
console.log(`  C (70-79): ${report.gradeDistribution.C}`);
console.log(`  D (60-69): ${report.gradeDistribution.D}`);
console.log(`  F (0-59): ${report.gradeDistribution.F}`);

// Top performing criteria
console.log("Strengths:");
report.strengths.forEach(strength => {
  console.log(`  ${strength.title}: ${strength.avgScore}/${strength.maxPossible} (${strength.performanceRatio})`);
});

// Criteria needing improvement
console.log("Weaknesses:");
report.weaknesses.forEach(weakness => {
  console.log(`  ${weakness.title}: ${weakness.avgScore}/${weakness.maxPossible} (${weakness.performanceRatio})`);
});
```

Python

```python
assignment_id = "assign-456"
report = client.v1.assignment_grader.generate_report(assignment_id)

print("Assignment Report:")
print(f"Title: {report.title}")
print(f"Total Submissions: {report.total_submissions}")
print(f"Average Grade: {report.statistics.average_grade}")
print(f"Max Grade: {report.statistics.max_grade}")
print(f"Min Grade: {report.statistics.min_grade}")
print(f"Standard Deviation: {report.statistics.standard_deviation}")

# Grade distribution
print("Grade Distribution:")
print(f"  A (90-100): {report.grade_distribution.A}")
print(f"  B (80-89): {report.grade_distribution.B}")
print(f"  C (70-79): {report.grade_distribution.C}")
print(f"  D (60-69): {report.grade_distribution.D}")
print(f"  F (0-59): {report.grade_distribution.F}")

# Top performing criteria
print("Strengths:")
for strength in report.strengths:
    print(f"  {strength.title}: {strength.avg_score}/{strength.max_possible} ({strength.performance_ratio})")

# Criteria needing improvement
print("Weaknesses:")
for weakness in report.weaknesses:
    print(f"  {weakness.title}: {weakness.avg_score}/{weakness.max_possible} ({weakness.performance_ratio})")
```

Java

```java
import com.studyfetch.javasdk.models.v1.assignmentgrader.AssignmentGraderGenerateReportResponse;

String assignmentId = "assign-456";
AssignmentGraderGenerateReportResponse report = client.v1().assignmentGrader().generateReport(assignmentId);

System.out.println("Assignment Report:");
System.out.println("Title: " + report.title());
System.out.println("Total Submissions: " + report.totalSubmissions());
System.out.println("Average Grade: " + report.statistics().averageGrade());
System.out.println("Max Grade: " + report.statistics().maxGrade());
System.out.println("Min Grade: " + report.statistics().minGrade());
System.out.println("Standard Deviation: " + report.statistics().standardDeviation());

// Grade distribution
System.out.println("Grade Distribution:");
System.out.println("  A (90-100): " + report.gradeDistribution().a());
System.out.println("  B (80-89): " + report.gradeDistribution().b());
System.out.println("  C (70-79): " + report.gradeDistribution().c());
System.out.println("  D (60-69): " + report.gradeDistribution().d());
System.out.println("  F (0-59): " + report.gradeDistribution().f());

// Top performing criteria
System.out.println("Strengths:");
for (AssignmentGraderGenerateReportResponse.Strength strength : report.strengths()) {
    System.out.println("  " + strength.title() + ": " + strength.avgScore() + "/" + 
                      strength.maxPossible() + " (" + strength.performanceRatio() + ")");
}

// Criteria needing improvement
System.out.println("Weaknesses:");
for (AssignmentGraderGenerateReportResponse.Weakness weakness : report.weaknesses()) {
    System.out.println("  " + weakness.title() + ": " + weakness.avgScore() + "/" + 
                      weakness.maxPossible() + " (" + weakness.performanceRatio() + ")");
}
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.AssignmentGrader;
using System;
using System.Threading.Tasks;

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

        string assignmentID = "assign-456";
        var report = await client.V1.AssignmentGrader.GenerateReport(new()
        {
            AssignmentID = assignmentID
        });

        Console.WriteLine("Assignment Report:");
        Console.WriteLine($"Title: {report.Title}");
        Console.WriteLine($"Total Submissions: {report.TotalSubmissions}");
        Console.WriteLine($"Average Grade: {report.Statistics.AverageGrade}");
        Console.WriteLine($"Max Grade: {report.Statistics.MaxGrade}");
        Console.WriteLine($"Min Grade: {report.Statistics.MinGrade}");
        Console.WriteLine($"Standard Deviation: {report.Statistics.StandardDeviation}");

        // Grade distribution
        Console.WriteLine("Grade Distribution:");
        Console.WriteLine($"  A (90-100): {report.GradeDistribution.A}");
        Console.WriteLine($"  B (80-89): {report.GradeDistribution.B}");
        Console.WriteLine($"  C (70-79): {report.GradeDistribution.C}");
        Console.WriteLine($"  D (60-69): {report.GradeDistribution.D}");
        Console.WriteLine($"  F (0-59): {report.GradeDistribution.F}");

        // Top performing criteria
        Console.WriteLine("Strengths:");
        foreach (var strength in report.Strengths)
        {
            Console.WriteLine($"  {strength.Title}: {strength.AvgScore}/{strength.MaxPossible} ({strength.PerformanceRatio})");
        }

        // Criteria needing improvement
        Console.WriteLine("Weaknesses:");
        foreach (var weakness in report.Weaknesses)
        {
            Console.WriteLine($"  {weakness.Title}: {weakness.AvgScore}/{weakness.MaxPossible} ({weakness.PerformanceRatio})");
        }
    }
}
```

## Rubric Templates

Rubric templates are reusable rubrics scoped to your organization. They optionally store an `assignmentContent` so the same source material is automatically supplied every time the template is used.

### Create Rubric Template

```
POST /assignment-grader/rubric-templates
```

#### Request Body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | `string` | Yes | Display name for the template. |
| `description` | `string` | No | Notes about when/how to use the template. |
| `assignmentContent` | `string` | No | Default source material applied to any grading request that uses this template (overridable per request). |
| `criteria` | `RubricCriterion[]` | Yes | Grading criteria. Must contain at least one entry. |

#### Example Request (curl)

```bash
curl -X POST https://api.yourapp.com/api/v1/assignment-grader/rubric-templates \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Water Cycle — Reading Comprehension",
    "description": "Grades student answers about the water cycle reading passage",
    "assignmentContent": "The Water Cycle\n\nWater on Earth is constantly moving...",
    "criteria": [
      { "title": "Q1 — Evaporation",   "description": "Mentions sun heating water", "pointsPossible": 5 },
      { "title": "Q2 — Condensation",  "description": "Describes vapor cooling/clouds", "pointsPossible": 5 },
      { "title": "Q3 — Precipitation", "description": "Names ≥3 forms of precipitation", "pointsPossible": 5 }
    ]
  }'
```

#### Response, `201 Created`

A `RubricTemplate` document.

#### SDK Examples

JavaScript

```javascript
const templateParams = {
  name: "Standard Essay Rubric",
  description: "Basic 100-point essay grading rubric",
  assignmentContent: "Optional — default source material for this template", // Optional: used when no assignmentContent is provided at grading time
  criteria: [
    {
      pointsPossible: 25,
      title: "Thesis Statement",
      description: "Clear, arguable thesis that addresses the prompt"
    },
    {
      pointsPossible: 30,
      title: "Evidence and Analysis",
      description: "Strong evidence with thorough analysis"
    },
    {
      pointsPossible: 25,
      title: "Organization",
      description: "Logical structure with smooth transitions"
    },
    {
      pointsPossible: 20,
      title: "Writing Mechanics",
      description: "Grammar, spelling, and citation format"
    }
  ]
};

const template = await client.v1.assignmentGrader.rubricTemplates.create(templateParams);
console.log(`Template created: ${template._id}`);
```

Python

```python
template_params = {
    "name": "Standard Essay Rubric",
    "description": "Basic 100-point essay grading rubric",
    "assignmentContent": "Optional — default source material for this template",  # Optional: used when no assignmentContent is provided at grading time
    "criteria": [
        {
            "pointsPossible": 25,
            "title": "Thesis Statement",
            "description": "Clear, arguable thesis that addresses the prompt"
        },
        {
            "pointsPossible": 30,
            "title": "Evidence and Analysis",
            "description": "Strong evidence with thorough analysis"
        },
        {
            "pointsPossible": 25,
            "title": "Organization",
            "description": "Logical structure with smooth transitions"
        },
        {
            "pointsPossible": 20,
            "title": "Writing Mechanics",
            "description": "Grammar, spelling, and citation format"
        }
    ]
}

template = client.v1.assignment_grader.rubric_templates().create(template_params)
print(f"Template created: {template._id}")
```

Java

```java
RubricTemplateCreateParams templateParams = RubricTemplateCreateParams.builder()
    .name("Standard Essay Rubric")
    .description("Basic 100-point essay grading rubric")
    .assignmentContent("Optional — default source material for this template") // Optional: used when no assignmentContent is provided at grading time
    .addCriterion(RubricTemplateCreateParams.Criterion.builder()
        .pointsPossible(25.0)
        .title("Thesis Statement")
        .description("Clear, arguable thesis that addresses the prompt")
        .build())
    .addCriterion(RubricTemplateCreateParams.Criterion.builder()
        .pointsPossible(30.0)
        .title("Evidence and Analysis")
        .description("Strong evidence with thorough analysis")
        .build())
    .addCriterion(RubricTemplateCreateParams.Criterion.builder()
        .pointsPossible(25.0)
        .title("Organization")
        .description("Logical structure with smooth transitions")
        .build())
    .addCriterion(RubricTemplateCreateParams.Criterion.builder()
        .pointsPossible(20.0)
        .title("Writing Mechanics")
        .description("Grammar, spelling, and citation format")
        .build())
    .build();

RubricTemplateCreateResponse template = client.v1().assignmentGrader()
    .rubricTemplates().create(templateParams);
System.out.println("Template created: " + template._id());
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.AssignmentGrader;
using StudyfetchSDK.Models.V1.AssignmentGrader.RubricTemplates;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

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

        var templateParams = new RubricTemplateCreateParams()
        {
            Name = "Standard Essay Rubric",
            Description = "Basic 100-point essay grading rubric",
            AssignmentContent = "Optional — default source material for this template", // Optional: used when no assignmentContent is provided at grading time
            Criteria = new List<RubricCriterion>
            {
                new RubricCriterion()
                {
                    PointsPossible = 25,
                    Title = "Thesis Statement",
                    Description = "Clear, arguable thesis that addresses the prompt"
                },
                new RubricCriterion()
                {
                    PointsPossible = 30,
                    Title = "Evidence and Analysis",
                    Description = "Strong evidence with thorough analysis"
                },
                new RubricCriterion()
                {
                    PointsPossible = 25,
                    Title = "Organization",
                    Description = "Logical structure with smooth transitions"
                },
                new RubricCriterion()
                {
                    PointsPossible = 20,
                    Title = "Writing Mechanics",
                    Description = "Grammar, spelling, and citation format"
                }
            }
        };

        var template = await client.V1.AssignmentGrader.RubricTemplates.Create(templateParams);
        Console.WriteLine($"Template created: {template._ID}");
    }
}
```

### List Rubric Templates

```
GET /assignment-grader/rubric-templates
```

Returns every rubric template for the authenticated organization, sorted newest first.

#### Example Request (curl)

```bash
curl -X GET https://api.yourapp.com/api/v1/assignment-grader/rubric-templates \
  -H "x-api-key: YOUR_API_KEY"
```

#### SDK Examples

JavaScript

```javascript
const templates = await client.v1.assignmentGrader.rubricTemplates.list();

templates.forEach(template => {
  console.log(`Name: ${template.name}`);
  console.log(`Description: ${template.description}`);
  console.log(`Created: ${template.createdAt}`);
});
```

Python

```python
templates = client.v1.assignment_grader.rubric_templates().list()

for template in templates:
    print(f"Name: {template.name}")
    print(f"Description: {template.description}")
    print(f"Created: {template.created_at}")
```

Java

```java
import com.studyfetch.javasdk.models.v1.assignmentgrader.rubrictemplates.RubricTemplateListParams;
import com.studyfetch.javasdk.models.v1.assignmentgrader.rubrictemplates.RubricTemplateListResponse;

List<RubricTemplateListResponse> templates = client.v1().assignmentGrader()
    .rubricTemplates().list(
        RubricTemplateListParams.builder()
            .build()
    );

for (RubricTemplateListResponse template : templates) {
    System.out.println("Name: " + template.name());
    System.out.println("Description: " + template.description());
    System.out.println("Created: " + template.createdAt());
}
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.AssignmentGrader;
using System;
using System.Threading.Tasks;

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

        var templates = await client.V1.AssignmentGrader.RubricTemplates.List();

        foreach (var template in templates)
        {
            Console.WriteLine($"Name: {template.Name}");
            Console.WriteLine($"Description: {template.Description}");
            Console.WriteLine($"Created: {template.CreatedAt}");
        }
    }
}
```

### Get Rubric Template by ID

```
GET /assignment-grader/rubric-templates/:id
```

| Path param | Description |
| --- | --- |
| `id` | Rubric template `_id`. |

Returns a single `RubricTemplate`. Returns `404 Not Found` if not found in your organization.

#### SDK Examples

JavaScript

```javascript
const templateId = "template-123";
const template = await client.v1.assignmentGrader.rubricTemplates.getByID(templateId);

console.log(`Template: ${template.name}`);
console.log(`Description: ${template.description}`);
console.log(`Criteria:`, template.criteria);
```

Python

```python
template_id = "template-123"
template = client.v1.assignment_grader.rubric_templates().get_by_id(template_id)

print(f"Template: {template.name}")
print(f"Description: {template.description}")
print(f"Criteria: {template.criteria}")
```

Java

```java
import com.studyfetch.javasdk.models.v1.assignmentgrader.rubrictemplates.RubricTemplateGetByIdParams;
import com.studyfetch.javasdk.models.v1.assignmentgrader.rubrictemplates.RubricTemplateGetByIdResponse;

String templateId = "template-123";
RubricTemplateGetByIdResponse template = client.v1().assignmentGrader()
    .rubricTemplates().getById(
        RubricTemplateGetByIdParams.builder()
            .id(templateId)
            .build()
    );

System.out.println("Template: " + template.name());
System.out.println("Description: " + template.description());
System.out.println("Criteria: " + template.criteria());
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.AssignmentGrader;
using System;
using System.Threading.Tasks;

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

        string templateID = "template-123";
        var template = await client.V1.AssignmentGrader.RubricTemplates.GetByID(new()
        {
            ID = templateID
        });

        Console.WriteLine($"Template: {template.Name}");
        Console.WriteLine($"Description: {template.Description}");
        Console.WriteLine($"Criteria: {template.Criteria}");
    }
}
```

### Delete Rubric Template

```
DELETE /assignment-grader/rubric-templates/:id
```

#### Response, `200 OK`

```json
{ "success": true, "message": "Rubric template deleted successfully" }
```

Returns `404 Not Found` if the template doesn't exist or belongs to a different org.

#### SDK Examples

JavaScript

```javascript
const templateId = "template-123";
await client.v1.assignmentGrader.rubricTemplates.delete(templateId);
console.log("Rubric template deleted successfully");
```

Python

```python
template_id = "template-123"
client.v1.assignment_grader.rubric_templates().delete(template_id)
print("Rubric template deleted successfully")
```

Java

```java
String templateId = "template-123";
client.v1().assignmentGrader().rubricTemplates().delete(templateId);
System.out.println("Rubric template deleted successfully");
```

C#

```csharp
using StudyfetchSDK;
using System;
using System.Threading.Tasks;
using StudyfetchSDK.Models.V1.AssignmentGrader.RubricTemplates;

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

        string templateID = "template-123";
        await client.V1.AssignmentGrader.RubricTemplates.Delete(new()
        {
            ID = templateID
        });
        Console.WriteLine("Rubric template deleted successfully");
    }
}
```

## Assignment Content

The `assignmentContent` field provides the AI grader with **the original material students were working from**, such as a reading passage, case study, or prompt. This is critical for accurate grading because the AI can verify student answers against the actual source instead of relying on its own knowledge.

### Where You Can Set It

| Location | When it's used |
| --- | --- |
| Top-level field on `POST /assignment-grader/create` | Used for that single grading request |
| Inside the `rubric` object on `POST /assignment-grader/create` | Same as above (top-level takes priority if both set) |
| On a rubric template (`POST /assignment-grader/rubric-templates`) | Automatically used whenever that template is selected for grading, unless overridden at the request level |

### Priority Order

When `assignmentContent` is specified in multiple places, the following priority applies:

1. **Top-level `assignmentContent`** on the grading request (highest priority)
2. **`assignmentContent` inside the `rubric` object** on the grading request
3. **`assignmentContent` saved on the rubric template** (if `rubricTemplateId` is used, lowest priority)

Note

This means you can save a default passage on a rubric template and override it per-request when needed, for example, if you update a passage slightly for one section of students.

## Usage Patterns

Every grading request has up to four pieces working together:

| Piece | What it is | Where it goes |
| --- | --- | --- |
| **Source material** | The essay, passage, case study, or prompt the student reads first | `assignmentContent` |
| **Question** | What the student is asked to do or answer | Criterion `title` and `description` |
| **Rubric** | The grading criteria, what counts as a good answer | `rubric.criteria[]` or a saved rubric template |
| **Student answer** | The student's actual submission | `textToGrade` or `materialId` |

The AI grader reads all four, then scores the student's answer against the rubric using the source material as the ground truth. Below are the most common patterns for combining them.

### Pattern 1, Reading Comprehension (Passage + Questions)

A teacher assigns a reading passage and asks students to answer questions about it. Each question becomes a rubric criterion, and the passage goes in `assignmentContent`.

```bash
curl -X POST https://api.yourapp.com/api/v1/assignment-grader/create \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Chapter 5 Reading Comprehension",
    "assignmentContent": "The Industrial Revolution began in Britain in the late 18th century. Factories replaced cottage industries, steam power transformed transportation, and urban populations surged as workers migrated from rural areas. Working conditions were often dangerous, with long hours and child labor common until reform acts were passed in the mid-19th century.",
    "textToGrade": "1. The Industrial Revolution started in Britain.\n2. Steam power was used for transportation.\n3. Working conditions were great because factories were new.\n4. Child labor ended because of the Reform Acts.",
    "rubric": {
      "criteria": [
        {
          "title": "Q1 — Origin",
          "description": "Where and when did the Industrial Revolution begin? Student should mention Britain and the late 18th century.",
          "pointsPossible": 5
        },
        {
          "title": "Q2 — Steam power",
          "description": "How did steam power change society? Student should mention transportation.",
          "pointsPossible": 5
        },
        {
          "title": "Q3 — Working conditions",
          "description": "Describe working conditions. Student should mention dangerous conditions, long hours, or child labor — NOT that conditions were good.",
          "pointsPossible": 5
        },
        {
          "title": "Q4 — Reform",
          "description": "What ended child labor? Student should reference reform acts in the mid-19th century.",
          "pointsPossible": 5
        }
      ]
    },
    "assignmentId": "HISTORY-CH5",
    "studentIdentifier": "student@school.edu"
  }'
```

The AI will flag Q3 as incorrect because the student said conditions were "great", the passage says they were dangerous.

### Pattern 2, Essay / Open-Ended Writing (No Source Material)

When students write an original essay (no assigned reading), you don't need `assignmentContent` at all. The rubric criteria describe what a good essay looks like.

```bash
curl -X POST https://api.yourapp.com/api/v1/assignment-grader/create \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Persuasive Essay — School Uniforms",
    "textToGrade": "I believe schools should require uniforms because they reduce bullying and help students focus on learning instead of fashion...",
    "rubric": {
      "criteria": [
        {
          "title": "Thesis Statement",
          "description": "Clear, arguable thesis in the introduction that takes a position on the topic.",
          "pointsPossible": 20
        },
        {
          "title": "Supporting Arguments",
          "description": "At least two distinct arguments supporting the thesis, each with reasoning or evidence.",
          "pointsPossible": 30
        },
        {
          "title": "Counterargument",
          "description": "Acknowledges and responds to at least one opposing viewpoint.",
          "pointsPossible": 20
        },
        {
          "title": "Organization & Clarity",
          "description": "Logical paragraph structure, clear transitions, and a conclusion that reinforces the thesis.",
          "pointsPossible": 20
        },
        {
          "title": "Grammar & Mechanics",
          "description": "Minimal spelling, punctuation, and grammar errors.",
          "pointsPossible": 10
        }
      ]
    },
    "assignmentId": "ESSAY-UNIFORMS",
    "studentIdentifier": "student@school.edu"
  }'
```

### Pattern 3, Case Study Analysis (Scenario + Specific Questions)

Give the AI a case study as `assignmentContent`, then define criteria that test whether the student applied the right frameworks or identified the right issues.

```bash
curl -X POST https://api.yourapp.com/api/v1/assignment-grader/create \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Business Ethics Case Study",
    "assignmentContent": "Case Study: GreenTech Corp discovered that one of their suppliers uses child labor. The supplier accounts for 40% of GreenTech raw materials and switching would increase costs by 25%. GreenTech CEO must decide whether to continue the relationship, find alternatives, or publicly disclose the issue. Stakeholders include shareholders, employees, the children involved, and consumers.",
    "textToGrade": "GreenTech should drop the supplier immediately because child labor is wrong. They can find another supplier easily.",
    "rubric": {
      "criteria": [
        {
          "title": "Stakeholder Identification",
          "description": "Student identifies at least 3 stakeholders from the case and explains how each is affected.",
          "pointsPossible": 10
        },
        {
          "title": "Ethical Framework",
          "description": "Student applies at least one ethical framework (utilitarian, deontological, virtue ethics, etc.) to analyze the dilemma.",
          "pointsPossible": 15
        },
        {
          "title": "Trade-off Analysis",
          "description": "Student acknowledges the cost/supply trade-off (25% cost increase, 40% of materials) rather than oversimplifying.",
          "pointsPossible": 10
        },
        {
          "title": "Recommendation Quality",
          "description": "Student provides a nuanced recommendation with implementation steps, not just a one-line answer.",
          "pointsPossible": 15
        }
      ]
    }
  }'
```

The AI will notice the student oversimplified, they didn't acknowledge the trade-offs or apply an ethical framework.

### Pattern 4, Reusable Template for Repeated Assignments

When many students answer the same questions about the same material, save everything in a rubric template so you only define the rubric and passage once.

**Create the template once:**

```bash
curl -X POST https://api.yourapp.com/api/v1/assignment-grader/rubric-templates \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Photosynthesis Quiz",
    "assignmentContent": "Photosynthesis is the process by which green plants convert sunlight, water, and carbon dioxide into glucose and oxygen. It occurs primarily in the leaves, within organelles called chloroplasts. The light-dependent reactions happen in the thylakoid membranes, while the Calvin cycle occurs in the stroma.",
    "criteria": [
      { "title": "Inputs", "description": "Student names sunlight, water, and CO2 as inputs.", "pointsPossible": 5 },
      { "title": "Outputs", "description": "Student names glucose and oxygen as outputs.", "pointsPossible": 5 },
      { "title": "Location", "description": "Student mentions chloroplasts or leaves.", "pointsPossible": 5 }
    ]
  }'
```

**Grade each student, just send the answer:**

```bash
curl -X POST https://api.yourapp.com/api/v1/assignment-grader/create \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Photosynthesis Quiz",
    "rubricTemplateId": "TEMPLATE_ID",
    "assignmentId": "BIO-PHOTOSYNTHESIS",
    "studentIdentifier": "student@school.edu",
    "textToGrade": "Plants use sunlight and water to make food. This happens in the leaves."
  }'
```

The passage and rubric are pulled from the template automatically, no need to re-send them.

### Choosing the Right Pattern

| Scenario | Use `assignmentContent`? | Use a template? |
| --- | --- | --- |
| Students answer questions about a specific reading | Yes | Yes, if grading multiple students |
| Students write an original essay with no assigned reading | No | Yes, if the rubric is reused across assignments |
| One-off grading of a single submission | Depends on whether there's source material | No, use an inline rubric |
| Same assignment given to 30+ students | Yes (bake it into the template) | Yes |
| Case study or lab report with reference material | Yes | Optional |

## Grading from Material

Grade an assignment that has already been uploaded as a material.

Warning

**Important**: Materials need time to process after upload. You must wait for the material to have `status: 'active'` before grading it. Attempting to grade a material that is still processing will fail.

**Tip**: Use the `createAndProcess` method for text materials to avoid manual waiting - it automatically waits for processing to complete.

JavaScript

```javascript
// First upload the assignment as a material
const material = await client.v1.materials.create({
  name: "Student Essay - John Doe",
  content: {
    type: "pdf",
    file: studentEssayFile
  },
  references: [
    {
      title: 'Assignment Instructions',
      url: 'https://example.com/assignment-instructions'
    }
  ] // Optional: source references
});

// Wait for material to finish processing
let materialStatus = 'processing';
while (materialStatus !== 'active') {
  const updatedMaterial = await client.v1.materials.retrieve(material._id);
  materialStatus = updatedMaterial.status;
  if (materialStatus === 'error') {
    throw new Error('Material processing failed');
  }
  if (materialStatus === 'processing') {
    await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds
  }
}

// Then grade using the material ID
const gradeResult = await client.v1.assignmentGrader.create({
  rubric: rubricDefinition,
  title: "Essay Assignment",
  materialId: material._id,
  assignmentId: "assign-456",
  studentIdentifier: "[john.doe@example.com](mailto:john.doe@example.com)"
});
```

Python

```python
import time

# First upload the assignment as a material
material = client.v1.materials.create({
    "name": "Student Essay - John Doe",
    "content": {
        "type": "pdf",
        "file": student_essay_file
    },
    "references": [
        {
            "title": "Assignment Instructions",
            "url": "https://example.com/assignment-instructions"
        }
    ]  # Optional: source references
})

# Wait for material to finish processing
material_status = "processing"
while material_status != "active":
    updated_material = client.v1.materials.retrieve(material["_id"])
    material_status = updated_material.status
    if material_status == "error":
        raise Exception("Material processing failed")
    if material_status == "processing":
        time.sleep(2)  # Wait 2 seconds

# Then grade using the material ID
grade_result = client.v1.assignment_grader.create({
    "rubric": rubric_definition,
    "title": "Essay Assignment",
    "materialId": material["_id"],
    "assignmentId": "assign-456",
    "studentIdentifier": "john.doe@example.com"
})
```

Java

```java
import com.studyfetch.javasdk.models.v1.materials.upload.UploadUploadFileParams;
import com.studyfetch.javasdk.models.v1.materials.MaterialResponse;
import com.studyfetch.javasdk.models.v1.materials.MaterialRetrieveParams;
import java.io.File;
import java.io.FileInputStream;

// First upload the assignment as a material
File studentEssayFile = new File("student-essay.pdf");
UploadUploadFileParams uploadParams = UploadUploadFileParams.builder()
    .file(new FileInputStream(studentEssayFile))
    .name("Student Essay - John Doe")
    .build();
MaterialResponse material = client.v1().materials().upload().uploadFile(uploadParams);

// Wait for material to finish processing
MaterialResponse.Status status = MaterialResponse.Status.PROCESSING;
while (status.equals(MaterialResponse.Status.PROCESSING)) {
    Thread.sleep(2000); // Wait 2 seconds
    MaterialResponse updatedMaterial = client.v1().materials().retrieve(
        MaterialRetrieveParams.builder()
            .id(material._id())
            .build()
    );
    status = updatedMaterial.status();
    if (status.equals(MaterialResponse.Status.ERROR)) {
        throw new Exception("Material processing failed");
    }
}

// Then grade using the material ID
AssignmentGraderCreateParams gradeParams = AssignmentGraderCreateParams.builder()
    .rubric(rubricDefinition)
    .title("Essay Assignment")
    .materialId(material._id())
    .assignmentId("assign-456")
    .studentIdentifier("john.doe@example.com")
    .build();
AssignmentGraderCreateResponse gradeResult = client.v1().assignmentGrader().create(gradeParams);
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using StudyfetchSDK.Models.V1.AssignmentGrader;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

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

        // First upload the assignment as a material
        var fileBytes = await File.ReadAllBytesAsync("student-essay.pdf");
        var material = await client.V1.Materials.Upload.UploadFile(new()
        {
            File = fileBytes,
            FileName = "student-essay.pdf",
            Name = "Student Essay - John Doe"
        });

        // Wait for material to finish processing
        string materialStatus = "processing";
        while (materialStatus != "active")
        {
            var updatedMaterial = await client.V1.Materials.Retrieve(new()
            {
                ID = material._ID
            });
            materialStatus = updatedMaterial.Status.Raw();
            if (materialStatus == "error")
            {
                throw new Exception("Material processing failed");
            }
            if (materialStatus == "processing")
            {
                await Task.Delay(2000); // Wait 2 seconds
            }
        }

        // Then grade using the material ID
        var gradeResult = await client.V1.AssignmentGrader.Create(new AssignmentGraderCreateParams()
        {
            Rubric = rubricDefinition,
            Title = "Essay Assignment",
            MaterialID = material._ID,
            AssignmentID = "assign-456",
            StudentIdentifier = "john.doe@example.com"
        });
    }
}
```

### Using Create and Process (Recommended for Text)

For text assignments, use `createAndProcess` to automatically wait for processing:

JavaScript

```javascript
// Create and process text material in one step
const material = await client.v1.materials.createAndProcess({
  content: { 
    type: 'text',
    text: studentEssayText
  },
  name: 'Student Essay - John Doe',
  references: [
    {
      title: 'Assignment Prompt',
      url: 'https://example.com/assignment-prompt'
    }
  ] // Optional: source references
});

// Material is immediately ready for grading
const gradeResult = await client.v1.assignmentGrader.create({
  rubric: rubricDefinition,
  title: "Essay Assignment",
  materialId: material._id,
  assignmentId: "assign-456",
  studentIdentifier: "[john.doe@example.com](mailto:john.doe@example.com)"
});
```

Python

```python
# Create and process text material in one step
material = client.v1.materials.create_and_process({
    'content': {
        'type': 'text',
        'text': student_essay_text
    },
    'name': 'Student Essay - John Doe',
    'references': [
        {
            'title': 'Assignment Prompt',
            'url': 'https://example.com/assignment-prompt'
        }
    ]  # Optional: source references
})

# Material is immediately ready for grading
grade_result = client.v1.assignment_grader.create({
    "rubric": rubric_definition,
    "title": "Essay Assignment",
    "materialId": material["_id"],
    "assignmentId": "assign-456",
    "studentIdentifier": "john.doe@example.com"
})
```

Java

```java
import com.studyfetch.javasdk.models.v1.materials.MaterialCreateAndProcessParams;
import com.studyfetch.javasdk.models.v1.materials.MaterialResponse;
import com.studyfetch.javasdk.models.v1.materials.Content;
import com.studyfetch.javasdk.models.v1.materials.Reference;
import java.util.List;

// Create and process text material in one step
MaterialCreateAndProcessParams createParams = MaterialCreateAndProcessParams.builder()
    .content(Content.builder()
        .type(Content.Type.TEXT)
        .text(studentEssayText)
        .build())
    .name("Student Essay - John Doe")
    .references(List.of(
        Reference.builder()
            .title("Assignment Prompt")
            .url("https://example.com/assignment-prompt")
            .build()
    )) // Optional: source references
    .build();
MaterialResponse material = client.v1().materials().createAndProcess(createParams);

// Material is immediately ready for grading
AssignmentGraderCreateParams gradeParams = AssignmentGraderCreateParams.builder()
    .rubric(rubricDefinition)
    .title("Essay Assignment")
    .materialId(material._id())
    .assignmentId("assign-456")
    .studentIdentifier("john.doe@example.com")
    .build();
AssignmentGraderCreateResponse gradeResult = client.v1().assignmentGrader().create(gradeParams);
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using StudyfetchSDK.Models.V1.AssignmentGrader;
using System;
using System.Threading.Tasks;

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

        // Create and process text material in one step
        string studentEssayText = "Student essay content here...";
        var material = await client.V1.Materials.CreateAndProcess(new()
        {
            Content = new()
            {
                Type = StudyfetchSDK.Models.V1.Materials.ContentProperties.Type.Text,
                Text = studentEssayText
            },
            Name = "Student Essay - John Doe"
        });

        // Material is immediately ready for grading
        var gradeResult = await client.V1.AssignmentGrader.Create(new AssignmentGraderCreateParams()
        {
            Rubric = rubricDefinition,
            Title = "Essay Assignment",
            MaterialID = material._ID,
            AssignmentID = "assign-456",
            StudentIdentifier = "john.doe@example.com"
        });
    }
}
```

## Full Example: Passage Reading + Question Answering

This end-to-end walkthrough shows the full lifecycle: create a reusable template with a passage baked in, grade multiple students against it, then pull a class-wide educator report.

### Step 1, Create a Rubric Template with the Passage

```bash
curl -X POST https://api.yourapp.com/api/v1/assignment-grader/rubric-templates \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "The Water Cycle — Reading Comprehension",
    "description": "Grades student answers about the water cycle passage",
    "assignmentContent": "The Water Cycle\n\nWater on Earth is constantly moving. The sun heats water in oceans, lakes, and rivers, causing it to evaporate into water vapor that rises into the atmosphere. As the vapor rises, it cools and condenses into tiny droplets that form clouds — a process called condensation. When enough droplets gather, they fall back to Earth as precipitation (rain, snow, sleet, or hail). Some precipitation flows into rivers and streams as runoff, eventually returning to the ocean. Some seeps into the ground and becomes groundwater, which feeds wells and springs. This continuous movement of water — from surface to atmosphere and back — is called the water cycle, or hydrological cycle.",
    "criteria": [
      { "title": "Q1 — Evaporation",   "description": "Mentions the sun heating bodies of water", "pointsPossible": 5 },
      { "title": "Q2 — Condensation",  "description": "Describes vapor cooling and forming droplets/clouds", "pointsPossible": 5 },
      { "title": "Q3 — Precipitation", "description": "Names ≥3 forms of precipitation from the passage", "pointsPossible": 5 },
      { "title": "Q4 — Groundwater",   "description": "Mentions groundwater, wells, or springs", "pointsPossible": 5 },
      { "title": "Q5 — Closed loop",   "description": "Conveys that the cycle has no beginning or end", "pointsPossible": 5 }
    ]
  }'
```

Capture `_id` from the response, that's your `TEMPLATE_ID`.

JavaScript

```javascript
const template = await client.v1.assignmentGrader.rubricTemplates.create({
  name: "The Water Cycle — Reading Comprehension",
  description: "Grades student answers about the water cycle reading passage",
  assignmentContent: "The Water Cycle\n\nWater on Earth is constantly moving. The sun heats water in oceans, lakes, and rivers, causing it to evaporate into water vapor that rises into the atmosphere. As the vapor rises, it cools and condenses into tiny droplets that form clouds — a process called condensation. When enough droplets gather, they fall back to Earth as precipitation (rain, snow, sleet, or hail). Some precipitation flows into rivers and streams as runoff, eventually returning to the ocean. Some seeps into the ground and becomes groundwater, which feeds wells and springs. This continuous movement of water — from surface to atmosphere and back — is called the water cycle, or hydrological cycle. It has no beginning or end; it is a closed loop that has been running for billions of years.",
  criteria: [
    {
      title: "Question 1 — Evaporation",
      description: "What causes water to evaporate? Student should mention the sun heating bodies of water.",
      pointsPossible: 5
    },
    {
      title: "Question 2 — Condensation",
      description: "Explain what condensation is. Student should describe water vapor cooling and forming droplets/clouds.",
      pointsPossible: 5
    },
    {
      title: "Question 3 — Precipitation Types",
      description: "Name at least 3 forms of precipitation mentioned in the passage.",
      pointsPossible: 5
    },
    {
      title: "Question 4 — Groundwater",
      description: "What happens to precipitation that seeps into the ground? Student should mention groundwater, wells, or springs.",
      pointsPossible: 5
    },
    {
      title: "Question 5 — Why is it called a cycle?",
      description: "Why is the water cycle described as a closed loop? Student should convey that it has no beginning or end and repeats continuously.",
      pointsPossible: 5
    }
  ]
});

console.log(`Template created: ${template._id}`);
```

Python

```python
template = client.v1.assignment_grader.rubric_templates().create({
    "name": "The Water Cycle — Reading Comprehension",
    "description": "Grades student answers about the water cycle reading passage",
    "assignmentContent": "The Water Cycle\n\nWater on Earth is constantly moving. The sun heats water in oceans, lakes, and rivers, causing it to evaporate into water vapor that rises into the atmosphere. As the vapor rises, it cools and condenses into tiny droplets that form clouds — a process called condensation. When enough droplets gather, they fall back to Earth as precipitation (rain, snow, sleet, or hail). Some precipitation flows into rivers and streams as runoff, eventually returning to the ocean. Some seeps into the ground and becomes groundwater, which feeds wells and springs. This continuous movement of water — from surface to atmosphere and back — is called the water cycle, or hydrological cycle. It has no beginning or end; it is a closed loop that has been running for billions of years.",
    "criteria": [
        {
            "title": "Question 1 — Evaporation",
            "description": "What causes water to evaporate? Student should mention the sun heating bodies of water.",
            "pointsPossible": 5
        },
        {
            "title": "Question 2 — Condensation",
            "description": "Explain what condensation is. Student should describe water vapor cooling and forming droplets/clouds.",
            "pointsPossible": 5
        },
        {
            "title": "Question 3 — Precipitation Types",
            "description": "Name at least 3 forms of precipitation mentioned in the passage.",
            "pointsPossible": 5
        },
        {
            "title": "Question 4 — Groundwater",
            "description": "What happens to precipitation that seeps into the ground? Student should mention groundwater, wells, or springs.",
            "pointsPossible": 5
        },
        {
            "title": "Question 5 — Why is it called a cycle?",
            "description": "Why is the water cycle described as a closed loop? Student should convey that it has no beginning or end and repeats continuously.",
            "pointsPossible": 5
        }
    ]
})

print(f"Template created: {template._id}")
```

### Step 2, Grade Each Student's Submission

Repeat this for every student, keeping the same `assignmentId`.

```bash
curl -X POST https://api.yourapp.com/api/v1/assignment-grader/create \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Water Cycle Reading Comprehension",
    "rubricTemplateId": "TEMPLATE_ID",
    "assignmentId": "WATER-CYCLE-2026",
    "studentIdentifier": "jane.doe@school.edu",
    "textToGrade": "1. Water evaporates because the sun heats it...\n2. Condensation is when vapor turns into clouds...\n3. Rain, snow, sleet.\n4. It becomes part of rivers.\n5. It just keeps going."
  }'
```

JavaScript

```javascript
const result = await client.v1.assignmentGrader.create({
  title: "Water Cycle Reading Comprehension",
  rubricTemplateId: template._id,  // Reuse the template from Step 1
  assignmentId: "WATER-CYCLE-2026",
  studentIdentifier: "jane.doe@school.edu",
  textToGrade: "1. Water evaporates because the sun heats it up from oceans and lakes.\n2. Condensation is when water vapor goes up and turns into clouds.\n3. Rain, snow, and sleet.\n4. It goes into the ground and becomes part of rivers.\n5. It is called a cycle because it keeps going around and around without stopping."
});

console.log(`Grade: ${result.gradedAssignment.grade}/100`);

// Per-criterion feedback
result.gradedAssignment.rubric.criteria.forEach(c => {
  console.log(`${c.title}: ${c.pointsAwarded}/${c.pointsPossible}`);
  console.log(`  Feedback: ${c.feedback}`);
});
```

Python

```python
result = client.v1.assignment_grader.create({
    "title": "Water Cycle Reading Comprehension",
    "rubricTemplateId": template._id,  # Reuse the template from Step 1
    "assignmentId": "WATER-CYCLE-2026",
    "studentIdentifier": "jane.doe@school.edu",
    "textToGrade": "1. Water evaporates because the sun heats it up from oceans and lakes.\n2. Condensation is when water vapor goes up and turns into clouds.\n3. Rain, snow, and sleet.\n4. It goes into the ground and becomes part of rivers.\n5. It is called a cycle because it keeps going around and around without stopping."
})

print(f"Grade: {result.graded_assignment.grade}/100")

# Per-criterion feedback
for c in result.graded_assignment.rubric.criteria:
    print(f"{c.title}: {c.points_awarded}/{c.points_possible}")
    print(f"  Feedback: {c.feedback}")
```

**What the AI grader does:**

1. Reads the **rubric criteria** (questions + what to look for)
2. Reads the **assignment content** (the water cycle passage) as the source of truth
3. Reads the **student's submission** (their answers)
4. Grades each answer against the passage, not against its own knowledge
5. Returns per-criterion scores and feedback

### Step 3, Pull the Educator Report

After grading multiple students with the same `assignmentId`, generate an aggregate report:

```bash
curl -X GET https://api.yourapp.com/api/v1/assignment-grader/educator-report/WATER-CYCLE-2026 \
  -H "x-api-key: YOUR_API_KEY"
```

You'll receive aggregate statistics, grade distribution, per-criterion analysis, strengths, weaknesses, and a list of every submission.

JavaScript

```javascript
const report = await client.v1.assignmentGrader.generateReport("WATER-CYCLE-2026");

console.log(`Average Grade: ${report.statistics.averageGrade}`);
console.log(`Total Submissions: ${report.totalSubmissions}`);

// See which criteria students struggled with
report.weaknesses.forEach(w => {
  console.log(`Weakness: ${w.title} — avg ${w.avgScore}/${w.maxPossible}`);
});
```

Python

```python
report = client.v1.assignment_grader.generate_report("WATER-CYCLE-2026")

print(f"Average Grade: {report.statistics.average_grade}")
print(f"Total Submissions: {report.total_submissions}")

# See which criteria students struggled with
for w in report.weaknesses:
    print(f"Weakness: {w.title} — avg {w.avg_score}/{w.max_possible}")
```

## Inline Rubric with Assignment Content (No Template)

If you don't need a reusable template, you can pass everything in a single request:

JavaScript

```javascript
const result = await client.v1.assignmentGrader.create({
  title: "Pop Quiz — Chapter 3",
  assignmentContent: "Chapter 3 discusses the three branches of the US government: the Legislative branch (Congress), the Executive branch (the President), and the Judicial branch (the Supreme Court). The Legislative branch makes laws, the Executive branch enforces laws, and the Judicial branch interprets laws...",
  textToGrade: "1. The three branches are Legislative, Executive, and Military.\n2. Congress makes the laws.\n3. The Supreme Court enforces the laws.",
  rubric: {
    criteria: [
      {
        title: "Three Branches",
        description: "Student correctly names all three branches of government",
        pointsPossible: 10
      },
      {
        title: "Legislative Role",
        description: "Student explains what the Legislative branch does",
        pointsPossible: 5
      },
      {
        title: "Judicial Role",
        description: "Student explains what the Judicial branch does",
        pointsPossible: 5
      }
    ]
  }
});

// The AI will catch that the student said "Military" instead of "Judicial"
// and mixed up what the Supreme Court does
console.log(`Grade: ${result.gradedAssignment.grade}`);
```

Python

```python
result = client.v1.assignment_grader.create({
    "title": "Pop Quiz — Chapter 3",
    "assignmentContent": "Chapter 3 discusses the three branches of the US government: the Legislative branch (Congress), the Executive branch (the President), and the Judicial branch (the Supreme Court). The Legislative branch makes laws, the Executive branch enforces laws, and the Judicial branch interprets laws...",
    "textToGrade": "1. The three branches are Legislative, Executive, and Military.\n2. Congress makes the laws.\n3. The Supreme Court enforces the laws.",
    "rubric": {
        "criteria": [
            {
                "title": "Three Branches",
                "description": "Student correctly names all three branches of government",
                "pointsPossible": 10
            },
            {
                "title": "Legislative Role",
                "description": "Student explains what the Legislative branch does",
                "pointsPossible": 5
            },
            {
                "title": "Judicial Role",
                "description": "Student explains what the Judicial branch does",
                "pointsPossible": 5
            }
        ]
    }
})

# The AI will catch that the student said "Military" instead of "Judicial"
# and mixed up what the Supreme Court does
print(f"Grade: {result.graded_assignment.grade}")
```

## Object Reference

### `RubricCriterion` Object

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `title` | `string` | Yes | Short name of the criterion. |
| `description` | `string` | No | What you want graded. Written as instructions for the grader. |
| `pointsPossible` | `number` | Yes | Maximum points this criterion can earn. |

### `Rubric` Object (Request Input)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `criteria` | `RubricCriterion[]` | Yes | Must have ≥ 1 entry. |
| `assignmentContent` | `string` | No | Optional source material (superseded by top-level `assignmentContent`). |

### `GradedRubricCriterion` Object (Response)

| Field | Type | Description |
| --- | --- | --- |
| `title` | `string` | Mirrors the input criterion title. |
| `description` | `string` | Mirrors the input criterion description. |
| `pointsAwarded` | `number` | Points the AI awarded. |
| `pointsPossible` | `number` | Maximum points for this criterion. |
| `feedback` | `string` | Per-criterion feedback (second-person, addressed to the student). |

### `AssignmentGrader` Object

| Field | Type | Description |
| --- | --- | --- |
| `_id` | `string` (ObjectId) | Document ID. |
| `title` | `string` | Assignment title. |
| `grade` | `number` | Overall percentage (0–100), rounded to nearest whole number. |
| `rubric.attemptFeedback` | `string` | Holistic feedback for the whole submission. |
| `rubric.criteria` | `GradedRubricCriterion[]` | Per-criterion results. |
| `organizationId` | `string` | Owning organization. |
| `userId` | `string \| null` | User who triggered the grading (or `null` for API-key auth). |
| `materialId` | `string?` | Material that was graded (mutually exclusive with `textToGrade`). |
| `textToGrade` | `string?` | Raw text that was graded. |
| `rubricTemplateId` | `string?` | Template used (if any). |
| `assignmentId` | `string?` | Grouping tag for educator reports. |
| `studentIdentifier` | `string?` | Student email or ID. |
| `assignmentContent` | `string?` | Source material the AI used. |
| `createdAt` | `Date` | Timestamp. |
| `updatedAt` | `Date` | Timestamp. |

### `RubricTemplate` Object

| Field | Type | Description |
| --- | --- | --- |
| `_id` | `string` (ObjectId) | Template ID. |
| `name` | `string` | Template name. |
| `description` | `string?` | Optional description. |
| `assignmentContent` | `string?` | Default source material attached to this template. |
| `criteria` | `RubricCriterion[]` | Grading criteria. |
| `organizationId` | `string` | Owning organization. |
| `createdBy` | `string` | User who created the template. |
| `createdAt` | `Date` | Timestamp. |
| `updatedAt` | `Date` | Timestamp. |

## Error Responses

All errors follow the standard exception envelope.

| Status | When |
| --- | --- |
| `400 Bad Request` | Invalid body, missing required fields, both `materialId` and `textToGrade` supplied, neither rubric nor template provided, empty rubric, or unknown grader action. |
| `401 Unauthorized` | Missing or invalid `x-api-key` / `Authorization` header. |
| `404 Not Found` | Assignment, template, or report target not found in your organization. |
| `500 Internal Server Error` | Unexpected failure (AI service error, DB failure, etc.). |

Example error body:

```json
{
  "statusCode": 400,
  "message": "Must provide either materialId or textToGrade",
  "error": "Bad Request"
}
```

## Tips & Best Practices

- **Provide `assignmentContent` whenever possible.** It dramatically improves grading accuracy because the AI grades against the actual source instead of guessing.
- **Keep `assignmentContent` focused.** Paste the actual passage or prompt students received. Don't include instructions meant for the grader, put those in the criterion `description` fields instead.
- **Use criterion descriptions as grading instructions.** For example: _"Award full points only if the student names ≥2 specific dates from the passage."_
- **Save templates for repeated assignments.** If 30 students are all answering the same reading comprehension, create a template once and reuse it with `rubricTemplateId`.
- **Always set `assignmentId` and `studentIdentifier`** when you plan to run an educator report. Without `assignmentId`, the report can't aggregate submissions.
- **Top-level wins for `assignmentContent`.** If you need a one-off override of the template's saved content, just include `assignmentContent` at the top level of the grading request.
- **Cap on inputs.** Materials larger than ~30k chars per item are truncated for grading; very large materials (>100k chars) are also truncated when extracted.
- **Strict grading.** The grader awards **0 points** for factually incorrect answers. Post-processing also detects keywords like _"incorrect", "wrong", "opposite"_ in the AI's per-criterion feedback and forces `pointsAwarded` to 0 (unless the feedback also indicates partial correctness with words like _"partially", "mostly"_).

## Audio Recap

Source: https://studyfetch-api.honen.com/docs/6acd32099de34cae55b272c5

## Overview

The Audio Recap component converts your study materials into audio summaries, perfect for reviewing content during commutes, workouts, or when visual reading isn't practical. Supports multiple voices and languages.

When **`topic`** is set and **`folders`** are included, generation can narrow folder-derived materials toward that topic (best-effort). Use **`excludedMaterialIds`** to omit specific materials that would otherwise be included from selected folders; IDs listed here are ignored if the same material is also listed in **`materials`** (explicit selections win).

You can also create a recap from a **`topic`** alone, with no `materials` or `folders`. A topic-only recap is generated from the model's own knowledge of the topic, so its sections have no source citations. A component needs at least one material, one folder, or a non-empty `topic`; otherwise the request fails with `Component requires at least one material or folder`.

Tip

**How much source material fits per length.** When you generate a recap from materials _without_ a **`topic`**, the selected materials are split evenly across the recap up to an approximate source-content budget for the chosen length, and anything beyond it is truncated. Set a **`topic`** to prioritize the most relevant content instead of having it split evenly. The longer the recap (`duration`), the more source material it can include:

| Length | `duration` (min) | Approx. source material |
| --- | --- | --- |
| Short | 3 | ~100 KB |
| Medium | 6 | ~250 KB |
| Long | 12 | ~250 KB |
| Extended | 24 | ~400 KB |
| Complete | 30 | ~400 KB |
| Ultra | 45 | ~500 KB |

If your materials exceed the budget for the length you picked, either use a longer `duration` or set a `topic` so the most relevant content is kept.

## Creating an Audio Recap Component

JavaScript

```javascript
import StudyfetchSDK from '@studyfetch/sdk';const client = new StudyfetchSDK({
  apiKey: 'your-api-key',
  baseURL: 'https://studyfetchapi.com',
});

const audioComponent = await client.v1.components.create({
  name: 'Biology Chapter Audio Summary',
  type: 'audio_recap',
  config: {
    materials: ['mat-123', 'mat-456'],
    folders: ['folder-789'],
    excludedMaterialIds: ['mat-skip-from-folder'],
    recapType: 'SUMMARY',
    duration: 10,
    numParts: 3,
    isMultiVoice: true,
    voice1: 'Morgan',
    voice2: 'Riley',
    model: 'gpt-4.1-2025-04-14',
    topic: 'Cell Biology',
    theme: 'Educational podcast style'
  }
});

console.log('Audio recap component created:', audioComponent._id);
```

Python

```python
from studyfetch_sdk import StudyfetchSDK

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

audio_component = client.v1.components.create(
    name="Biology Chapter Audio Summary",
    type="audio_recap",
    config={
        "materials": ["mat-123", "mat-456"],
        "folders": ["folder-789"],
        "excluded_material_ids": ["mat-skip-from-folder"],
        "recap_type": "SUMMARY",
        "duration": 10,
        "num_parts": 3,
        "is_multi_voice": True,
        "voice1": "Morgan",
        "voice2": "Riley",
        "model": "gpt-4.1-2025-04-14",
        "topic": "Cell Biology",
        "theme": "Educational podcast style"
    }
)

print(f"Audio recap component created: {audio_component.api_id}")
```

Java

```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 CreateAudioRecapComponent {
    public static void main(String[] args) {
        StudyfetchSdkClient client = StudyfetchSdkOkHttpClient.builder()
            .fromEnv()
            .baseUrl("https://studyfetchapi.com")
            .build();

        ComponentCreateParams params = ComponentCreateParams.builder()
            .name("Biology Chapter Audio Summary")
            .type(ComponentCreateParams.Type.AUDIO_RECAP)
            .config(ComponentCreateParams.Config.AudioRecapConfigDto.builder()
                .materials(List.of("mat-123", "mat-456"))
                .folders(List.of("folder-789"))
                .excludedMaterialIds(List.of("mat-skip-from-folder"))
                .recapType(ComponentCreateParams.Config.AudioRecapConfigDto.RecapType.SUMMARY)
                .duration(10)
                .numParts(3)
                .isMultiVoice(true)
                .voice1(ComponentCreateParams.Config.AudioRecapConfigDto.AudioRecapVoice.MORGAN)
                .voice2(ComponentCreateParams.Config.AudioRecapConfigDto.AudioRecapVoice.RILEY)
                .model("gpt-4.1-2025-04-14")
                .topic("Cell Biology")
                .theme("Educational podcast style")
                .build())
            .build();

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

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Components;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using RecapType = StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.ConfigProperties.AudioRecapConfigDtoProperties.RecapType;
using AudioRecapVoice = StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.ConfigProperties.AudioRecapConfigDtoProperties.AudioRecapVoice;

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

        var audioComponent = await client.V1.Components.Create(new()
        {
            Name = "Biology Chapter Audio Summary",
            Type = StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.Type.AudioRecap,
            Config = new StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.ConfigProperties.AudioRecapConfigDto()
            {
                Materials = new List<string> { "mat-123", "mat-456" },
                Folders = new List<string> { "folder-789" },
                ExcludedMaterialIds = new List<string> { "mat-skip-from-folder" },
                RecapType = RecapType.Summary,
                Duration = 10,
                NumParts = 3,
                IsMultiVoice = true,
                Voice1 = AudioRecapVoice.Morgan,
                Voice2 = AudioRecapVoice.Riley,
                Model = "gpt-4.1-2025-04-14",
                Topic = "Cell Biology",
                Theme = "Educational podcast style",
            }
        });

        Console.WriteLine($"Audio recap component created: {audioComponent._ID}");
    }
}
```

Note

Creating a component requires your organization API key, so it belongs on your server. The React Native SDK renders and drives existing components with embed tokens; see React Native.

## Configuration Parameters

#### `name`

string · required

Name of the audio recap component

#### `type`

string · required

Must be `"audio_recap"`

#### `config`

object · required

Audio recap configuration object. Provide at least one of `materials`, `folders`, or `topic`.

### Configuration Properties

These properties go inside `config`.

#### `materials`

array

Array of material IDs to generate audio from. Optional when `folders` or `topic` is set.

#### `folders`

array

Array of folder IDs containing materials

#### `excludedMaterialIds`

array

Material IDs to exclude when expanding selected **folders**. Has no effect on materials you list directly in **`materials`**; if an ID appears in both **`materials`** and **`excludedMaterialIds`**, the explicit **`materials`** entry wins.

#### `recapType`

string · default: SUMMARY

Type of audio recap to generate:

- `SUMMARY` - Concise summary highlighting important points
- `LECTURE` - Formal academic lecture style
- `PODCAST` - Conversational podcast-style discussion
- `AUDIO_BOOK` - Detailed audio book narration

#### `duration`

number · default: 10

Target duration in minutes for the audio recap (affects content depth). Longer durations also raise the amount of source material included before truncation, see the source-material budget table in the Overview.

#### `numParts`

number · default: 3

Number of sections to divide the content into

#### `isMultiVoice`

boolean · default: false

Whether to use multiple voices for a conversational format

#### `voice1`

string · default: Morgan

Primary narrator voice. One of:

- `Alex` (female)
- `Morgan` (male) - Default
- `Riley` (female)
- `Jordan` (female)
- `Sparky` - StudyFetch's mascot voice; uses its own intro lines
- `Taylor` (male)

Older values are still accepted and mapped to the voices above, but new integrations should use these names.

#### `voice2`

string · default: Riley

Secondary voice for multi-voice conversations (used when `isMultiVoice` is true). Accepts the same values as `voice1`.

#### `model`

string · default: gemini-3-flash-preview

AI model to use for content generation

#### `topic`

string

Specific topic to focus on within the materials. Together with **`folders`**, this can steer which folder-derived materials are used (best-effort relevance filtering). With no `materials` or `folders`, the recap is generated from the topic alone.

#### `theme`

string

Style or theme for the audio content (e.g., "casual educational", "formal academic")

## Response

```json
{
  "_id": "comp_202mno",
  "name": "Biology Chapter Audio Summary",
  "type": "audio_recap",
  "status": "processing",
  "config": {
    "materials": ["mat-123", "mat-456"],
    "folders": ["folder-789"],
    "excludedMaterialIds": ["mat-skip-from-folder"],
    "recapType": "SUMMARY",
    "duration": 10,
    "numParts": 3,
    "isMultiVoice": true,
    "voice1": "Puck",
    "voice2": "Aoede",
    "model": "gpt-4.1-2025-04-14",
    "topic": "Cell Biology",
    "theme": "Educational podcast style"
  },
  "createdAt": "2024-01-15T10:00:00Z",
  "updatedAt": "2024-01-15T10:00:00Z",
  "organizationId": "org_456def",
  "audioFile": {
    "url": null,
    "duration": null,
    "size": null
  }
}
```

Note

The API stores voices by their underlying text-to-speech voice ID, so `config.voice1` and `config.voice2` in responses can differ from the names you sent (for example, `Morgan` is stored as `Puck` and `Riley` as `Aoede`). Both forms are accepted on later updates.

## Embedding This Component

Once you've created an Audio Recap component, you can embed it on your website using the embedding API.

### Generate Embed URL

JavaScript

```javascript
const embedResponse = await client.v1.components.generateEmbed(audioComponent._id, {
  // User tracking
  userId: 'user-456',
  studentName: 'Jane Smith',  // Student name for display
  groupIds: ['class-101', 'class-102'],
  sessionId: 'session-789',  // Audio-specific features
  features: {
    enableBadWordsFilter: true,  // Required by the SDK type
    enableTranscript: true,
    enableOutline: true,
    enableHistory: true
  },

  // Dimensions
  width: '100%',
  height: '400px',

  // Token expiry
  expiryHours: 24
});
```

Python

```python
embed_response = client.v1.components.generate_embed(
    audio_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_transcript": True,
        "enable_outline": True,
        "enable_history": True
    },
    width="100%",
    height="400px",
    expiry_hours=24
)
```

Java

```java
import com.studyfetch.javasdk.client.StudyfetchSdkClient;
import com.studyfetch.javasdk.client.okhttp.StudyfetchSdkOkHttpClient;
import com.studyfetch.javasdk.models.v1.components.ComponentGenerateEmbedParams;
import com.studyfetch.javasdk.models.v1.components.ComponentGenerateEmbedResponse;

StudyfetchSdkClient client = StudyfetchSdkOkHttpClient.builder()
    .fromEnv()
    .baseUrl("https://studyfetchapi.com")
    .build();

ComponentGenerateEmbedParams params = ComponentGenerateEmbedParams.builder()
    .id(audioComponent._id())
    // User tracking
    .userId("user-456")
    .studentName("Jane Smith")  // Student name for display
    .groupIds(List.of("class-101", "class-102"))
    .sessionId("session-789")
    
    // Audio-specific features
    .features(ComponentGenerateEmbedParams.Features.builder()
        .enableBadWordsFilter(true)  // Required: build() throws without it
        .enableTranscript(true)
        .enableOutline(true)
        .enableHistory(true)
        .build())
    
    // Dimensions
    .width("100%")
    .height("400px")
    
    // Token expiry
    .expiryHours(24)
    .build();

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

C#

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

public class GenerateAudioRecapEmbed
{
    public static async Task GenerateEmbed()
    {
        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 = "component_123abc", // Replace with your component ID
            UserID = "user-456",
            StudentName = "Jane Smith",  // Student name for display
            GroupIDs = new List<string> { "class-101", "class-102" },
            Width = "100%",
            Height = "600px"
        });

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

### Audio Recap-Specific Embedding Features

#### `features.enableTranscript`

boolean · default: true

Show synchronized transcript alongside the audio player

#### `features.enableOutline`

boolean · default: true

Display chapter markers and content outline for easy navigation

#### `features.enableHistory`

boolean · default: true

Remember playback position and show listening history

## Generation Status

Recaps are generated in the background after the component is created. Use the generation status endpoint to poll from your server until the recap is ready. Generation has two stages:

| Stage | Fields | Values |
| --- | --- | --- |
| Section scripts | `recap.generationStatus`, `recap.generationCompleted`, `recap.generationError` | `pending` → `completed`, or `failed` with `generationError` |
| Section audio | `sections[].generated`, `sections[].generating`, `sections[].errorMsg` | Per section: `generating` while text-to-speech runs, `generated` once audio is ready, `errorMsg` if it failed |

`recap` is `null` until generation has created the recap record, so keep polling until it is present. The `componentId` can be the public component ID (for example, `audio_recap_abc123`) or the component's `_id`. A component that is not an audio recap in your organization returns `404`.

JavaScript

```javascript
const status = await client.v1.audioRecaps.retrieveGenerationStatus('audio_recap_abc123');if (status.recap?.generationStatus === 'failed') {
  console.error(status.recap.generationError);
} else {
  const ready = status.sections.filter((s) => s.generated).length;
  console.log(`${ready} of ${status.sections.length} sections ready`);
}
```

Python

```python
status = client.v1.audio_recaps.retrieve_generation_status("audio_recap_abc123")

if status.recap and status.recap.generation_status == "failed":
    print(status.recap.generation_error)
else:
    ready = sum(1 for s in status.sections if s.generated)
    print(f"{ready} of {len(status.sections)} sections ready")
```

Java

```java
import com.studyfetch.javasdk.models.v1.audiorecaps.AudioRecapGenerationStatusResponse;

AudioRecapGenerationStatusResponse status = client.v1().audioRecaps()
    .retrieveGenerationStatus("audio_recap_abc123");

status.recap().ifPresent(recap -> System.out.println(recap.generationStatus()));
long ready = status.sections().stream().filter(s -> s.generated()).count();
System.out.println(ready + " of " + status.sections().size() + " sections ready");
```

C#

```csharp
using System.Linq;

var status = await client.V1.AudioRecaps.RetrieveGenerationStatus(new()
{
    ComponentID = "audio_recap_abc123",
});

Console.WriteLine(status.Recap?.GenerationStatus);
Console.WriteLine($"{status.Sections.Count(s => s.Generated)} of {status.Sections.Count} sections ready");
```

cURL

```curl
curl "https://studyfetchapi.com/api/v1/audio-recaps/components/audio_recap_abc123" \
  -H "x-api-key: your-api-key"
```

#### Response

```json
{
  "recap": {
    "id": "recap-id-1",
    "title": "Biology Chapter Audio Summary",
    "generationCompleted": true,
    "generationStatus": "completed"
  },
  "sections": [
    { "id": "section-id-1", "order": 0, "generated": true, "generating": false },
    { "id": "section-id-2", "order": 1, "generated": false, "generating": true },
    { "id": "section-id-3", "order": 2, "generated": false, "generating": false, "errorMsg": "..." }
  ]
}
```

## User Progress & Status Tracking

Audio Recap embeds can track how far each learner has listened through a recap. Progress is tracked per **user**, per **section**, and can be grouped by class or cohort using `groupIds`.

Progress is recorded only when the embed token includes a `userId`. Anonymous embeds do not create progress records.

Info

StudyFetch does not generate user IDs for you. Use the stable learner identifier from your platform, such as an LMS user ID, student ID, UUID, or email address.

### How Tracking Works

When you generate an embed URL, include one `userId` for the learner viewing that iframe:

```json
{
  "userId": "student-001",
  "groupIds": ["class-101"]
}
```

One embed session should represent one learner. To track a full class, generate one embed token per learner with a unique `userId` and shared `groupIds`.

The Audio Recap player reports:

- `lastPosition`, the learner's last known playback position for a section, in seconds. Only moves forward; a lower position does not overwrite a higher one.
- `completed`, whether the learner completed a section. Once a section is completed it stays completed.
- `groupIds`, group or class IDs supplied when generating the embed

### List Progress for a Recap

Use the audio recap **`componentId`** (for example, `audio_recap_abc123`) to list all users with recorded progress, most recently active first. Filter with `groupId` or `userId`, and page with `limit` and `skip`.

JavaScript

```javascript
const progress = await client.v1.audioRecaps.listProgress('audio_recap_abc123', {
  groupId: 'class-101',
});console.log(progress.users);
```

Python

```python
progress = client.v1.audio_recaps.list_progress(
    "audio_recap_abc123",
    group_id="class-101",
)

print(progress.users)
```

Java

```java
var progress = client.v1().audioRecaps().listProgress(
    "audio_recap_abc123",
    AudioRecapListProgressParams.builder()
        .groupId("class-101")
        .build()
);

System.out.println(progress.users());
```

C#

```csharp
var progress = await client.V1.AudioRecaps.ListProgress(new()
{
    ComponentID = "audio_recap_abc123",
    GroupID = "class-101",
});

Console.WriteLine(progress.TotalUsers);
```

cURL

```curl
curl "https://studyfetchapi.com/api/v1/audio-recaps/progress/audio_recap_abc123?groupId=class-101" \
  -H "x-api-key: your-api-key"
```

#### Response

```json
{
  "totalSections": 21,
  "totalUsers": 2,
  "users": [
    {
      "userId": "student-001",
      "groupIds": ["class-101"],
      "sectionsCompleted": 2,
      "completionPct": 10,
      "lastActivityAt": "2026-06-18T15:20:45.577Z",
      "currentSection": {
        "order": 2,
        "lastPosition": 9
      }
    }
  ]
}
```

### Get One User's Section Progress

Use this endpoint to see section-by-section playback status for a learner.

JavaScript

```javascript
const detail = await client.v1.audioRecaps.retrieveUserProgress(
  'audio_recap_abc123',
  'student-001'
);console.log(detail.sections);
```

Python

```python
detail = client.v1.audio_recaps.retrieve_user_progress(
    "audio_recap_abc123",
    "student-001",
)

print(detail.sections)
```

Java

```java
var detail = client.v1().audioRecaps().retrieveUserProgress(
    "audio_recap_abc123",
    "student-001"
);

System.out.println(detail.sections());
```

C#

```csharp
var detail = await client.V1.AudioRecaps.RetrieveUserProgress(new()
{
    ComponentID = "audio_recap_abc123",
    UserID = "student-001",
});

Console.WriteLine(detail.User.CompletionPct);
```

cURL

```curl
curl "https://studyfetchapi.com/api/v1/audio-recaps/progress/audio_recap_abc123/users/student-001" \
  -H "x-api-key: your-api-key"
```

#### Response

```json
{
  "user": {
    "userId": "student-001",
    "groupIds": ["class-101"],
    "sectionsCompleted": 2,
    "totalSections": 21,
    "completionPct": 10
  },
  "sections": [
    {
      "sectionId": "section-id-1",
      "order": 0,
      "isUserQuestion": false,
      "scriptPreview": "Sparky here! Let's explore...",
      "lastPosition": 8,
      "completed": true,
      "completedAt": "2026-06-18T15:20:17.740Z",
      "updatedAt": "2026-06-18T15:20:17.764Z"
    },
    {
      "sectionId": "section-id-2",
      "order": 1,
      "isUserQuestion": false,
      "scriptPreview": "Our exploration begins...",
      "lastPosition": 9,
      "completed": false,
      "updatedAt": "2026-06-18T15:20:45.577Z"
    }
  ]
}
```

`isUserQuestion` is `true` for sections added when a learner asks a follow-up question in the player.

### List Progress Across Recaps for One User

Use this endpoint to build a "My recaps" list: one call returns recap-level progress for a learner on up to 100 audio recaps. Pass `componentIds` (public component IDs or component `_id`s) to choose the recaps, or omit it to return every recap the learner has started. IDs that are not audio recap components in your organization are returned in `notFound` instead of failing the request. More than 100 IDs returns `400`.

JavaScript

```javascript
const progress = await client.v1.audioRecaps.listUserProgress('student-001', {
  componentIds: ['audio_recap_abc123', 'audio_recap_def456'],
});for (const recap of progress.recaps) {
  console.log(recap.componentId, `${recap.completionPct}%`, `${recap.remainingSeconds}s left`);
}
```

Python

```python
progress = client.v1.audio_recaps.list_user_progress(
    "student-001",
    component_ids=["audio_recap_abc123", "audio_recap_def456"],
)

for recap in progress.recaps:
    print(recap.component_id, f"{recap.completion_pct}%", f"{recap.remaining_seconds}s left")
```

Java

```java
import com.studyfetch.javasdk.models.v1.audiorecaps.AudioRecapListUserProgressParams;
import com.studyfetch.javasdk.models.v1.audiorecaps.AudioRecapListUserProgressResponse;

AudioRecapListUserProgressResponse progress = client.v1().audioRecaps().listUserProgress(
    "student-001",
    AudioRecapListUserProgressParams.builder()
        .addComponentId("audio_recap_abc123")
        .addComponentId("audio_recap_def456")
        .build()
);

progress.recaps().forEach(recap ->
    System.out.println(recap.componentId() + " " + recap.completionPct() + "%"));
```

C#

```csharp
var progress = await client.V1.AudioRecaps.ListUserProgress(new()
{
    UserID = "student-001",
    ComponentIDs = new List<string> { "audio_recap_abc123", "audio_recap_def456" },
});

foreach (var recap in progress.Recaps)
{
    Console.WriteLine($"{recap.ComponentID}: {recap.CompletionPct}% ({recap.RemainingSeconds}s left)");
}
```

cURL

```curl
curl "https://studyfetchapi.com/api/v1/audio-recaps/users/student-001/progress?componentIds=audio_recap_abc123,audio_recap_def456" \
  -H "x-api-key: your-api-key"
```

#### Response

```json
{
  "userId": "student-001",
  "recaps": [
    {
      "componentId": "audio_recap_abc123",
      "generationStatus": "completed",
      "completionPct": 33,
      "totalSections": 3,
      "sectionsCompleted": 1,
      "durationSeconds": 540,
      "listenedSeconds": 230,
      "remainingSeconds": 310,
      "lastActivityAt": "2026-10-05T15:20:45.577Z"
    }
  ],
  "notFound": ["audio_recap_def456"]
}
```

- `recaps` has one entry per resolved component, in request order.
- `completionPct` is `sectionsCompleted / totalSections`, from 0 to 100.
- `durationSeconds` is approximate: it uses each section's generated audio length, or an estimate from the script (about 150 words per minute) while that section's audio is not ready.
- `listenedSeconds` counts completed sections in full plus the last position in unfinished ones. `remainingSeconds` is `durationSeconds - listenedSeconds`.
- `lastActivityAt` is `null` if the learner has not started the recap.

### Embed in Your HTML

```html
<iframe 
  src="https://embed.studyfetch.com/component/comp_202mno?token=..."
  width="100%"
  height="400px"
  frameborder="0"
  style="border: 1px solid #e5e5e5; border-radius: 8px;">
</iframe>
```

## Section Feedback

Learners can rate each section with a thumbs up or thumbs down and an optional reason. List feedback for your organization, filtered by `componentId`, `recapId`, `feedbackType` (`thumbsUp` or `thumbsDown`), `userId`, `groupId`, `startDate`, and `endDate`. Results are paged with `limit` (default 100) and `skip`.

JavaScript

```javascript
const feedback = await client.v1.audioRecaps.listFeedback({
  componentId: 'audio_recap_abc123',
  feedbackType: 'thumbsDown',
});console.log(feedback.total, feedback.stats);

// The rated section plus its sibling sections, for one feedback item
const context = await client.v1.audioRecaps.retrieveFeedbackContext(feedback.feedback[0]._id);
```

Python

```python
feedback = client.v1.audio_recaps.list_feedback(
    component_id="audio_recap_abc123",
    feedback_type="thumbsDown",
)

print(feedback.total, feedback.stats)

# The rated section plus its sibling sections, for one feedback item
context = client.v1.audio_recaps.retrieve_feedback_context(feedback.feedback[0].api_id)
```

Java

```java
import com.studyfetch.javasdk.models.v1.audiorecaps.AudioRecapListFeedbackParams;
import com.studyfetch.javasdk.models.v1.audiorecaps.AudioRecapListFeedbackResponse;

AudioRecapListFeedbackResponse feedback = client.v1().audioRecaps().listFeedback(
    AudioRecapListFeedbackParams.builder()
        .componentId("audio_recap_abc123")
        .feedbackType(AudioRecapListFeedbackParams.FeedbackType.THUMBS_DOWN)
        .build()
);

System.out.println(feedback.total());
```

C#

```csharp
using StudyfetchSDK.Models.V1.AudioRecaps.AudioRecapListFeedbackParamsProperties;

var feedback = await client.V1.AudioRecaps.ListFeedback(new()
{
    ComponentID = "audio_recap_abc123",
    FeedbackType = FeedbackType.ThumbsDown,
});

Console.WriteLine(feedback.Total);
```

cURL

```curl
curl "https://studyfetchapi.com/api/v1/audio-recaps/feedback?componentId=audio_recap_abc123&feedbackType=thumbsDown" \
  -H "x-api-key: your-api-key"
```

The response contains `feedback` (the feedback items), `total`, `groups` (group IDs seen in the results), and `stats` with `thumbsUp`, `thumbsDown`, and `withReason` counts.

## React Native

The React Native SDK (`@studyfetch/react-native-sdk`) renders Audio Recap natively with embed tokens instead of an iframe. Import it from `@studyfetch/react-native-sdk/audio-recap`, which requires the `expo-audio` peer dependency, and wrap your app in `EmbedTokenProvider` with a `getToken` function that asks your backend to mint an embed token for a component.

```tsx
import { EmbedTokenProvider } from '@studyfetch/react-native-sdk';
import {
  StudyFetchAudioRecap,
  useAudioRecap,
  useAudioRecapsProgress,
} from '@studyfetch/react-native-sdk/audio-recap';

// Full player: playback, per-section progress, follow-up questions, feedback
<EmbedTokenProvider getToken={fetchToken}>
  <StudyFetchAudioRecap componentId="audio_recap_abc123" userId="student-001" />
</EmbedTokenProvider>

// Generation status: the hook polls while anything is still generating
const { recap, sections, generating, retrySection } = useAudioRecap({
  componentId: 'audio_recap_abc123',
  userId: 'student-001',
});
if (recap?.generationStatus === 'failed') console.warn(recap.generationError);

// Recap-level progress for a "My recaps" list
const { progress, errors, loading, refresh } = useAudioRecapsProgress({
  componentIds: ['audio_recap_abc123', 'audio_recap_def456'],
  userId: 'student-001',
});
```

- Pass a `userId`. Progress is stored per user; without one, playback position is not saved.
- `useAudioRecapsProgress` returns the same fields as List Progress Across Recaps for One User, keyed by `componentId`. Each recap is read with its own embed token, so `getToken` is called once per component. `useAudioRecapProgress` does the same for a single recap.
- On iOS, add `"UIBackgroundModes": ["audio"]` to your app config so playback continues when the screen locks.

## Authentication

Source: https://studyfetch-api.honen.com/docs/6acd32089de34cae55b272c0

All requests to the StudyFetch API require authentication using an API key. This guide covers how to obtain and use your API key across different platforms.

## Obtaining Your API Key

### Via StudyFetch Dashboard

1. Log in to your [StudyFetch Console](https://console.studyfetch.com)
2. Click **API Keys**
3. Click **Create New API Key**
4. Give your key a descriptive name (e.g., "Production App")
5. Copy the generated key

## Using Your API Key

The StudyFetch API uses API key authentication via the `x-api-key` header.

### Header Format

```
x-api-key: YOUR_API_KEY
```

### Example Requests

JavaScript

```javascript
import StudyfetchSDK from '@studyfetch/sdk';

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

// The SDK automatically includes the x-api-key header
const components = await client.v1.components.list();
```

Python

```python
from studyfetch_sdk import StudyfetchSDK

client = StudyfetchSDK(
    api_key="YOUR_API_KEY",
    base_url="https://studyfetchapi.com",
)

# The SDK automatically includes the x-api-key header
components = client.v1.components.list()
```

Java

```java
import com.studyfetch.javasdk.client.StudyfetchSdkClient;
import com.studyfetch.javasdk.client.okhttp.StudyfetchSdkOkHttpClient;

StudyfetchSdkClient client = StudyfetchSdkOkHttpClient.builder()
    .apiKey("YOUR_API_KEY")
    .baseUrl("https://studyfetchapi.com")
    .build();

// The SDK automatically includes the x-api-key header
var components = client.v1().components().list();
```

Bash

```bash
curl https://studyfetchapi.com/api/v1/components \
  -H "x-api-key: YOUR_API_KEY"
```

## Changelog

Source: https://studyfetch-api.honen.com/docs/6acd321c9de34cae55b272cb

## Latest Updates

### Chat and Other Components, October 2025 to October 2026

- **Export learners' questions.** `GET /api/v1/chat/prompts/export` and `GET /api/v1/chat/session/{sessionId}/export` download the questions asked of a chat component as CSV. The SDKs don't have methods for these yet.
- **Voice transcription.** `POST /api/v1/chat/transcribe` converts recorded audio to text.
- **Feedback by group.** Chat feedback can be filtered by `groupId` (`retrieveFeedback({ groupId })`, `retrieve_feedback(group_id=...)`).
- **Embed context.** Pushing context now replaces the previous context instead of adding to it, and context is kept separate for each user and session.
- **Embed tokens.** Tokens are valid for 1 hour regardless of `expiryHours`. `POST /api/v1/embed/refresh-token` re-issues a token for up to 12 hours. Embed URLs can use a host specific to your organization, so always use the returned `embedUrl`.
- **Scenarios.** A top-level `description` is required. The TypeScript, Python, and C# SDKs' config types don't cover scenarios yet; see Scenarios for working examples.
- **React Native.** `@studyfetch/react-native-sdk` documents its Chat, Scenarios, Flashcards, and Practice Test components on each page.
- **Corrected docs.** Every component page now uses the Python SDK's real `generate_embed` and snake\_case keys, lowercase enum values, `type: "data_analyst"`, and the real defaults. The pages also flag settings that are accepted but not used.

See Chat for the streaming format, context, feedback, and export details.

### Audio Recap, August to October 2026

- **Generation status endpoint.** `GET /api/v1/audio-recaps/components/{componentId}` returns script generation status (`pending`, `completed`, `failed`) and per-section audio status. SDK method: `retrieveGenerationStatus` (`retrieve_generation_status` in Python).
- **Progress across recaps for one user.** `GET /api/v1/audio-recaps/users/{userId}/progress` returns completion, approximate duration, and listened/remaining seconds for up to 100 recaps in one call. SDK method: `listUserProgress` (`list_user_progress` in Python).
- **Section feedback in the SDKs.** `listFeedback` and `retrieveFeedbackContext` read learners' thumbs up/down ratings on recap sections.
- **Voice names.** `voice1` and `voice2` now take `Alex`, `Morgan`, `Riley`, `Jordan`, `Sparky`, or `Taylor`. Older voice values are still accepted.
- **Topic-only recaps.** A recap can be created from a `topic` with no materials or folders.
- **Sparky.** The mascot narrator voice, previously introduced as Spark.E, now introduces itself as Sparky.
- **Progress fix.** A completed section now always reports `completed: true`, and stays completed.
- **React Native.** `@studyfetch/react-native-sdk` adds `useAudioRecapsProgress` and `useAudioRecapProgress`, and stops polling recaps whose generation failed.

| SDK | Version with these changes |
| --- | --- |
| JavaScript (`@studyfetch/sdk`) | 1.52.0 |
| Python (`studyfetch_sdk`) | 0.1.0-alpha.40 |
| Java | 0.1.0-alpha.37 |
| C# | 0.8.19 |
| React Native (`@studyfetch/react-native-sdk`) | 0.5.0 |

See Audio Recap for details and examples.

### JavaScript

#### v1.44.1

_Released: October 21, 2025_

**Features**

- **Chat Feedback Context API**
  - Added `client.v1.chat.retrieveFeedbackContext()` method to retrieve specific message and full conversation for a feedback item
  - Retrieves complete conversation context for analyzing user feedback
  - Accepts `feedbackId` parameter to identify the specific feedback item
  - See documentation
  - Related: v1.44.0 includes Chat Feedback Analytics and Embed Context features

---

#### v1.44.0

_Released: October 21, 2025_

**Features**

- **Chat Embed Context Management**
  - Added context API for pushing dynamic context to embedded chat components
  - New `client.v1.embed.context.push()` method to add context strings to specific embed instances
  - New `client.v1.embed.context.retrieve()` method to get current context for an embed
  - New `client.v1.embed.context.clear()` method to clear context from an embed
  - Enables dynamic content updates (e.g., question changes in practice tests) without losing chat history
  - See documentation
- **Chat Feedback Analytics**
  - Added `client.v1.chat.retrieveFeedback()` method to retrieve user feedback data
  - Support for filtering feedback by component ID, user ID, date range, and feedback type
  - Feedback type options: `thumbsUp` and `thumbsDown`
  - See documentation
- **Chat Component Configuration**
  - Added `enableFeedback` option (default: true) to enable/disable thumbs up/down feedback with reason
- **API Updates**
  - Enhanced embed service with context management capabilities
  - Updated type definitions and method signatures

---

#### v1.43.0

_Released: October 8, 2025_

**Features**

- **API Updates**
  - Added PDF Generator service for creating presentations
  - Added `studentName` parameter to component embedding
  - Updated API contracts and type definitions
- **Chat Component Enhancements**
  - Added `hideTitle` configuration option to hide chat component headings (icon and title)
  - Added `hideEmptyState` option to hide default empty state UI
  - Added support for custom HTML to replace the default icon and message when chat is empty via `emptyStateHTML`
  - Removed user avatar from chat interface for cleaner UI
  - Removed Spark.E references from prompt guidance system

---

#### v1.42.0

_Released: September 24, 2025_

**Features**

- **API Updates**
  - Updated endpoint definitions and API contracts
  - Improved request/response handling
  - Enhanced type definitions and method signatures

---

#### v1.41.0

_Released: September 23, 2025_

**New Features**

- **References Support**
  - Added `References` property to material creation and response objects
  - Support for adding source citations with title and URL
  - Enhanced material metadata with reference tracking

---

#### v1.40.0

_Released: September 23, 2025_

**Features**

- Added `enableReferenceMode` option for chat components and embedding
- Added message grading analytics (prompting and responsibility scores)
- Added `update` method for materials to update reference information (title and URL)
- API updates with latest endpoint definitions

**Bug Fixes**

- Coerce nullable values to undefined for better TypeScript compatibility
- Updated singularization rules

---

#### v1.38.0

_Released: August 21, 2025_

**Bug Fixes**

- Fixed response structure inconsistencies in API methods
- Corrected property naming conventions across response objects
- Updated method signatures to match actual implementation
- Improved response type definitions for better type safety

---

#### v1.37.0

_Released: August 21, 2025_

**Documentation Fixes**

- Fixed JavaScript code examples across all documentation
- Updated `parentFolderId` handling in folder operations (use `undefined` instead of `null`)
- Added proper null checks for optional fields before accessing properties
- Fixed pagination checks to handle potentially undefined `totalPages`

---

#### v1.36.0

_Released: August 19, 2025_

**New Features**

- **Pagination Support**
  - Added pagination parameters (`limit` and `page`) to `materials.list()`
  - Added pagination parameters to `folders.listMaterials()`
  - Materials list response now returns paginated object with `data`, `limit`, `page`, `total`, and `totalPages`

**Breaking Changes**

- **Materials List Response**
  - Changed from array to paginated object structure
  - Access materials via `response.data` instead of direct array
  - Added pagination metadata: `total`, `totalPages`, `page`, `limit`
- **Folder Retrieve Response**
  - Removed `materials` field from folder retrieve response
  - Removed `totalSize` field from folder retrieve response
  - Use `folders.listMaterials()` or `materials.list()` with `folderId` to get folder contents

**Documentation**

- Updated all code examples to use new paginated response structure
- Added examples for iterating through multiple pages
- Added documentation for new `folders.listMaterials()` method

---

#### v1.35.0

_Released: August 18, 2025_

**Improvements**

- Refactored folders API

---

#### v1.34.0

_Released: August 7, 2025_

**Breaking Changes**

- **Assignment Grader API**
  - Changed `rubricTemplates()` to be a property instead of callable
  - Now use `rubricTemplates.create()`, `rubricTemplates.list()`, etc.
  - Affects all rubric template operations

**Documentation**

- Updated all code examples to use new rubricTemplates property syntax
- Added migration guide for rubric template changes
- Improved TypeScript type definitions for template operations

---

#### v1.33.0

_Released: July 26, 2025_

**New Features**

- **AI-Powered Material Generation**
  - Added `generate()` method to create study materials from topics
  - Added `generateAndProcess()` method for synchronous generation
  - Support for multiple material types: outlines, overviews, notes, summaries
  - Customizable length, education level, and target folder
- **Chat Streaming API**
  - Support for both AI SDK format (messages array) and custom format
  - Image attachment support in chat messages
  - Stream processing with proper error handling

**Improvements**

- Enhanced material management with generation capabilities
- Better TypeScript type definitions for generated materials
- Improved streaming performance with chunked responses

**Documentation**

- Added comprehensive material generation guide
- New streaming chat implementation examples
- Updated API reference with all generation parameters

---

#### v1.27.0

_Released: July 24, 2025_

**New Features**

- **Guardrails for Chat Component**
  - Added `enableGuardrails` and `guardrailRules` configuration options
  - Support for BLOCK, WARN, and MODIFY actions on AI responses
  - Server-side content policy enforcement for safety and compliance
- **Enhanced Chat Component**
  - Added thinking/reasoning capabilities to chat responses
- **Keyboard Navigation for Flashcards**
  - Arrow keys for card navigation
  - Enhanced accessibility support

**Improvements**

- **Component Embed Generation Flexibility**
  - Both `componentId` and `_id` are now accepted in generateEmbed methods
  - Better backwards compatibility with existing integrations

**Bug Fixes**

- Fixed citation placement in chat responses to correctly scope content within provided materials
- Fixed minor bugs in flashcard component animations and state management
- Fixed test component scoring edge cases

---

#### v1.26.0

_Released: July 22, 2025_

**New Features**

- Added `createAndProcess` method for materials API
  - Automatically waits for material processing to complete
  - Returns fully processed material ready for immediate use
  - Eliminates need for manual polling of processing status
- Added Data Analyst component API
  - New component type for data analysis and visualization
  - Supports streaming analysis, sessions, and image uploads
  - Includes methods for statistical analysis and chart generation

**Bug Fixes**

- Fixed assignment grader response format
  - Removed inaccessible `rubric` field from response examples
  - Updated documentation to reflect actual API response structure

**Documentation**

- Added comprehensive rubric creation examples
- Improved material processing documentation
- Added best practices for assignment grading

---

#### v1.25.0

_Released: July 20, 2025_

**Features**

- Initial assignment grader API implementation
- Support for custom rubrics with multiple criteria
- Material-based grading support

### Python

#### v0.1.0-alpha.33

_Released: October 21, 2025_

**Features**

- **Chat Feedback Context API**
  - Added `client.v1.chat.retrieve_feedback_context()` method to retrieve specific message and full conversation for a feedback item
  - Retrieves complete conversation context for analyzing user feedback
  - Accepts `feedback_id` parameter to identify the specific feedback item
  - See documentation
  - Related: v0.1.0-alpha.32 includes Chat Feedback Analytics and Embed Context features

---

#### v0.1.0-alpha.32

_Released: October 21, 2025_

**Features**

- **Chat Embed Context Management**
  - Added context API for pushing dynamic context to embedded chat components
  - New `client.v1.embed.context.push()` method to add context strings to specific embed instances
  - New `client.v1.embed.context.retrieve()` method to get current context for an embed
  - New `client.v1.embed.context.clear()` method to clear context from an embed
  - Enables dynamic content updates (e.g., question changes in practice tests) without losing chat history
  - See documentation
- **Chat Feedback Analytics**
  - Added `client.v1.chat.retrieve_feedback()` method to retrieve user feedback data
  - Support for filtering feedback by component ID, user ID, date range, and feedback type
  - Feedback type options: `"thumbsUp"` and `"thumbsDown"`
  - See documentation
- **Chat Component Configuration**
  - Added `enable_feedback` option (default: true) to enable/disable thumbs up/down feedback with reason
- **API Updates**
  - Enhanced embed service with context management capabilities
  - Updated type definitions and method signatures

---

#### v0.1.0-alpha.31

_Released: October 8, 2025_

**Features**

- **API Updates**
  - Added PDF Generator service for creating presentations
  - Added `student_name` parameter to component embedding
  - Updated API contracts and type definitions
- **Chat Component Enhancements**
  - Added `hide_title` configuration option to hide chat component headings (icon and title)
  - Added `hide_empty_state` option to hide default empty state UI
  - Added support for custom HTML to replace the default icon and message when chat is empty via `empty_state_html`
  - Removed user avatar from chat interface for cleaner UI
  - Removed Spark.E references from prompt guidance system

---

#### v0.1.0-alpha.30

_Released: September 24, 2025_

**Features**

- **Chat Feedback Context API**
  - Added `client.v1.chat.retrieve_feedback_context()` method to retrieve specific message and full conversation for a feedback item
  - Retrieves complete conversation context for analyzing user feedback
  - Accepts `feedback_id` parameter to identify the specific feedback item
  - See documentation
  - Related: v0.1.0-alpha.32 includes Chat Feedback Analytics and Embed Context features
- **API Updates**
  - Updated endpoint definitions and API contracts
  - Improved request/response handling
  - Enhanced type definitions and method signatures

---

#### v0.1.0-alpha.29

_Released: September 23, 2025_

**New Features**

- **References Support**
  - Added `references` parameter to material creation methods
  - Support for adding source citations with title and URL
  - Enhanced material metadata with reference tracking

---

#### v0.1.0-alpha.28

_Released: September 23, 2025_

**New Features**

- Added `enable_reference_mode` option for chat components and embedding
- Added message grading analytics (prompting and responsibility scores)
- Added `update` method for materials to update reference information (title and URL)
- API updates with latest endpoint definitions

---

#### v0.1.0-alpha.26

_Released: August 21, 2025_

**Bug Fixes**

- Fixed response structure inconsistencies in API methods
- Corrected property naming conventions across response objects
- Updated method signatures to match actual implementation
- Improved response type definitions for better type safety

---

#### v0.1.0-alpha.24

_Released: August 19, 2025_

**New Features**

- **Pagination Support**
  - Added pagination parameters (`limit` and `page`) to `materials.list()`
  - Added pagination parameters to `folders.list_materials()`
  - Materials list response now returns paginated object with `data`, `limit`, `page`, `total`, and `total_pages`

**Breaking Changes**

- **Materials List Response**
  - Changed from `List[MaterialListResponseItem]` to single `MaterialListResponse` object
  - Access materials via `response.data` instead of direct list
  - Added pagination metadata: `total`, `total_pages`, `page`, `limit`
- **Folder Retrieve Response**
  - Removed `materials` field from folder retrieve response
  - Removed `total_size` field from folder retrieve response
  - Use `folders.list_materials()` or `materials.list()` with `folder_id` to get folder contents

**Documentation**

- Updated all code examples to use new paginated response structure
- Added examples for iterating through multiple pages
- Added documentation for new `folders.list_materials()` method

---

#### v0.1.0-alpha.23

_Released: August 18, 2025_

**Improvements**

- Refactored folders API

---

#### v0.1.0-alpha.22

_Released: August 7, 2025_

**New Features**

- **Assignment Grader API**
  - Added support for rubric templates
  - Added assignment grouping functionality
  - Improved grading response structure

**Documentation**

- Added comprehensive rubric template examples
- Updated assignment grader documentation
- Added new code samples for template management

---

#### v0.1.0-alpha.21

_Released: July 26, 2025_

**New Features**

- **AI-Powered Material Generation**
  - Added `generate()` method to create study materials from topics
  - Added `generate_and_process()` method for synchronous generation
  - Support for multiple material types: outlines, overviews, notes, summaries
  - Customizable length, education level, and target folder
- **Chat Streaming API**
  - Support for both AI SDK format (messages array) and custom format
  - Async streaming with proper chunk handling
  - Image attachment support in chat messages

**Improvements**

- Enhanced material management with generation capabilities
- Better async/await support for streaming operations
- Improved error handling for generation timeouts

**Documentation**

- Added comprehensive material generation guide
- New async streaming implementation examples
- Updated API reference with all generation parameters

---

#### v0.1.0-alpha.20

_Released: July 24, 2025_

**New Features**

- **Guardrails for Chat Component**
  - Added `enableGuardrails` and `guardrailRules` configuration options
  - Support for BLOCK, WARN, and MODIFY actions on AI responses
  - Server-side content policy enforcement for safety and compliance
- **Enhanced Chat Component**
  - Added thinking/reasoning capabilities to chat responses
  - Improved multi-step problem solving with visible thought process
- **Keyboard Navigation for Flashcards**
  - Arrow keys for card navigation
  - Spacebar to flip cards
  - Enhanced accessibility support

**Improvements**

- **Component Embed Generation Flexibility**
  - Both `componentId` and `_id` are now accepted in generateEmbed methods
  - Better backwards compatibility with existing integrations

**Bug Fixes**

- Fixed citation placement in chat responses to correctly scope content within provided materials
- Fixed minor bugs in flashcard component animations and state management
- Fixed test component scoring edge cases

---

#### v0.1.0-alpha.19

_Released: July 22, 2025_

**New Features**

- Added `create_and_process` method for materials API
  - Synchronous material creation and processing
  - Ideal for immediate material usage scenarios
  - Consistent with JavaScript SDK functionality
- Added Data Analyst component API
  - Full support for data analysis operations
  - Methods for send\_message, stream, retrieve\_session, get\_history
  - Image upload capabilities for data visualization

**Bug Fixes**

- Fixed assignment grader response format documentation
  - Aligned response examples with actual API behavior
  - Removed references to private fields

**Documentation**

- Enhanced material management examples
- Added rubric definition guide
- Improved code consistency across SDKs

---

#### v0.1.0-alpha.18

_Released: July 20, 2025_

**Features**

- Assignment grader API support
- Material upload and processing
- Folder management capabilities

### Java

#### v0.1.0-alpha.33

_Released: October 21, 2025_

**Features**

- **Chat Feedback Context API**
  - Added `client.v1().chat().retrieveFeedbackContext()` method to retrieve specific message and full conversation for a feedback item
  - Retrieves complete conversation context for analyzing user feedback
  - Accepts `feedbackId` parameter to identify the specific feedback item
  - See documentation
  - Related: v0.1.0-alpha.29 includes Chat Feedback Analytics and Embed Context features

---

#### v0.1.0-alpha.29

_Released: October 21, 2025_

**Features**

- **Chat Embed Context Management**
  - Added context API for pushing dynamic context to embedded chat components
  - New `client.v1().embed().context().push()` method to add context strings to specific embed instances
  - New `client.v1().embed().context().retrieve()` method to get current context for an embed
  - New `client.v1().embed().context().clear()` method to clear context from an embed
  - Enables dynamic content updates (e.g., question changes in practice tests) without losing chat history
  - See documentation
- **Chat Feedback Analytics**
  - Added `client.v1().chat().retrieveFeedback()` method to retrieve user feedback data
  - Support for filtering feedback by component ID, user ID, date range, and feedback type
  - Feedback type enum: `ChatRetrieveFeedbackParams.FeedbackType.THUMBS_UP` and `THUMBS_DOWN`
  - See documentation
- **Chat Component Configuration**
  - Added `enableFeedback` option (default: true) to enable/disable thumbs up/down feedback with reason
- **API Updates**
  - Enhanced embed service with context management capabilities
  - Updated type definitions and method signatures

---

#### v0.1.0-alpha.28

_Released: October 8, 2025_

**Features**

- **API Updates**
  - Added PDF Generator service for creating presentations
  - Added `studentName` parameter to component embedding
  - Updated API contracts and type definitions
- **Chat Component Enhancements**
  - Added `hideTitle` configuration option to hide chat component headings (icon and title)
  - Added `hideEmptyState` option to hide default empty state UI
  - Added support for custom HTML to replace the default icon and message when chat is empty via `emptyStateHTML`
  - Removed user avatar from chat interface for cleaner UI
  - Removed Spark.E references from prompt guidance system

---

#### v0.1.0-alpha.27

_Released: September 24, 2025_

**Features**

- **API Updates**
  - Updated endpoint definitions and API contracts
  - Improved request/response handling
  - Enhanced type definitions and method signatures

---

#### v0.1.0-alpha.26

_Released: September 23, 2025_

**New Features**

- **References Support**
  - Added `references` parameter to material creation methods
  - Support for adding source citations with title and URL
  - Enhanced material metadata with reference tracking

---

#### v0.1.0-alpha.25

_Released: September 23, 2025_

**New Features**

- Added `enableReferenceMode` option for chat components and embedding
- Added message grading analytics (prompting and responsibility scores)
- Added `update` method for materials to update reference information (title and URL)
- API updates with latest endpoint definitions

---

#### v0.1.0-alpha.23

_Released: August 21, 2025_

**Bug Fixes**

- Fixed response structure inconsistencies in API methods
- Corrected property accessor methods across response objects
- Updated method signatures to match actual implementation
- Improved response type definitions for better type safety

---

#### v0.1.0-alpha.22

_Released: August 21, 2025_

**Documentation Fixes**

- **Comprehensive Documentation Update**
  - Fixed all Java code examples to use correct SDK patterns
  - Updated all API calls to use parameter objects with builder pattern
  - Fixed response types: `MaterialResponse`, `ComponentResponse`, `FolderCreateResponse`, etc.
  - Corrected `FolderListResponse` usage - it returns `List<FolderListResponse>` not a single object
  - Fixed Optional field handling - omit fields instead of passing `null`
  - Updated `Content` imports to use standalone class instead of inner class
  - Fixed enum usage for fields like `difficulty`, `recapType` in components
  - Corrected assignment grader to use `RubricCriterion` from `rubrictemplates` package
  - Fixed `AssignmentGraderCreateResponse` with `gradedAssignment().get().grade()` pattern
  - Updated chat analytics to use `ChatAnalyticsResponse` and `OffsetDateTime` for dates
  - Fixed material search to use `MaterialSearchResponse` and handle Optional fields
  - Added proper imports for all examples including `List`, `Collections`, `OffsetDateTime`

---

#### v0.1.0-alpha.21

_Released: August 19, 2025_

**New Features**

- **Pagination Support**
  - Added pagination parameters (`limit` and `page`) to `materials.list()`
  - Added pagination parameters to `folders.listMaterials()`
  - Materials list response now returns paginated object with `data`, `limit`, `page`, `total`, and `totalPages`

**Breaking Changes**

- **Materials List Response**
  - Changed from `List<MaterialListResponse>` to single `MaterialListResponse` object
  - Access materials via `response.data()` instead of direct list
  - Added pagination metadata: `total()`, `totalPages()`, `page()`, `limit()`
- **Folder Retrieve Response**
  - Removed `materials()` method from folder retrieve response
  - Removed `totalSize()` method from folder retrieve response
  - Use `folders.listMaterials()` or `materials.list()` with `folderId()` to get folder contents

**Documentation**

- Updated all code examples to use new paginated response structure
- Added examples for iterating through multiple pages
- Added documentation for new `folders.listMaterials()` method

---

#### v0.1.0-alpha.20

_Released: August 18, 2025_

**Improvements**

- Refactored folders API

---

#### v0.1.0-alpha.19

_Released: August 7, 2025_

**New Features**

- **Assignment Grader API**
  - Added RubricTemplateService for template management
  - Added support for assignment grouping via AssignmentId
  - Enhanced grading response structure with proper typing

**Improvements**

- Better builder patterns for template operations
- Improved type safety for assignment grouping
- Enhanced error handling for template operations

**Documentation**

- Added detailed rubric template examples
- Updated assignment grader documentation
- Added missing imports for template operations

---

#### v0.1.0-alpha.18

_Released: July 26, 2025_

**New Features**

- **AI-Powered Material Generation**
  - Added `generate()` method to create study materials from topics
  - Added `generateAndProcess()` method for synchronous generation
  - Support for MaterialGenerateParams.Type enum: OUTLINE, OVERVIEW, NOTES, SUMMARY
  - Support for MaterialGenerateParams.Level enum: HIGH\_SCHOOL, COLLEGE, PROFESSIONAL
  - Support for MaterialGenerateParams.Length enum: SHORT, MEDIUM, LONG
- **Chat Streaming API**
  - ChatStreamParams builder with full type safety
  - Support for both AI SDK format and custom message format
  - Image attachment support with ChatStreamParams.Message.Image

**Improvements**

- Enhanced builder patterns for all material generation operations
- Better enum support for type-safe configurations
- Improved Optional handling for nullable parameters

**Documentation**

- Added comprehensive material generation guide with Java examples
- New streaming implementation patterns
- Updated API reference with proper enum usage

---

#### v0.1.0-alpha.17

_Released: July 24, 2025_

**New Features**

- **Guardrails for Chat Component**
  - Added `enableGuardrails` and `guardrailRules` configuration options
  - Support for GuardrailRule.Action enum: BLOCK, WARN, and MODIFY
  - Server-side content policy enforcement for safety and compliance
  - Full builder pattern support for GuardrailRule configuration
- **Enhanced Chat Component**
  - Added thinking/reasoning capabilities to chat responses
  - Improved multi-step problem solving with visible thought process
- **Keyboard Navigation for Flashcards**
  - Arrow keys for card navigation
  - Spacebar to flip cards
  - Enhanced accessibility support

**Improvements**

- **Component Embed Generation Flexibility**
  - Both `componentId` and `_id` are now accepted in generateEmbed methods
  - Better backwards compatibility with existing integrations

**Bug Fixes**

- Fixed citation placement in chat responses to correctly scope content within provided materials
- Fixed minor bugs in flashcard component animations and state management
- Fixed test component scoring edge cases

---

#### v0.1.0-alpha.16

_Released: July 22, 2025_

**New Features**

- Added `createAndProcess` method for materials API
  - Uses `MaterialCreateAndProcessParams` for configuration
  - Blocks until material processing completes
  - Returns `Material` object with active status
- Added Data Analyst component API
  - New `DataAnalystService` with full API support

**Bug Fixes**

- Fixed assignment grader response format
  - Corrected Java examples to not access private `rubric` field
  - Updated material creation examples to use proper imports
  - Fixed `Content` class references to use correct package structure

**Breaking Changes**

- Material creation now uses `com.studyfetch.javasdk.models.v1.materials.Content` instead of nested classes
- Upload functionality requires `UploadUploadFileParams` from the upload subpackage

**Documentation**

- Added detailed rubric builder examples
- Improved type safety in code examples
- Added missing imports for File operations

---

#### v0.1.0-alpha.15

_Released: July 20, 2025_

**Features**

- Initial Java SDK release
- Core API functionality for materials and grading
- Builder pattern implementation for all request types

### C#

#### v0.8.4

_Released: March 5, 2026_

**Features**

- **Assignment Grader**
  - Added optional `AssignmentContent` property to `AssignmentGraderCreateParams` for providing reading passages or source material as grading context
  - Added `AssignmentContent` to `Rubric` nested model for rubric-level context
  - Added `AssignmentContent` to `AssignmentGraderResponse` for response data
  - Added `AssignmentContent` to `RubricTemplateCreateParams` and `RubricTemplateResponse`
- **Usage Stats**
  - Added `ComponentType` query parameter to `UsageGetStatsParams` for filtering by component type
  - Added `ResourceID` query parameter to `UsageGetStatsParams` for filtering by component ID

**Changes**

- **Components**
  - Disabled `ScenariosConfigDto` as a config variant in the `Config` union type
  - Scenario files and `Type.Scenarios` enum values preserved for backward compatibility

---

#### v0.8.1

_Released: October 21, 2025_

**Features**

- **Chat Feedback Context API**
  - Added `RetrieveFeedbackContext()` method to retrieve specific message and full conversation for a feedback item
  - Retrieves complete conversation context for analyzing user feedback
  - See documentation
  - Related: v0.8.0 includes Chat Feedback Analytics and Embed Context features

---

#### v0.8.0

_Released: October 21, 2025_

**Features**

- **Chat Embed Context Management**
  - Added context API for pushing dynamic context to embedded chat components
  - New `Embed.Context.Push()` method to add context strings to specific embed instances
  - New `Embed.Context.Retrieve()` method to get current context for an embed
  - New `Embed.Context.Clear()` method to clear context from an embed
  - Enables dynamic content updates (e.g., question changes in practice tests) without losing chat history
  - See documentation
- **Chat Feedback Analytics**
  - Added `Chat.RetrieveFeedback()` method to retrieve user feedback data
  - Support for filtering feedback by component ID, user ID, date range, and feedback type
  - Feedback type enum with `ThumbsUp` and `ThumbsDown` values
  - See documentation
- **Chat Component Configuration**
  - Added `EnableFeedback` property (default: true) to enable/disable thumbs up/down feedback with reason
- **API Updates**
  - Enhanced embed service with context management capabilities
  - Updated type definitions and method signatures

---

#### v0.7.0

_Released: October 8, 2025_

**Features**

- **API Updates**
  - Added PDF Generator service for creating presentations
  - Added `StudentName` parameter to component embedding
  - Refactored exception handling
  - Refactored union types
- **Chat Component Enhancements**
  - Added `HideTitle` configuration option to hide chat component headings (icon and title)
  - Added `HideEmptyState` option to hide default empty state UI
  - Added support for custom HTML to replace the default icon and message when chat is empty via `EmptyStateHTML`
  - Removed user avatar from chat interface for cleaner UI
  - Removed Spark.E references from prompt guidance system

---

#### v0.6.1

_Released: September 24, 2025_

**Features**

- **API Updates**
  - Updated endpoint definitions and API contracts
  - Improved request/response handling
  - Enhanced type definitions and method signatures

---

#### v0.6.0

_Released: September 23, 2025_

**New Features**

- **References Support**
  - Added `References` property to material creation and response objects
  - Support for adding source citations with title and URL
  - Enhanced material metadata with reference tracking

---

#### v0.5.0

_Released: September 23, 2025_

**New Features**

- Added `EnableReferenceMode` option for chat components and embedding
- Added message grading analytics (prompting and responsibility scores)
- Added `Update` method for materials to update reference information (title and URL)
- API updates with latest endpoint definitions

**Improvements**

- Better type naming conventions
- Improved authentication handling
- Updated singularization rules

**Bug Fixes**

- Removed unused null class
- Cleaned up example project references

---

#### v0.3.1

_Released: August 21, 2025_

**Bug Fixes**

- Fixed response structure inconsistencies in API methods
- Corrected property naming conventions across response objects
- Updated method signatures to match actual implementation
- Improved response type definitions for better type safety

---

#### v0.3.0

_Released: August 21, 2025_

**Documentation Fixes**

- **Major Documentation Overhaul**
  - Fixed all C# code examples across the entire documentation
  - Standardized client initialization patterns using `var client = new StudyfetchSDKClient()`
  - Fixed parameter instantiation to use `new()` syntax consistently
  - Corrected response property access (e.g., `Materials` instead of `Data`, `TotalCount` instead of `Total`)
  - Fixed enum usage for status values using `.Raw()` method
  - Corrected guardrails implementation in chat components
  - Fixed file upload examples to use `File.ReadAllBytesAsync()` with `FileName` parameter
  - Updated assignment grader to use `RubricCriterion` from correct namespace
  - Fixed direct ID passing to use parameter objects with builders

---

#### v0.2.0

_Released: August 19, 2025_

**New Features**

- **Pagination Support**
  - Added pagination parameters (`Limit` and `Page`) to `Materials.List()`
  - Added pagination parameters to `Folders.ListMaterials()`
  - Materials list response now returns paginated object with `Data`, `Limit`, `Page`, `Total`, and `TotalPages`
- **Union Type Helpers**
  - Added `Switch` and `Match` helper methods for union types
  - Added `TryPick*` methods for type-safe union handling

**Breaking Changes**

- **Materials List Response**
  - Changed from `List<MaterialListResponse>` to single `MaterialListResponse` object
  - Access materials via `response.Data` instead of direct list
  - Added pagination metadata: `Total`, `TotalPages`, `Page`, `Limit`
- **Folder Retrieve Response**
  - Removed `Materials` property from folder retrieve response
  - Removed `TotalSize` property from folder retrieve response
  - Use `Folders.ListMaterials()` or `Materials.List()` with `FolderID` to get folder contents

**Documentation**

- Updated all code examples to use new paginated response structure
- Added examples for iterating through multiple pages
- Added documentation for new `Folders.ListMaterials()` method

---

#### v0.1.0

_Released: August 19, 2025_

**Features**

- Initial beta release of C# SDK

---

#### v0.0.3

_Released: August 18, 2025_

**Improvements**

- Refactored folders API

## Version Support

| SDK | Current Version | Minimum Supported |
| --- | --- | --- |
| JavaScript | 1.52.0 | 1.20.0 |
| Python | 0.1.0-alpha.40 | 0.1.0-alpha.15 |
| Java | 0.1.0-alpha.37 | 0.1.0-alpha.15 |
| C# | 0.8.19 | 0.0.1 |
| React Native | 0.5.0 | N/A |

Note

Alpha versions may contain breaking changes between releases. We recommend staying on the latest version for the best experience and bug fixes.

## Chat

Source: https://studyfetch-api.honen.com/docs/6acd32069de34cae55b272b5

## 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

```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

```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

```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#

```csharp
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}");
    }
}
```

Note

Creating a component requires your organization API key, so it belongs on your server. The React Native SDK renders existing components with embed tokens; see React Native.

## 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

```json
{
  "_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

```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

```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

```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#

```csharp
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

```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

```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

```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#

```csharp
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

```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

```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

```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#

```csharp
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`).

Warning

**Embed tokens are valid for 1 hour.** `expiryHours` is accepted, but it only changes the `expiresAt` value in the response; the token itself always expires 1 hour after it is issued, and the chat embed does not renew it. If learners keep the page open longer, load the iframe with a fresh embed URL before the hour is up, using the same `userId`. The API can also re-issue a token with the same settings: `POST /api/v1/embed/refresh-token` with body `{ "token": "..." }`, up to 10 minutes after the token expires and up to 12 hours after the first token was issued. The SDKs do not have a refresh method yet.

### 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.

```html
<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

```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

```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

```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#

```csharp
// 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

```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](https://ai-sdk.dev) 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.

```bash
curl "https://studyfetchapi.com/api/v1/chat/transcribe" \
  -H "x-api-key: your-api-key" \
  -F "audio=@question.webm"
```

## 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.

Note

**Pushing context replaces it.** Each push stores a single context string and discards the previous one, so push the full context each time. Clearing first is not needed.

### Push Context

JavaScript

```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

```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

```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#

```csharp
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
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

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

Python

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

Java

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

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

C#

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

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

cURL

```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

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

Python

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

Java

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

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

C#

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

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

cURL

```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

```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

```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

```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#

```csharp
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

```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

```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

```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
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

```json
{
  "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

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

Python

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

Java

```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
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`.

```bash
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
```

Note

This export reads questions recorded since prompt capture was added in late August 2026. Conversations from before then appear only if they were backfilled. To export an older conversation, use the per-session export below.

### 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.

```bash
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.

```tsx
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`.

## Chat Analytics

Source: https://studyfetch-api.honen.com/docs/6acd32089de34cae55b272bb

## Overview

The Chat Analytics component is an embeddable dashboard for one Chat component. It helps educators and administrators understand usage patterns, identify popular topics, measure engagement levels, and make data-driven decisions to improve the learning experience.

To read the same analytics from your server instead, use the Chat Analytics API.

## Creating a Chat Analytics Component

`chatComponentId` must be the chat component's `_id` (the 24-character database ID returned when you created it), not its `chat_…` `componentId`.

JavaScript

```javascript
import StudyfetchSDK from '@studyfetch/sdk';

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

const analyticsComponent = await client.v1.components.create({
  name: 'Platform Chat Analytics Dashboard',
  type: 'chat_analytics',
  config: {
    chatComponentId: '6650f1c2a9b3e4d5f6a7b8c9', // The chat component's _id
    dateRange: 30,
    autoRefresh: true,
    refreshInterval: 5,
    enableExport: true,
    showSummary: true,
    showTopics: true,
    showUserStats: true,
  },
});

console.log('Chat Analytics component created:', analyticsComponent._id);
```

Python

```python
from studyfetch_sdk import StudyfetchSDK

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

analytics_component = client.v1.components.create(
    name="Platform Chat Analytics Dashboard",
    type="chat_analytics",
    config={
        "chat_component_id": "6650f1c2a9b3e4d5f6a7b8c9",  # The chat component's _id
        "date_range": 30,
        "auto_refresh": True,
        "refresh_interval": 5,
        "enable_export": True,
        "show_summary": True,
        "show_topics": True,
        "show_user_stats": True,
    },
)

print(f"Chat Analytics component created: {analytics_component.api_id}")
```

Java

```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;

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

        ComponentCreateParams params = ComponentCreateParams.builder()
            .name("Platform Chat Analytics Dashboard")
            .type(ComponentCreateParams.Type.CHAT_ANALYTICS)
            .config(ComponentCreateParams.Config.ChatAnalyticsConfigDto.builder()
                .chatComponentId("6650f1c2a9b3e4d5f6a7b8c9") // The chat component's _id
                .dateRange(30.0)
                .autoRefresh(true)
                .refreshInterval(5.0)
                .enableExport(true)
                .showSummary(true)
                .showTopics(true)
                .showUserStats(true)
                .build())
            .build();

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

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Components;
using System;
using System.Threading.Tasks;

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

        var analyticsComponent = await client.V1.Components.Create(new()
        {
            Name = "Platform Chat Analytics Dashboard",
            Type = StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.Type.ChatAnalytics,
            Config = new StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.ConfigProperties.ChatAnalyticsConfigDto()
            {
                ChatComponentID = "6650f1c2a9b3e4d5f6a7b8c9", // The chat component's _id
                DateRange = 30,
                AutoRefresh = true,
                RefreshInterval = 5,
                EnableExport = true,
                ShowSummary = true,
                ShowTopics = true,
                ShowUserStats = true
            }
        });

        Console.WriteLine($"Chat Analytics component created: {analyticsComponent._ID}");
    }
}
```

## Configuration Parameters

#### `name`

string · required

Name of the chat analytics component

#### `type`

string · required

Must be `chat_analytics`

#### `config`

object · required

Configuration options for the chat analytics component. Unknown keys are rejected with `400`; unlike other components, this one does not accept `materials` or `folders`.

### config properties

These properties go inside `config`.

#### `chatComponentId`

string · required

`_id` of the chat component to analyze. It isn't checked when the component is created, so an invalid ID only shows up as an error when the dashboard loads.

#### `dateRange`

number · default: 30

Default date range in days for the dashboard

#### `autoRefresh`

boolean · default: false

Refresh the dashboard automatically. Only takes effect when `refreshInterval` is also set.

#### `refreshInterval`

number

Refresh interval in minutes, from 1 to 60. Required for `autoRefresh` to work; there is no default.

#### `enableExport`

boolean · default: true

Show the CSV export button

#### `showSummary`

boolean · default: true

Show the summary section

#### `showTopics`

boolean · default: true

Show the top topics section

#### `showUserStats`

boolean · default: true

Show user statistics and activity patterns

## Response

```json
{
  "_id": "6651a2b3c4d5e6f7a8b9c0d1",
  "componentId": "chat_analytics_V1StGXR8_Z",
  "name": "Platform Chat Analytics Dashboard",
  "type": "chat_analytics",
  "status": "active",
  "origin": "api",
  "organization": "64f0a1b2c3d4e5f6a7b8c9d0",
  "materials": [],
  "folders": [],
  "config": {
    "chatComponentId": "6650f1c2a9b3e4d5f6a7b8c9",
    "dateRange": 30,
    "autoRefresh": true,
    "refreshInterval": 5,
    "enableExport": true,
    "showSummary": true,
    "showTopics": true,
    "showUserStats": true
  },
  "usageCount": 0,
  "createdAt": "2026-10-06T10:00:00.000Z",
  "updatedAt": "2026-10-06T10:00:00.000Z"
}
```

## Embedding This Component

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

Note

**The embed token's `userId` filters the dashboard.** If you generate the embed with a `userId`, the dashboard shows only that user's conversations. For an educator or admin view of all learners, leave `userId` out. `groupIds` on the token do not filter the dashboard.

### Generate Embed URL

JavaScript

```javascript
// Organization-wide dashboard: no userId
const embedResponse = await client.v1.components.generateEmbed(analyticsComponent._id, {
  studentName: 'Dr. Patel',  // Name for display
  width: '100%',
  height: '800px',
});

console.log(embedResponse.embedUrl);
```

Python

```python
# Organization-wide dashboard: no user_id
embed_response = client.v1.components.generate_embed(
    analytics_component.api_id,
    student_name="Dr. Patel",  # Name for display
    width="100%",
    height="800px",
)

print(embed_response.embed_url)
```

Java

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

// Organization-wide dashboard: no userId
ComponentGenerateEmbedParams params = ComponentGenerateEmbedParams.builder()
    .id(analyticsComponent._id())
    .studentName("Dr. Patel")  // Name for display
    .width("100%")
    .height("800px")
    .build();

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

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

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Components;
using System;
using System.Threading.Tasks;

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

        // Organization-wide dashboard: no UserID
        var embedResponse = await client.V1.Components.GenerateEmbed(new()
        {
            ID = analyticsComponentId,
            StudentName = "Dr. Patel",  // Name for display
            Width = "100%",
            Height = "800px"
        });

        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`). The dashboard's settings come from the component's `config`; it doesn't use any `features` settings.

Warning

**Embed tokens are valid for 1 hour.** `expiryHours` only changes the `expiresAt` value in the response; the token itself always expires 1 hour after it is issued, and the embed does not renew it, so auto-refresh stops working after an hour. If the dashboard stays open longer, load the iframe with a fresh embed URL. See Chat for the token refresh endpoint.

### 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.

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

The React Native SDK does not include a Chat Analytics component; use the iframe embed.

## Client Uploads

Source: https://studyfetch-api.honen.com/docs/6acd32009de34cae55b2728b

## Overview

The Uploads component is a client-side component that allows your end users to upload materials and files directly into your application. It provides an embeddable file upload interface that integrates seamlessly into your website.

Every file uploaded through the embed becomes a material in the component's `folderId`, where other components can use it. Learners can choose several files at once and decide whether images are extracted from them (on by default).

**Example use case**: Professors can use this component to upload course materials into a designated folder, which then serves as the knowledge base for an AI Teaching Assistant for their class.

Note

To upload files from your own server instead, use the Materials upload API, which the SDKs support. See the Materials API Reference.

## Creating an Uploads Component

JavaScript

```javascript
import StudyfetchSDK from '@studyfetch/sdk';

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

const uploadsComponent = await client.v1.components.create({
  name: 'Study Material Uploader',
  description: 'Course materials for BIO 101',
  type: 'uploads',
  config: {
    folderId: 'folder_123', // Required: Target folder for uploads
  },
});

console.log('Uploads component created:', uploadsComponent._id, uploadsComponent.componentId);
```

Python

```python
from studyfetch_sdk import StudyfetchSDK

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

uploads_component = client.v1.components.create(
    name="Study Material Uploader",
    description="Course materials for BIO 101",
    type="uploads",
    config={
        "folder_id": "folder_123",  # Required: Target folder for uploads
    },
)

print(f"Uploads component created: {uploads_component.api_id} {uploads_component.component_id}")
```

Java

```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;

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

        ComponentCreateParams params = ComponentCreateParams.builder()
            .name("Study Material Uploader")
            .description("Course materials for BIO 101")
            .type(ComponentCreateParams.Type.UPLOADS)
            .config(ComponentCreateParams.Config.UploadsConfigDto.builder()
                .folderId("folder_123")  // Required: Target folder for uploads
                .build())
            .build();

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

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Components;
using System;
using System.Threading.Tasks;

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

        var uploadsComponent = await client.V1.Components.Create(new()
        {
            Name = "Study Material Uploader",
            Description = "Course materials for BIO 101",
            Type = StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.Type.Uploads,
            Config = new StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.ConfigProperties.UploadsConfigDto()
            {
                FolderID = "folder_123"  // Required: Target folder for uploads
            }
        });

        Console.WriteLine($"Uploads component created: {uploadsComponent._ID}");
    }
}
```

## Configuration Parameters

#### `name`

string · required

Name of the uploads component

#### `description`

string

Optional description of the uploads component. This is a top-level field, not part of `config`.

#### `type`

string · required

Must be `"uploads"`

#### `config`

object · required

Uploads configuration object. Unknown keys (including `description`) are rejected with `400`.

### Configuration Properties

These properties go inside `config`.

#### `folderId`

string · required

The folder ID where uploaded materials will be stored

#### `materials`

array

Accepted and stored with the component, but not used by uploads

#### `folders`

array

Accepted and stored with the component, but not used by uploads

## Response

```json
{
  "_id": "6650f1c2a9b3e4d5f6a7b8c9",
  "componentId": "uploads_V1StGXR8_Z",
  "name": "Study Material Uploader",
  "description": "Course materials for BIO 101",
  "type": "uploads",
  "status": "active",
  "origin": "api",
  "organization": "64f0a1b2c3d4e5f6a7b8c9d0",
  "materials": [],
  "folders": [],
  "config": {
    "folderId": "folder_123"
  },
  "usageCount": 0,
  "createdAt": "2026-10-06T10:00:00.000Z",
  "updatedAt": "2026-10-06T10:00:00.000Z"
}
```

## Embedding This Component

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

### Generate Embed URL

JavaScript

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

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

console.log(embedResponse.embedUrl);
```

Python

```python
embed_response = client.v1.components.generate_embed(
    uploads_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",
    width="100%",
    height="400px",
)

print(embed_response.embed_url)
```

Java

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

import java.util.List;

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

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

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

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

C#

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

public class GenerateUploadsEmbed
{
    public static async Task GenerateEmbed(string uploadsComponentId)
    {
        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 = uploadsComponentId,
            UserID = "user-456",
            StudentName = "Jane Smith",  // Student name for display
            GroupIDs = new List<string> { "class-101", "class-102" },
            SessionID = "session-789",
            Width = "100%",
            Height = "400px"
        });

        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`).

The uploads embed doesn't use any `features` settings.

Warning

**Embed tokens are valid for 1 hour.** `expiryHours` only changes the `expiresAt` value in the response; the token itself always expires 1 hour after it is issued, and the embed does not renew it. If users keep the page open longer, load the iframe with a fresh embed URL. See Chat for the token refresh endpoint.

### 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.

```html
<iframe
  src="EMBED_URL_FROM_RESPONSE"
  width="100%"
  height="400px"
  frameborder="0"
  style="border: 1px solid #e5e5e5; border-radius: 8px;">
</iframe>
```

The React Native SDK does not include an Uploads component; use the iframe embed, or upload from your server with the Materials API.

## Data Analyst

Source: https://studyfetch-api.honen.com/docs/6acd32059de34cae55b272b0

## Overview

The Data Analyst component is an AI-powered chatbot designed specifically for educators. It provides insights into student engagement, performance metrics, and learning patterns. Educators can ask natural-language questions about their students' study habits, identify areas where students may need additional support, and receive recommendations to improve learning outcomes.

The analyst answers from your organization's StudyFetch usage data: the usage events your learners generate in embedded components. It writes and runs analysis code against that data, so answers can include calculations and charts. Materials are optional and only serve as background reference.

Use `config.groupIds` to limit the analyst to specific classes or cohorts. The `groupIds` you pass when generating an embed don't restrict what the analyst can see.

## Creating a Data Analyst Component

JavaScript

```javascript
import StudyfetchSDK from '@studyfetch/sdk';

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

const dataAnalystComponent = await client.v1.components.create({
  name: 'Student Performance Analyst',
  type: 'data_analyst',
  config: {
    model: 'gpt-4.1-2025-04-14',
    groupIds: ['class-101', 'class-102'], // Only analyze these classes
    materials: ['mat-123'],               // Optional reference material
    enableWebSearch: false,
    maxSteps: 5,
  },
});

console.log('Data Analyst component created:', dataAnalystComponent._id, dataAnalystComponent.componentId);
```

Python

```python
from studyfetch_sdk import StudyfetchSDK

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

data_analyst_component = client.v1.components.create(
    name="Student Performance Analyst",
    type="data_analyst",
    config={
        "model": "gpt-4.1-2025-04-14",
        "group_ids": ["class-101", "class-102"],  # Only analyze these classes
        "materials": ["mat-123"],                 # Optional reference material
        "enable_web_search": False,
        "max_steps": 5,
    },
)

print(f"Data Analyst component created: {data_analyst_component.api_id} {data_analyst_component.component_id}")
```

Java

```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 CreateDataAnalystComponent {
    public static void main(String[] args) {
        StudyfetchSdkClient client = StudyfetchSdkOkHttpClient.builder()
            .fromEnv()
            .baseUrl("https://studyfetchapi.com")
            .build();

        ComponentCreateParams params = ComponentCreateParams.builder()
            .name("Student Performance Analyst")
            .type(ComponentCreateParams.Type.DATA_ANALYST)
            .config(ComponentCreateParams.Config.DataAnalystConfigDto.builder()
                .model("gpt-4.1-2025-04-14")
                .groupIds(List.of("class-101", "class-102")) // Only analyze these classes
                .materials(List.of("mat-123"))               // Optional reference material
                .enableWebSearch(false)
                .maxSteps(5)
                .build())
            .build();

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

C#

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

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

        var dataAnalystComponent = await client.V1.Components.Create(new()
        {
            Name = "Student Performance Analyst",
            Type = StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.Type.DataAnalyst,
            Config = new StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.ConfigProperties.DataAnalystConfigDto()
            {
                Model = "gpt-4.1-2025-04-14",
                GroupIDs = new List<string> { "class-101", "class-102" }, // Only analyze these classes
                Materials = new List<string> { "mat-123" },               // Optional reference material
                EnableWebSearch = false,
                MaxSteps = 5,
            }
        });

        Console.WriteLine($"Data Analyst component created: {dataAnalystComponent._ID}");
    }
}
```

## Configuration Parameters

#### `name`

string · required

Name of the data analyst component

#### `type`

string · required

Must be `"data_analyst"` (with an underscore)

#### `config`

object · required

Data analyst configuration object. Unknown keys are rejected with `400`.

### Configuration Properties

These properties go inside `config`.

#### `model`

string · required

Model key for analysis, such as `gpt-4.1-2025-04-14` or `gemini-2.5-flash`. The key is checked when a question is asked, not when the component is created, so an unrecognized key makes requests fail.

#### `groupIds`

array

Group or class IDs to analyze. When set, the analyst only sees usage from learners in these groups. When omitted, it analyzes your whole organization.

#### `materials`

array

Optional material IDs the analyst can search for background, such as a syllabus or course guide

#### `folders`

array

Optional folder IDs the analyst can search for background

#### `enableRAGSearch`

boolean · default: true

Search `materials` and `folders` when answering

#### `enableWebSearch`

boolean · default: true

Allow web searches. In the embed, the educator's web search toggle decides per message; its starting state comes from `features.enableWebSearch`, which defaults to off.

#### `enableComponentCreation`

boolean · default: true

Allow the analyst to create study components during the conversation

#### `maxSteps`

number · default: 3

Maximum tool-call steps per answer

#### `systemPrompt`

string

Accepted and stored, but the analyst currently always uses its own built-in instructions.

#### `temperature`

number

Accepted and stored, but the analyst currently uses a fixed temperature.

#### `maxTokens`

number

Accepted and stored, but the analyst currently uses a fixed output limit.

#### `enableHistory`

boolean

Stored on the component. The embed reads this setting from the embed token; use `features.enableHistory`.

#### `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.

## Response

```json
{
  "_id": "6650f1c2a9b3e4d5f6a7b8c9",
  "componentId": "data_analyst_V1StGXR8_Z",
  "name": "Student Performance Analyst",
  "type": "data_analyst",
  "status": "active",
  "origin": "api",
  "organization": "64f0a1b2c3d4e5f6a7b8c9d0",
  "materials": ["mat-123"],
  "folders": [],
  "config": {
    "model": "gpt-4.1-2025-04-14",
    "groupIds": ["class-101", "class-102"],
    "materials": ["mat-123"],
    "enableWebSearch": false,
    "maxSteps": 5
  },
  "usageCount": 0,
  "createdAt": "2026-10-06T10:00:00.000Z",
  "updatedAt": "2026-10-06T10:00:00.000Z"
}
```

## Embedding This Component

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

### Generate Embed URL

JavaScript

```javascript
const embedResponse = await client.v1.components.generateEmbed(dataAnalystComponent._id, {
  // User tracking
  userId: 'educator-456',
  studentName: 'Ms. Rivera',  // Name for display
  sessionId: 'session-789',

  // Data Analyst features
  features: {
    enableBadWordsFilter: true,  // Required by the SDK type; no effect on Data Analyst
    enableHistory: true,
    enableVoice: false,
    enableFollowUps: true,
    placeholderText: 'Ask about your students...',
  },

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

console.log(embedResponse.embedUrl);
```

Python

```python
embed_response = client.v1.components.generate_embed(
    data_analyst_component.api_id,
    user_id="educator-456",
    student_name="Ms. Rivera",  # Name for display
    session_id="session-789",
    features={
        "enable_bad_words_filter": True,  # Required by the SDK type; no effect on Data Analyst
        "enable_history": True,
        "enable_voice": False,
        "enable_follow_ups": True,
        "placeholder_text": "Ask about your students...",
    },
    width="100%",
    height="700px",
)

print(embed_response.embed_url)
```

Java

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

ComponentGenerateEmbedParams params = ComponentGenerateEmbedParams.builder()
    .id(dataAnalystComponent._id())
    // User tracking
    .userId("educator-456")
    .studentName("Ms. Rivera")  // Name for display
    .sessionId("session-789")

    // Data Analyst features
    .features(ComponentGenerateEmbedParams.Features.builder()
        .enableBadWordsFilter(true)  // Required: build() throws without it
        .enableHistory(true)
        .enableVoice(false)
        .enableFollowUps(true)
        .placeholderText("Ask about your students...")
        .build())

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

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

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

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Components;
using StudyfetchSDK.Models.V1.Components.ComponentGenerateEmbedParamsProperties;
using System;
using System.Threading.Tasks;

public class GenerateDataAnalystEmbed
{
    public static async Task GenerateEmbed(string dataAnalystComponentId)
    {
        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 = dataAnalystComponentId,
            UserID = "educator-456",
            StudentName = "Ms. Rivera",  // Name for display
            SessionID = "session-789",
            Features = new Features(enableBadWordsFilter: true)
            {
                EnableHistory = true,
                EnableVoice = false,
                EnableFollowUps = true,
                PlaceholderText = "Ask about your students..."
            },
            Width = "100%",
            Height = "700px"
        });

        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`).

Warning

**Embed tokens are valid for 1 hour.** `expiryHours` only changes the `expiresAt` value in the response; the token itself always expires 1 hour after it is issued, and the embed does not renew it. If educators keep the page open longer, load the iframe with a fresh embed URL. See Chat for the token refresh endpoint.

### Data Analyst-Specific Embedding Features

#### `features.enableHistory`

boolean · default: true

Show past conversations and let educators 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.enableWebSearch`

boolean · default: false

Starting state of the web search toggle

#### `features.enableComponentCreation`

boolean · default: true

Allow creating study components from the conversation

#### `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 input

### 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.

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

The React Native SDK does not include a Data Analyst component; use the iframe embed.

## Explainers

Source: https://studyfetch-api.honen.com/docs/6acd32059de34cae55b272ab

## Overview

The Explainers component turns your study materials into a narrated explainer video. Video generation starts in the background as soon as the component is created; the embed shows progress until the video is ready, and you can also check on the video from your server.

Explainer videos are generated in English. The React Native SDK does not support Explainers yet; use the iframe embed.

## Creating an Explainers Component

JavaScript

```javascript
import StudyfetchSDK from '@studyfetch/sdk';

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

const explainerComponent = await client.v1.components.create({
  name: 'Cell Biology Explainer',
  type: 'explainers',
  config: {
    materials: ['mat-123', 'mat-456'],
    folders: ['folder-789'],
    title: 'Understanding Cell Structure and Function',
    style: 'professional',
    targetLength: 120,
    model: 'gpt-4o-mini-2024-07-18',
    imageSearch: true,
    webSearch: true,
    verticalVideo: false,
  },
});

console.log('Explainer component created:', explainerComponent._id, explainerComponent.componentId);
```

Python

```python
from studyfetch_sdk import StudyfetchSDK

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

explainer_component = client.v1.components.create(
    name="Cell Biology Explainer",
    type="explainers",
    config={
        "materials": ["mat-123", "mat-456"],
        "folders": ["folder-789"],
        "title": "Understanding Cell Structure and Function",
        "style": "professional",
        "target_length": 120,
        "model": "gpt-4o-mini-2024-07-18",
        "image_search": True,
        "web_search": True,
        "vertical_video": False,
    },
)

print(f"Explainer component created: {explainer_component.api_id} {explainer_component.component_id}")
```

Java

```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 CreateExplainersComponent {
    public static void main(String[] args) {
        StudyfetchSdkClient client = StudyfetchSdkOkHttpClient.builder()
            .fromEnv()
            .baseUrl("https://studyfetchapi.com")
            .build();

        ComponentCreateParams params = ComponentCreateParams.builder()
            .name("Cell Biology Explainer")
            .type(ComponentCreateParams.Type.EXPLAINERS)
            .config(ComponentCreateParams.Config.ExplainersConfigDto.builder()
                .materials(List.of("mat-123", "mat-456"))
                .folders(List.of("folder-789"))
                .title("Understanding Cell Structure and Function")
                .style("professional")
                .targetLength(120)
                .model("gpt-4o-mini-2024-07-18")
                .imageSearch(true)
                .webSearch(true)
                .verticalVideo(false)
                .build())
            .build();

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

C#

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

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

        var explainerComponent = await client.V1.Components.Create(new()
        {
            Name = "Cell Biology Explainer",
            Type = StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.Type.Explainers,
            Config = new StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.ConfigProperties.ExplainersConfigDto()
            {
                Materials = new List<string> { "mat-123", "mat-456" },
                Folders = new List<string> { "folder-789" },
                Title = "Understanding Cell Structure and Function",
                Style = "professional",
                TargetLength = 120,
                Model = "gpt-4o-mini-2024-07-18",
                ImageSearch = true,
                WebSearch = true,
                VerticalVideo = false,
            }
        });

        Console.WriteLine($"Explainer component created: {explainerComponent._ID}");
    }
}
```

## Configuration Parameters

#### `name`

string · required

Name of the explainers component

#### `type`

string · required

Must be `"explainers"`

#### `config`

object · required

Explainers configuration object. Provide at least one material or folder. Unknown keys are rejected with `400`.

### Configuration Properties

These properties go inside `config`.

#### `materials`

array

Material IDs to build the video from. Provide `materials`, `folders`, or both.

#### `folders`

array

Folder IDs whose materials (including subfolders) the video is built from

#### `title`

string

Title of the explainer video. Defaults to the component `name`.

#### `style`

string · default: professional

Style of the video, as free text (for example, `professional`)

#### `targetLength`

number · default: 60

Target video length in seconds. Use a value from 15 to 1200; the API does not check this range when the component is created.

#### `model`

string · default: gpt-4o-mini-2024-07-18

Model that summarizes your materials into the brief the video is generated from

#### `imageSearch`

boolean · default: false

Search for relevant images to include in the video

#### `webSearch`

boolean · default: false

Search the web for additional information and context

#### `verticalVideo`

boolean · default: false

Create the video in vertical format (9:16) for mobile viewing

Note

Each component generates one video. Updating the component's `config` later does not regenerate it; create a new component instead.

## Response

```json
{
  "_id": "6650f1c2a9b3e4d5f6a7b8c9",
  "componentId": "explainers_V1StGXR8_Z",
  "name": "Cell Biology Explainer",
  "type": "explainers",
  "status": "active",
  "origin": "api",
  "organization": "64f0a1b2c3d4e5f6a7b8c9d0",
  "materials": ["mat-123", "mat-456"],
  "folders": ["folder-789"],
  "config": {
    "materials": ["mat-123", "mat-456"],
    "folders": ["folder-789"],
    "title": "Understanding Cell Structure and Function",
    "style": "professional",
    "targetLength": 120,
    "model": "gpt-4o-mini-2024-07-18",
    "imageSearch": true,
    "webSearch": true,
    "verticalVideo": false
  },
  "usageCount": 0,
  "createdAt": "2026-10-06T10:00:00.000Z",
  "updatedAt": "2026-10-06T10:00:00.000Z"
}
```

The component is `active` as soon as it is created. The video's own progress is tracked separately; see Getting the Video.

## Getting the Video

To check on the video from your server, send `{ "action": "get_video" }` to `POST /api/v1/components/{id}/interact`, where `{id}` is the component's `_id`. It returns the component's most recent video. If the component has no video yet, this starts generating one.

The SDKs' `interact` methods don't send a request body yet, so use cURL or an HTTP client.

```bash
curl -X POST "https://studyfetchapi.com/api/v1/components/6650f1c2a9b3e4d5f6a7b8c9/interact" \
  -H "x-api-key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{ "action": "get_video" }'
```

### Response

```json
{
  "video": {
    "_id": "6650f2d3b4c5d6e7f8a9b0c1",
    "title": "Understanding Cell Structure and Function",
    "componentId": "explainers_V1StGXR8_Z",
    "status": "READY",
    "config": {
      "model": "gpt-4o-mini-2024-07-18",
      "targetLength": 120,
      "webSearch": true,
      "imageSearch": true,
      "verticalVideo": false,
      "style": "professional"
    },
    "results": {
      "videoUrl": "https://...",
      "thumbnailUrl": "https://...",
      "streamUrl": "https://...",
      "transcript": "...",
      "sections": [],
      "webSearchResults": [],
      "imageSources": []
    },
    "createdAt": "2026-10-06T10:00:01.000Z",
    "updatedAt": "2026-10-06T10:03:12.000Z"
  }
}
```

| `status` | Meaning |
| --- | --- |
| `PENDING` | The video is being generated. Poll again in a few seconds. |
| `READY` | The video is ready; `results.videoUrl` is set. |
| `ERROR` | Generation failed. Create a new component to try again. |

`results` also carries the outline (`sections`), `transcript`, web sources, and image sources that the embed shows in its tabs.

## Embedding This Component

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

### Generate Embed URL

JavaScript

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

  // Explainer-specific features
  features: {
    enableBadWordsFilter: true,  // Required by the SDK type; no effect on Explainers
    enableWebSearchSources: true,
    enableImageSources: true,
    enableTranscript: true,
    enableOutline: true,
  },

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

console.log(embedResponse.embedUrl);
```

Python

```python
embed_response = client.v1.components.generate_embed(
    explainer_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; no effect on Explainers
        "enable_web_search_sources": True,
        "enable_image_sources": True,
        "enable_transcript": True,
        "enable_outline": True,
    },
    width="100%",
    height="600px",
)

print(embed_response.embed_url)
```

Java

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

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

    // Explainer-specific features
    .features(ComponentGenerateEmbedParams.Features.builder()
        .enableBadWordsFilter(true)  // Required: build() throws without it
        .enableWebSearchSources(true)
        .enableImageSources(true)
        .enableTranscript(true)
        .enableOutline(true)
        .build())

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

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

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

C#

```csharp
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 GenerateExplainersEmbed
{
    public static async Task GenerateEmbed(string explainerComponentId)
    {
        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 = explainerComponentId,
            UserID = "user-456",
            StudentName = "Jane Smith",  // Student name for display
            GroupIDs = new List<string> { "class-101", "class-102" },
            SessionID = "session-789",
            Features = new Features(enableBadWordsFilter: true)
            {
                EnableWebSearchSources = true,
                EnableImageSources = true,
                EnableTranscript = true,
                EnableOutline = true
            },
            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`).

Warning

**Embed tokens are valid for 1 hour.** `expiryHours` only changes the `expiresAt` value in the response; the token itself always expires 1 hour after it is issued, and the embed does not renew it. If learners keep the page open longer, load the iframe with a fresh embed URL. See Chat for the token refresh endpoint.

### Explainer-Specific Embedding Features

The embed shows the video with up to four tabs below it. Each feature turns one tab on or off.

#### `features.enableOutline`

boolean · default: true

**Outline** tab: the video's sections

#### `features.enableTranscript`

boolean · default: true

**Transcript** tab: the narration text

#### `features.enableWebSearchSources`

boolean · default: true

**Web Sources** tab: web pages used when `webSearch` is on

#### `features.enableImageSources`

boolean · default: true

**Images** tab: sources of images used when `imageSearch` is on

### 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.

```html
<iframe
  src="EMBED_URL_FROM_RESPONSE"
  width="100%"
  height="600px"
  frameborder="0"
  allowfullscreen
  style="border: 1px solid #e5e5e5; border-radius: 8px;">
</iframe>
```

## Flashcards

Source: https://studyfetch-api.honen.com/docs/6acd32049de34cae55b272a5

## Overview

The Flashcards component automatically generates interactive flashcards from your study materials. It supports multiple card types, spaced repetition learning, and difficulty levels to optimize your study sessions.

Cards are generated in the background after the component is created, and generation waits for materials that are still processing. Learners can start studying in the embed while cards are still being added.

## Creating a Flashcards Component

JavaScript

```javascript
import StudyfetchSDK from '@studyfetch/sdk';

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

const flashcardsComponent = await client.v1.components.create({
  name: 'Biology Flashcards',
  type: 'flashcards',
  config: {
    materials: ['mat-123', 'mat-456'],
    folders: ['folder-789'],
    difficulty: 'mixed',
    totalFlashcards: 30,
    viewMode: 'normal',
    cardTypes: ['BASIC', 'MULTIPLE_CHOICE'],
    model: 'gpt-4o-mini-2024-07-18',
  },
});

console.log('Flashcards component created:', flashcardsComponent._id, flashcardsComponent.componentId);
```

Python

```python
from studyfetch_sdk import StudyfetchSDK

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

flashcards_component = client.v1.components.create(
    name="Biology Flashcards",
    type="flashcards",
    config={
        "materials": ["mat-123", "mat-456"],
        "folders": ["folder-789"],
        "difficulty": "mixed",
        "total_flashcards": 30,
        "view_mode": "normal",
        "card_types": ["BASIC", "MULTIPLE_CHOICE"],
        "model": "gpt-4o-mini-2024-07-18",
    },
)

print(f"Flashcards component created: {flashcards_component.api_id} {flashcards_component.component_id}")
```

Java

```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 CreateFlashcardsComponent {
    public static void main(String[] args) {
        StudyfetchSdkClient client = StudyfetchSdkOkHttpClient.builder()
            .fromEnv()
            .baseUrl("https://studyfetchapi.com")
            .build();

        ComponentCreateParams params = ComponentCreateParams.builder()
            .name("Biology Flashcards")
            .type(ComponentCreateParams.Type.FLASHCARDS)
            .config(ComponentCreateParams.Config.FlashcardsConfigDto.builder()
                .materials(List.of("mat-123", "mat-456"))
                .folders(List.of("folder-789"))
                .difficulty(ComponentCreateParams.Config.FlashcardsConfigDto.Difficulty.MIXED)
                .totalFlashcards(30.0)
                .viewMode(ComponentCreateParams.Config.FlashcardsConfigDto.ViewMode.NORMAL)
                .cardTypes(List.of("BASIC", "MULTIPLE_CHOICE"))
                .model("gpt-4o-mini-2024-07-18")
                .build())
            .build();

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

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Components;
using StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.ConfigProperties.FlashcardsConfigDtoProperties;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

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

        var flashcardsComponent = await client.V1.Components.Create(new()
        {
            Name = "Biology Flashcards",
            Type = StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.Type.Flashcards,
            Config = new StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.ConfigProperties.FlashcardsConfigDto()
            {
                Materials = new List<string> { "mat-123", "mat-456" },
                Folders = new List<string> { "folder-789" },
                Difficulty = Difficulty.Mixed,
                TotalFlashcards = 30,
                ViewMode = ViewMode.Normal,
                CardTypes = new List<string> { "BASIC", "MULTIPLE_CHOICE" },
                Model = "gpt-4o-mini-2024-07-18",
            }
        });

        Console.WriteLine($"Flashcards component created: {flashcardsComponent._ID}");
    }
}
```

## Configuration Parameters

#### `name`

string · required

Name of the flashcards component

#### `type`

string · required

Must be `"flashcards"`

#### `config`

object · required

Flashcards configuration object. Provide at least one material or folder. Unknown keys and values in the wrong case are rejected with `400`.

### Configuration Properties

These properties go inside `config`.

#### `materials`

array

Material IDs to generate flashcards from. Provide `materials`, `folders`, or both.

#### `folders`

array

Folder IDs whose materials (including subfolders) flashcards are generated from

#### `difficulty`

string · default: medium

Difficulty level of generated cards. Lowercase:

- `easy` - Basic recall and definitions
- `medium` - Application and understanding
- `hard` - Complex analysis and synthesis
- `mixed` - Combination of all levels

#### `totalFlashcards`

number · default: 10

Number of flashcards to generate, from 1 to 50

#### `viewMode`

string · default: spaced\_repetition

How the embed presents cards:

- `spaced_repetition` - Show the cards due for review and let learners rate them
- `normal` - Browse all cards in order

#### `cardTypes`

array · default: \["BASIC"\]

Types of flashcards to generate:

- `BASIC` - Front and back
- `MULTIPLE_CHOICE` - Question with answer options
- `FILL_IN_THE_BLANK` - Sentence with a missing term
- `CLOZE` - Text with hidden passages

#### `model`

string · default: gpt-4o-mini-2024-07-18

AI model used to generate the flashcards

#### `learningSteps`

string

Accepted and stored, but not currently used. Learning steps are fixed at 1 minute, 10 minutes, and 1 day.

#### `maxReviewInterval`

number

Accepted and stored, but not currently used

## Response

```json
{
  "_id": "6650f1c2a9b3e4d5f6a7b8c9",
  "componentId": "flashcards_V1StGXR8_Z",
  "name": "Biology Flashcards",
  "type": "flashcards",
  "status": "active",
  "origin": "api",
  "organization": "64f0a1b2c3d4e5f6a7b8c9d0",
  "materials": ["mat-123", "mat-456"],
  "folders": ["folder-789"],
  "config": {
    "materials": ["mat-123", "mat-456"],
    "folders": ["folder-789"],
    "difficulty": "mixed",
    "totalFlashcards": 30,
    "viewMode": "normal",
    "cardTypes": ["BASIC", "MULTIPLE_CHOICE"],
    "model": "gpt-4o-mini-2024-07-18"
  },
  "usageCount": 0,
  "createdAt": "2026-10-06T10:00:00.000Z",
  "updatedAt": "2026-10-06T10:00:00.000Z"
}
```

The component is `active` as soon as it is created, while cards are still being generated. Generation progress is reported as `generationStatus` when you read the cards.

## Embedding This Component

Once you've created a Flashcards component, you can embed it on your website using the embedding API. The component must be `active`. Spaced-repetition progress is stored per `userId`, so generate each learner's embed with their own `userId`.

### Generate Embed URL

JavaScript

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

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

console.log(embedResponse.embedUrl);
```

Python

```python
embed_response = client.v1.components.generate_embed(
    flashcards_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",
    width="100%",
    height="500px",
)

print(embed_response.embed_url)
```

Java

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

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

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

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

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

C#

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

public class GenerateFlashcardsEmbed
{
    public static async Task GenerateEmbed(string flashcardsComponentId)
    {
        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 = flashcardsComponentId,
            UserID = "user-456",
            StudentName = "Jane Smith",  // Student name for display
            GroupIDs = new List<string> { "class-101", "class-102" },
            SessionID = "session-789",
            Width = "100%",
            Height = "500px"
        });

        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`).

The flashcards embed doesn't use any `features` settings. How it presents cards comes from the component's `viewMode`.

Warning

**Embed tokens are valid for 1 hour.** `expiryHours` only changes the `expiresAt` value in the response; the token itself always expires 1 hour after it is issued, and the embed does not renew it. If learners keep the page open longer, load the iframe with a fresh embed URL. See Chat for the token refresh endpoint.

### 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.

```html
<iframe
  src="EMBED_URL_FROM_RESPONSE"
  width="100%"
  height="500px"
  frameborder="0"
  style="border: 1px solid #e5e5e5; border-radius: 8px;">
</iframe>
```

## Reading Cards and Progress

To work with cards from your server, send an action to `POST /api/v1/components/{id}/interact`, where `{id}` is the component's `_id`:

| `action` | Body fields | Returns |
| --- | --- | --- |
| `get_all` | `userId`, `limit` (default 20), `offset` | `cards`, `totalCards`, `hasMore`, `generationStatus` |
| `get_due` | `userId`, `limit` (default 20) | Cards due for review for that user, `totalDue`, `generationStatus` |
| `rate` | `userId`, `cardId`, `rating` | The card's updated spaced-repetition `progress`, including its next `dueDate` |
| `get_stats` | `userId` | The user's study statistics for the set |

`rating` is `0` (Again), `1` (Hard), `2` (Good), or `3` (Easy). `generationStatus` reports `totalRequested`, `totalGenerated`, `inProgress`, `complete`, `failed`, and `waitingForMaterials` while cards are being generated (or before any exist). It is `null` once generation has finished and cards exist.

The SDKs' `interact` methods don't send a request body yet, so use cURL or an HTTP client.

```bash
curl -X POST "https://studyfetchapi.com/api/v1/components/6650f1c2a9b3e4d5f6a7b8c9/interact" \
  -H "x-api-key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{ "action": "get_due", "userId": "user-456", "limit": 10 }'
```

## React Native

The React Native SDK (`@studyfetch/react-native-sdk`) renders Flashcards natively with embed tokens instead of an iframe. Import it from `@studyfetch/react-native-sdk/flashcards` and wrap your app in `EmbedTokenProvider` with a `getToken` function that asks your backend to mint an embed token for a component.

```tsx
import { EmbedTokenProvider } from '@studyfetch/react-native-sdk';
import { StudyFetchFlashcards, useFlashcards } from '@studyfetch/react-native-sdk/flashcards';

// Full flashcards UI with Again / Hard / Good / Easy rating
<EmbedTokenProvider getToken={fetchToken}>
  <StudyFetchFlashcards componentId="flashcards_V1StGXR8_Z" userId="user-456" mode="due" />
</EmbedTokenProvider>

// Headless: build your own UI
const { cards, current, stats, generating, rate, next, prev, loadMore } = useFlashcards({
  componentId: 'flashcards_V1StGXR8_Z',
  userId: 'user-456',
});
```

- `mode` is `all` (default) or `due`.
- Pass a `userId`; ratings are saved per user.
- `generating` is `true` while cards are still being created.

## Folders

Source: https://studyfetch-api.honen.com/docs/6acd2e3b94f05db978608d0f

## Overview

Folders help you organize materials into logical groups. You can:

- Create folders and subfolders
- Move materials between folders
- Delete folders
- List folder contents
- Create nested folder structures

## Folder Object

```json
{
  "_id": "folder_123abc",
  "name": "Biology Notes",
  "organizationId": "org_456def",
  "parentFolderId": null, // null for root folders
  "materialCount": 15,
  "subfolderCount": 3,
  "path": "/Biology Notes",
  "status": "active",
  "createdAt": "2024-01-15T10:00:00Z",
  "updatedAt": "2024-01-15T10:00:00Z"
}
```

## Create Folder

Create a new folder to organize your materials.

JavaScript

```javascript
// Create a root folder
const folder = await client.v1.folders.create({
  name: 'Biology Course',
  parentFolderId: undefined // Optional, null for root folder
});

// Create a subfolder
const subfolder = await client.v1.folders.create({
  name: 'Chapter 1 - Cell Structure',
  parentFolderId: folder._id
});

console.log('Folder created:', folder._id);
console.log('Subfolder created:', subfolder._id);
```

Python

```python
# Create a root folder
folder = client.v1.folders.create(
    name="Biology Course",
    parent_id=None  # Optional, None for root folder
)

# Create a subfolder
subfolder = client.v1.folders.create(
    name="Chapter 1 - Cell Structure",
    parent_id=folder._id
)

print(f"Folder created: {folder._id}")
print(f"Subfolder path: {subfolder.path}")
```

Java

```java
import com.studyfetch.javasdk.models.v1.folders.FolderCreateParams;

// Create a root folder
FolderCreateResponse folder = client.v1().folders().create(
    FolderCreateParams.builder()
        .name("Biology Course")
        // Don't set parentFolderId for root folder - it defaults to Optional.empty()
        .build()
);

// Create a subfolder
FolderCreateResponse subfolder = client.v1().folders().create(
    FolderCreateParams.builder()
        .name("Chapter 1 - Cell Structure")
        .parentFolderId(folder._id())
        .build()
);

System.out.println("Folder created: " + folder._id());
System.out.println("Subfolder created: " + subfolder._id());
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Folders;
using System;
using System.Threading.Tasks;

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

        // Create a root folder
        var folder = await client.V1.Folders.Create(new()
        {
            Name = "Biology Course",
            ParentFolderID = null // Optional, null for root folder
        });

        // Create a subfolder
        var subfolder = await client.V1.Folders.Create(new()
        {
            Name = "Chapter 1 - Cell Structure",
            ParentFolderID = folder._ID
        });

        Console.WriteLine($"Folder created: {folder._ID}");
        Console.WriteLine($"Subfolder path: {subfolder.ParentFolderID}");
    }
}
```

## List All Folders

Get all folders in your organization.

JavaScript

```javascript
// Get all folders
const folders = await client.v1.folders.list();

// Get only root folders
const rootFolders = await client.v1.folders.list({
  parentFolderId: undefined
});

// Get subfolders of a specific folder
const subfolders = await client.v1.folders.list({
  parentFolderId: 'folder_123abc'
});

console.log(`Found ${folders.length} total folders`);
```

Python

```python
# Get all folders
folders = client.v1.folders.list()

# Get only root folders
root_folders = client.v1.folders.list(
    parent_folder_id=None
)

# Get subfolders of a specific folder
subfolders = client.v1.folders.list(
    parent_folder_id="folder_123abc"
)

print(f"Found {len(folders)} total folders")
```

Java

```java
import com.studyfetch.javasdk.models.v1.folders.FolderListParams;
import com.studyfetch.javasdk.models.v1.folders.FolderListResponse;
import java.util.List;

// Get all folders
List<FolderListResponse> folders = client.v1().folders().list(
    FolderListParams.builder()
        .build()
);

// Get only root folders (don't set parentFolderId)
List<FolderListResponse> rootFolders = client.v1().folders().list(
    FolderListParams.builder()
        // Don't set parentFolderId to get root folders
        .build()
);

// Get subfolders of a specific folder
List<FolderListResponse> subfolders = client.v1().folders().list(
    FolderListParams.builder()
        .parentFolderId("folder_123abc")
        .build()
);

System.out.println("Found " + folders.size() + " total folders");
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Folders;
using System;
using System.Threading.Tasks;
using System.Text.Json;

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

        // Get all folders
        var folders = await client.V1.Folders.List(new());

        // Get only root folders
        var rootFolders = await client.V1.Folders.List(new()
        {
            ParentFolderID = null
        });

        // Get subfolders of a specific folder
        var subfolders = await client.V1.Folders.List(new()
        {
            ParentFolderID = "folder_123abc"
        });

        Console.WriteLine($"Found {folders.Count} total folders");
    }
}
```

## Get Folder Details

Retrieve details about a specific folder including material and subfolder counts.

JavaScript

```javascript
const folder = await client.v1.folders.retrieve('folder_123abc');

console.log('Folder name:', folder.name);
console.log('Materials:', folder.materialCount);
console.log('Subfolders:', folder.subfolders.length);
console.log('Parent folder:', folder.parentFolderId || 'root');
```

Python

```python
folder = client.v1.folders.retrieve("folder_123abc")

print(f"Folder name: {folder.name}")
print(f"Materials: {folder.material_count}")
print(f"Subfolders: {folder.subfolder_count}")
print(f"Path: {folder.path}")
```

Java

```java
import com.studyfetch.javasdk.models.v1.folders.FolderRetrieveParams;
import com.studyfetch.javasdk.models.v1.folders.FolderRetrieveResponse;

FolderRetrieveResponse folder = client.v1().folders().retrieve(
    FolderRetrieveParams.builder()
        .id("folder_123abc")
        .build()
);

System.out.println("Folder name: " + folder.name());
System.out.println("Materials: " + folder.materialCount());
System.out.println("Subfolders: " + folder.subfolders().size());
System.out.println("Parent folder: " + folder.parentFolderId().orElse("root"));
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Folders;
using System;
using System.Threading.Tasks;

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

        var folder = await client.V1.Folders.Retrieve(new()
        {
            ID = "folder_123abc"
        });

        Console.WriteLine($"Folder name: {folder.Name}");
        Console.WriteLine($"Materials: {folder.MaterialCount}");
        Console.WriteLine($"Subfolders: {folder.Subfolders}");
        Console.WriteLine($"Path: {folder.ParentFolderID}");
    }
}
```

## Move Folder

Move a folder to a different parent folder or to the root level.

JavaScript

```javascript
// Move to another folder
const movedFolder = await client.v1.folders.move('folder_123abc', {
  parentFolderId: 'folder_456def'
});

// Move to root level
const rootFolder = await client.v1.folders.move('folder_123abc', {
  parentFolderId: null
});

console.log('Folder moved to:', movedFolder.parentFolderId || 'root');
```

Python

```python
# Move to another folder
moved_folder = client.v1.folders.move(
    folder_id="folder_123abc",
    parent_folder_id="folder_456def"
)

# Move to root level
root_folder = client.v1.folders.move(
    folder_id="folder_123abc",
    parent_folder_id=None
)

print(f"Folder moved to: {moved_folder.path}")
```

Java

```java
import com.studyfetch.javasdk.models.v1.folders.FolderMoveParams;

// Move to another folder
FolderMoveResponse movedFolder = client.v1().folders().move(
    FolderMoveParams.builder()
        .id("folder_123abc")
        .parentFolderId("folder_456def")
        .build()
);

// Move to root level
FolderMoveResponse rootFolder = client.v1().folders().move(
    FolderMoveParams.builder()
        .id("folder_123abc")
        // Don't set parentFolderId to move to root
        .build()
);

System.out.println("Folder moved to: " + movedFolder.parentFolderId().orElse("root"));
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Folders;
using System;
using System.Threading.Tasks;

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

        // Move to another folder
        var movedFolder = await client.V1.Folders.Move(new()
        {
            ID = "folder_123abc",
            ParentFolderID = "folder_456def"
        });

        // Move to root level
        var rootFolder = await client.V1.Folders.Move(new()
        {
            ID = "folder_123abc",
            ParentFolderID = null
        });

        Console.WriteLine($"Folder moved to: {movedFolder.ParentFolderID ?? "root"}");
    }
}
```

## Delete Folder

Delete a folder from your organization.

JavaScript

```javascript
// Delete folder and move materials to another folder
await client.v1.folders.delete('folder_123abc');

console.log('Folder deleted successfully');
```

Python

```python
# Delete folder
client.v1.folders.delete(
    folder_id="folder_123abc"
)

print("Folder deleted successfully")
```

Java

```java
import com.studyfetch.javasdk.models.v1.folders.FolderDeleteParams;

// Delete folder
client.v1().folders().delete(
    FolderDeleteParams.builder()
        .id("folder_123abc")
        .build()
);

System.out.println("Folder deleted successfully");
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Folders;
using System;
using System.Threading.Tasks;

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

        // Delete folder
        await client.V1.Folders.Delete(new()
        {
            ID = "folder_123abc"
        });

        Console.WriteLine("Folder deleted successfully");
    }
}
```

## Get Folder Contents

List all materials in a specific folder.

### Using materials.list()

JavaScript

```javascript
// Get materials in a folder with pagination
const materials = await client.v1.materials.list({
  folderId: 'folder_123abc',
  limit: '20',
  page: '1'
});

console.log(`Folder contains ${materials.materials?.length || 0} materials`);
console.log(`Total in folder: ${materials.totalCount}`);

// Display material names
materials.materials?.forEach(material => {
  console.log(`- ${material.name} (${material.contentType})`);
});
```

Python

```python
# Get materials in a folder with pagination
materials = client.v1.materials.list(
    folder_id="folder_123abc",
    limit="20",
    page="1"
)

print(f"Folder contains {len(materials.data)} materials")
print(f"Total in folder: {materials.total}")

# Display material names
for material in materials.data:
    print(f"- {material.name} ({material.content_type})")
```

Java

```java
// Get materials in a folder with pagination
MaterialListParams params = MaterialListParams.builder()
    .folderId("folder_123abc")
    .limit("20")
    .page("1")
    .build();
MaterialListResponse materials = client.v1().materials().list(params);

System.out.println("Folder contains " + materials.materials().orElse(Collections.emptyList()).size() + " materials");
System.out.println("Total in folder: " + materials.totalCount().orElse(0.0));

// Display material names
for (MaterialResponse material : materials.materials().orElse(Collections.emptyList())) {
    System.out.println("- " + material.name() + 
        " (" + material.contentType() + ")");
}
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using System;
using System.Threading.Tasks;

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

        // Get materials in a folder with pagination
        var materials = await client.V1.Materials.List(new()
        {
            FolderID = "folder_123abc",
            Limit = "20",
            Page = "1"
        });

        Console.WriteLine($"Folder contains {materials.Materials?.Count ?? 0} materials");
        Console.WriteLine($"Total in folder: {materials.TotalCount}");

        // Display material names
        foreach (var material in materials.Materials ?? [])
        {
            Console.WriteLine($"- {material.Name} ({material.ContentType})");
        }
    }
}
```

### Using folders.listMaterials()

Alternatively, you can use the `folders.listMaterials()` method to get materials directly from a folder with pagination support.

JavaScript

```javascript
// Get materials using folders.listMaterials() with pagination
const folderMaterials = await client.v1.folders.listMaterials(
  'folder_123abc',
  {
    limit: '20',
    page: '1'
  }
);

console.log(`Found ${folderMaterials.materials?.length || 0} materials`);
console.log(`Total: ${folderMaterials.totalCount}`);
console.log(`Page ${folderMaterials.page} of ${folderMaterials.totalPages}`);

// Iterate through all pages
if (folderMaterials.totalPages && folderMaterials.totalPages > 1) {
  for (let page = 2; page <= folderMaterials.totalPages; page++) {
    const nextPage = await client.v1.folders.listMaterials(
      'folder_123abc',
      { limit: '20', page: page.toString() }
    );
    console.log(`Page ${page}: ${nextPage.materials?.length || 0} materials`);
  }
}
```

Python

```python
# Get materials using folders.list_materials() with pagination
folder_materials = client.v1.folders.list_materials(
    "folder_123abc",
    limit="20",
    page="1"
)

print(f"Found {len(folder_materials.data)} materials")
print(f"Total: {folder_materials.total}")
print(f"Page {folder_materials.page} of {folder_materials.total_pages}")

# Iterate through all pages
for page in range(2, folder_materials.total_pages + 1):
    next_page = client.v1.folders.list_materials(
        "folder_123abc",
        limit="20",
        page=str(page)
    )
    print(f"Page {page}: {len(next_page.data)} materials")
```

Java

```java
import com.studyfetch.javasdk.models.v1.folders.FolderListMaterialsParams;
import com.studyfetch.javasdk.models.v1.folders.FolderListMaterialsResponse;
import java.util.Collections;

// Get materials using folders.listMaterials() with pagination
FolderListMaterialsParams params = FolderListMaterialsParams.builder()
    .id("folder_123abc")
    .limit("20")
    .page("1")
    .build();
FolderListMaterialsResponse folderMaterials = client.v1().folders()
    .listMaterials(params);

System.out.println("Found " + folderMaterials.materials().orElse(Collections.emptyList()).size() + " materials");
System.out.println("Total: " + folderMaterials.totalCount().orElse(0.0));
System.out.println("Page " + folderMaterials.page().orElse(0.0) + " of " + 
    folderMaterials.totalPages().orElse(0.0));

// Iterate through all pages
if (folderMaterials.totalPages().isPresent() && folderMaterials.totalPages().get() > 1) {
    for (int page = 2; page <= folderMaterials.totalPages().get(); page++) {
        FolderListMaterialsParams nextParams = FolderListMaterialsParams.builder()
            .id("folder_123abc")
            .limit("20")
            .page(String.valueOf(page))
            .build();
        FolderListMaterialsResponse nextPage = client.v1().folders()
            .listMaterials(nextParams);
        System.out.println("Page " + page + ": " + nextPage.materials().orElse(Collections.emptyList()).size() + " materials");
    }
}
```

C#

```csharp
// Get materials using folders.ListMaterials() with pagination
var folderMaterials = await client.V1.Folders.ListMaterials(new()
{
    ID = "folder_123abc"
});

Console.WriteLine($"Found {folderMaterials.Materials?.Count ?? 0} materials");

// Display material names
foreach (var material in folderMaterials.Materials ?? [])
{
    Console.WriteLine($"- {material.Name} ({material._ID})");
}
```

## Example: Complete Folder Organization

Here's a complete example of organizing course materials:

JavaScript

```javascript
// 1. Create main course folder
const courseFolder = await client.v1.folders.create({
  name: 'Biology 101'
});

// 2. Create chapter folders
const chapter1 = await client.v1.folders.create({
  name: 'Chapter 1 - Cells',
  parentFolderId: courseFolder._id
});

const chapter2 = await client.v1.folders.create({
  name: 'Chapter 2 - Genetics',
  parentFolderId: courseFolder._id
});

// 3. Upload materials to specific folders
const fileBuffer = await fs.promises.readFile(
  path.join(process.cwd(), 'cell-structure.pdf'),
);
const file = new File([fileBuffer], 'cell-structure.pdf', {
  type: 'application/pdf',
});

const material = await client.v1.materials.upload.uploadFile({
  file,
  name: 'Cell Structure Notes',
  folderId: chapter1._id
});

// 4. List folder structure
const subfolders = await client.v1.folders.list({
  parentFolderId: courseFolder._id
});

console.log(`Course folder: ${courseFolder.name}`);
subfolders.forEach(folder => {
  console.log(`  └─ ${folder.name}`);
});

// 5. Get materials in chapter 1 with pagination
const chapter1Materials = await client.v1.materials.list({
  folderId: chapter1._id,
  limit: '20',
  page: '1'
});

console.log(`\nChapter 1 materials: ${chapter1Materials.materials?.length || 0}`);
console.log(`Total in chapter: ${chapter1Materials.totalCount}`);

// Get next page if needed
if (chapter1Materials.totalPages && chapter1Materials.totalPages > 1) {
  const page2 = await client.v1.materials.list({
    folderId: chapter1._id,
    limit: '20',
    page: '2'
  });
  console.log(`Page 2 contains ${page2.materials?.length || 0} more materials`);
}
```

Python

```python
# 1. Create main course folder
course_folder = client.v1.folders.create(
    name="Biology 101"
)

# 2. Create chapter folders
chapter1 = client.v1.folders.create(
    name="Chapter 1 - Cells",
    parent_folder_id=course_folder._id
)

chapter2 = client.v1.folders.create(
    name="Chapter 2 - Genetics",
    parent_folder_id=course_folder._id
)

# 3. Upload materials to specific folders
with open('cell-structure.pdf', 'rb') as file:
    material = client.v1.materials.upload(
        file=file,
        name="Cell Structure Notes",
        folder_id=chapter1._id
    )

# 4. List folder structure
subfolders = client.v1.folders.list(
    parent_folder_id=course_folder._id
)

print(f"Course folder: {course_folder.name}")
for folder in subfolders:
    print(f"  └─ {folder.name}")

# 5. Get materials in chapter 1
chapter1_materials = client.v1.materials.list(
    folder_id=chapter1._id
)

print(f"\nChapter 1 materials: {len(chapter1_materials)}")
```

Java

```java
import com.studyfetch.javasdk.models.v1.folders.FolderCreateParams;
import com.studyfetch.javasdk.models.v1.folders.FolderCreateResponse;
import com.studyfetch.javasdk.models.v1.folders.FolderListParams;
import com.studyfetch.javasdk.models.v1.folders.FolderListResponse;
import com.studyfetch.javasdk.models.v1.materials.MaterialListParams;
import com.studyfetch.javasdk.models.v1.materials.MaterialListResponse;
import com.studyfetch.javasdk.models.v1.materials.MaterialResponse;
import com.studyfetch.javasdk.models.v1.materials.upload.UploadUploadFileParams;
import java.io.File;
import java.io.FileInputStream;
import java.util.Collections;
import java.util.List;

// 1. Create main course folder
FolderCreateResponse courseFolder = client.v1().folders().create(
    FolderCreateParams.builder()
        .name("Biology 101")
        // Don't set parentFolderId for root folder
        .build()
);

// 2. Create chapter folders
FolderCreateResponse chapter1 = client.v1().folders().create(
    FolderCreateParams.builder()
        .name("Chapter 1 - Cells")
        .parentFolderId(courseFolder._id())
        .build()
);

FolderCreateResponse chapter2 = client.v1().folders().create(
    FolderCreateParams.builder()
        .name("Chapter 2 - Genetics")
        .parentFolderId(courseFolder._id())
        .build()
);

// 3. Upload materials to specific folders
File file = new File("cell-structure.pdf");
UploadUploadFileParams uploadParams = UploadUploadFileParams.builder()
    .file(new FileInputStream(file))
    .name("Cell Structure Notes")
    .folderId(chapter1._id())
    .build();
MaterialResponse material = client.v1().materials().upload().uploadFile(uploadParams);

// 4. List folder structure
FolderListParams params = FolderListParams.builder()
    .parentFolderId(courseFolder._id())
    .build();
List<FolderListResponse> subfolders = client.v1().folders().list(params);

System.out.println("Course folder: " + courseFolder.name());
for (FolderListResponse folder : subfolders) {
    System.out.println("  └─ " + folder.name());
}

// 5. Get materials in chapter 1
MaterialListParams matParams = MaterialListParams.builder()
    .folderId(chapter1._id())
    .build();
MaterialListResponse chapter1Materials = client.v1().materials()
    .list(matParams);

System.out.println("\nChapter 1 materials: " + 
    chapter1Materials.materials().orElse(Collections.emptyList()).size());
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Folders;
using StudyfetchSDK.Models.V1.Materials;
using System;
using System.IO;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Text.Json;

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

        // 1. Create main course folder
        var courseFolder = await client.V1.Folders.Create(new()
        {
            Name = "Biology 101"
        });

        // 2. Create chapter folders
        var chapter1 = await client.V1.Folders.Create(new()
        {
            Name = "Chapter 1 - Cells",
            ParentFolderID = courseFolder._ID
        });

        var chapter2 = await client.V1.Folders.Create(new()
        {
            Name = "Chapter 2 - Genetics",
            ParentFolderID = courseFolder._ID
        });

        // 3. Upload materials to specific folders
        var fileBytes = await File.ReadAllBytesAsync("cell-structure.pdf");
        var material = await client.V1.Materials.Upload.UploadFile(new()
        {
            File = fileBytes,
            FileName = "cell-structure.pdf",
            Name = "Cell Structure Notes",
            FolderID = chapter1._ID
        });

        // 4. List folder structure
        var subfolders = await client.V1.Folders.List(new()
        {
            ParentFolderID = courseFolder._ID
        });

        Console.WriteLine($"Course folder: {courseFolder.Name}");
        foreach (var folder in subfolders)
        {
            Console.WriteLine($"  └─ {folder.Name}");
        }

        // 5. Get materials in chapter 1
        var chapter1Materials = await client.V1.Materials.List(new()
        {
            FolderID = chapter1._ID
        });

        Console.WriteLine($"\nChapter 1 materials: {chapter1Materials.Materials?.Count ?? 0}");
    }
}
```

## Generating Materials

Source: https://studyfetch-api.honen.com/docs/6acd2e3a94f05db978608d0a

## Overview

The StudyFetch API allows you to generate various types of study materials using AI. You can:

- Generate outlines for structured learning
- Create comprehensive study notes
- Build topic overviews
- Generate concise summaries
- Customize content length and education level
- Organize generated materials into folders

## Generate Material

Generate study materials from a topic. This method returns immediately with the material ID while generation happens asynchronously.

JavaScript

```javascript
const material = await client.v1.materials.generate({
  name: 'Photosynthesis Study Notes',
  topic: 'Photosynthesis in plants',
  type: 'notes',
  context: 'Focus on light and dark reactions, chloroplast structure',
  level: 'college',
  length: 'medium',
  folderId: 'folder_123abc'
});

console.log('Material ID:', material._id);
console.log('Status:', material.status); // 'processing'
```

Python

```python
material = client.v1.materials.generate(
    name="Photosynthesis Study Notes",
    topic="Photosynthesis in plants",
    type="notes",
    context="Focus on light and dark reactions, chloroplast structure",
    level="college",
    length="medium",
    folder_id="folder_123abc"
)

print(f"Material ID: {material._id}")
print(f"Status: {material.status}")  # 'processing'
```

Java

```java
import com.studyfetch.javasdk.models.v1.materials.MaterialGenerateParams;
import com.studyfetch.javasdk.models.v1.materials.GeneratedMaterialResponse;

MaterialGenerateParams params = MaterialGenerateParams.builder()
    .name("Photosynthesis Study Notes")
    .topic("Photosynthesis in plants")
    .type(MaterialGenerateParams.Type.NOTES)
    .context("Focus on light and dark reactions, chloroplast structure")
    .level(MaterialGenerateParams.Level.COLLEGE)
    .length(MaterialGenerateParams.Length.MEDIUM)
    .folderId("folder_123abc")
    .build();

GeneratedMaterialResponse material = client.v1().materials().generate(params);

System.out.println("Material ID: " + material._id());
System.out.println("Status: " + material.status()); // 'processing'

// Access references if any were added
if (material.references().isPresent() && !material.references().get().isEmpty()) {
    System.out.println("References:");
    for (var reference : material.references().get()) {
        System.out.println("  - " + reference.title() + ": " + reference.url().orElse(""));
    }
}
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using System;
using System.Threading.Tasks;

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

        var material = await client.V1.Materials.Generate(new()
        {
            Name = "Photosynthesis Study Notes",
            Topic = "Photosynthesis in plants",
            Type = StudyfetchSDK.Models.V1.Materials.MaterialGenerateParamsProperties.Type.Notes,
            Context = "Focus on light and dark reactions, chloroplast structure",
            Level = StudyfetchSDK.Models.V1.Materials.MaterialGenerateParamsProperties.Level.College,
            Length = StudyfetchSDK.Models.V1.Materials.MaterialGenerateParamsProperties.Length.Medium,
            FolderID = "folder_123abc"
        });

        Console.WriteLine($"Material ID: {material._ID}");
        Console.WriteLine($"Status: {material.Status}"); // 'processing'
    }
}
```

### Generate Parameters

#### `name`

string · required

Name for the generated material

#### `topic`

string · required

Topic or context to generate material from

#### `type`

string · required

Type of material to generate:

- `outline` - Structured hierarchical breakdown
- `overview` - High-level introduction to topic
- `notes` - Detailed study notes with explanations
- `summary` - Concise summary of key points

#### `context`

string

Additional context or details about the topic

#### `folderId`

string

Target folder ID to organize the material

#### `length`

string · default: medium

Length of the generated content:

- `short` - Brief content (1-2 pages)
- `medium` - Standard length (3-5 pages)
- `long` - Comprehensive content (6+ pages)

#### `level`

string · default: college

Target education level:

- `high_school` - Basic concepts, simpler language
- `college` - Standard academic level
- `professional` - Advanced, technical content

### Response

```json
{
  "_id": "material_789ghi",
  "name": "Photosynthesis Study Notes",
  "type": "notes",
  "contentType": "generated",
  "topic": "Photosynthesis in plants",
  "generationType": "notes",
  "status": "processing",
  "content": null,
  "wordCount": 0,
  "organizationId": "org_456def",
  "folderId": "folder_123abc",
  "level": "college",
  "length": "medium",
  "references": [],
  "createdAt": "2024-01-20T14:30:00Z",
  "updatedAt": "2024-01-20T14:30:00Z"
}
```

## Generate and Process Material

Generate materials and wait for processing to complete. This method polls until the material is ready or times out.

JavaScript

```javascript
const material = await client.v1.materials.generateAndProcess({
  name: 'Complete Biology Notes',
  topic: 'Cell division and mitosis',
  type: 'notes',
  level: 'college',
  length: 'long',
  pollIntervalMs: 2000,  // Check every 2 seconds
  timeoutMs: 300000      // Wait up to 5 minutes
});

console.log('Material ready!');
console.log('Word count:', material.metadata);
console.log('Content preview:', material.content.text);
```

Python

```python
material = client.v1.materials.generate_and_process(
    name="Complete Biology Notes",
    topic="Cell division and mitosis",
    type="notes",
    level="college",
    length="long",
    poll_interval_ms=2000,  # Check every 2 seconds
    timeout_ms=300000       # Wait up to 5 minutes
)

print("Material ready!")
print(f"Word count: {material.word_count}")
print(f"Content preview: {material.content[:200]}")
```

Java

```java
import com.studyfetch.javasdk.models.v1.materials.MaterialGenerateAndProcessParams;

MaterialGenerateAndProcessParams params = 
    MaterialGenerateAndProcessParams.builder()
        .name("Complete Biology Notes")
        .topic("Cell division and mitosis")
        .type(MaterialGenerateAndProcessParams.Type.NOTES)
        .level(MaterialGenerateAndProcessParams.Level.COLLEGE)
        .length(MaterialGenerateAndProcessParams.Length.LONG)
        .pollIntervalMs(2000.0)  // Check every 2 seconds
        .timeoutMs(300000.0)     // Wait up to 5 minutes
        .build();

GeneratedMaterialResponse material = client.v1().materials()
    .generateAndProcess(params);

System.out.println("Material ready!");
System.out.println("Material ID: " + material._id());
System.out.println("Material Name: " + material.name());

// Access references if any were added
if (material.references().isPresent() && !material.references().get().isEmpty()) {
    System.out.println("References:");
    for (var reference : material.references().get()) {
        System.out.println("  - " + reference.title() + ": " + reference.url().orElse(""));
    }
}
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using System;
using System.Threading.Tasks;

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

        var material = await client.V1.Materials.GenerateAndProcess(new()
        {
            Name = "Complete Biology Notes",
            Topic = "Cell division and mitosis",
            Type = StudyfetchSDK.Models.V1.Materials.MaterialGenerateAndProcessParamsProperties.Type.Notes,
            Level = StudyfetchSDK.Models.V1.Materials.MaterialGenerateAndProcessParamsProperties.Level.College,
            Length = StudyfetchSDK.Models.V1.Materials.MaterialGenerateAndProcessParamsProperties.Length.Long,
            PollIntervalMs = 2000,  // Check every 2 seconds
            TimeoutMs = 300000      // Wait up to 5 minutes
        });

        Console.WriteLine("Material ready!");
        Console.WriteLine($"Material ID: {material._ID}");
        Console.WriteLine($"Material Name: {material.Name}");
    }
}
```

### Generate and Process Parameters

All parameters from `generate()` plus:

#### `pollIntervalMs`

integer · default: 2000

Polling interval in milliseconds to check if material is ready

#### `timeoutMs`

integer · default: 300000

Maximum time to wait for processing in milliseconds (default: 5 minutes)

### Response

```json
{
  "_id": "material_789ghi",
  "name": "Complete Biology Notes",
  "type": "notes",
  "contentType": "generated",
  "topic": "Cell division and mitosis",
  "generationType": "notes",
  "status": "completed",
  "content": "# Cell Division and Mitosis\n\n## Introduction\n\nCell division is a fundamental process...",
  "wordCount": 2450,
  "organizationId": "org_456def",
  "folderId": null,
  "level": "college",
  "length": "long",
  "references": [],
  "createdAt": "2024-01-20T14:30:00Z",
  "updatedAt": "2024-01-20T14:31:45Z"
}
```

## Complete Workflow Example

Here's a complete example of generating and organizing study materials for a course:

JavaScript

```javascript
// 1. Create a folder for the course
const courseFolder = await client.v1.folders.create({
  name: 'Biology 101 - Spring 2024'
});

// 2. Generate course outline
const outline = await client.v1.materials.generateAndProcess({
  name: 'Biology 101 Course Outline',
  topic: 'Introduction to Biology for first-year students',
  type: 'outline',
  folderId: courseFolder._id,
  level: 'college',
  length: 'long'
});

console.log('Course outline generated:', outline.name);

// 3. Generate chapter materials
const chapters = [
  { name: 'Cell Biology', topic: 'Cell structure, organelles, and functions' },
  { name: 'Genetics', topic: 'DNA, RNA, heredity, and gene expression' },
  { name: 'Evolution', topic: 'Natural selection, speciation, and evidence' }
];

const chapterMaterials = await Promise.all(
  chapters.map(chapter => 
    client.v1.materials.generate({
      name: `${chapter.name} - Study Notes`,
      topic: chapter.topic,
      type: 'notes',
      folderId: courseFolder._id,
      level: 'college',
      length: 'medium',
      context: 'Include diagrams descriptions and key terminology'
    })
  )
);

console.log(`Generated ${chapterMaterials.length} chapter materials`);

// 4. Generate quick summaries for review
const summaries = await Promise.all(
  chapters.map(chapter =>
    client.v1.materials.generateAndProcess({
      name: `${chapter.name} - Quick Review`,
      topic: chapter.topic,
      type: 'summary',
      folderId: courseFolder._id,
      level: 'college',
      length: 'short'
    })
  )
);

// 5. List all materials in the folder
const allMaterials = await client.v1.materials.list({
  folderId: courseFolder._id
});

console.log('\nCourse materials:');
allMaterials.materials?.forEach(material => {
  console.log(`- ${material.name} (${material.contentType})`);
});
```

Python

```python
# 1. Create a folder for the course
course_folder = client.v1.folders.create(
    name="Biology 101 - Spring 2024"
)

# 2. Generate course outline
outline = client.v1.materials.generate_and_process(
    name="Biology 101 Course Outline",
    topic="Introduction to Biology for first-year students",
    type="outline",
    folder_id=course_folder._id,
    level="college",
    length="long"
)

print(f"Course outline generated: {outline.name}")

# 3. Generate chapter materials
chapters = [
    {"name": "Cell Biology", "topic": "Cell structure, organelles, and functions"},
    {"name": "Genetics", "topic": "DNA, RNA, heredity, and gene expression"},
    {"name": "Evolution", "topic": "Natural selection, speciation, and evidence"}
]

chapter_materials = []
for chapter in chapters:
    material = client.v1.materials.generate(
        name=f"{chapter['name']} - Study Notes",
        topic=chapter["topic"],
        type="notes",
        folder_id=course_folder._id,
        level="college",
        length="medium",
        context="Include diagrams descriptions and key terminology"
    )
    chapter_materials.append(material)

print(f"Generated {len(chapter_materials)} chapter materials")

# 4. Generate quick summaries for review
summaries = []
for chapter in chapters:
    summary = client.v1.materials.generate_and_process(
        name=f"{chapter['name']} - Quick Review",
        topic=chapter["topic"],
        type="summary",
        folder_id=course_folder._id,
        level="college",
        length="short"
    )
    summaries.append(summary)

# 5. List all materials in the folder
all_materials = client.v1.materials.list(
    folder_id=course_folder._id
)

print("\nCourse materials:")
for material in all_materials:
    material_type = material.generation_type or material.content_type
    print(f"- {material.name} ({material_type})")
```

Java

```java
import com.studyfetch.javasdk.client.StudyfetchSdkClient;
import com.studyfetch.javasdk.client.okhttp.StudyfetchSdkOkHttpClient;
import com.studyfetch.javasdk.models.v1.materials.*;
import com.studyfetch.javasdk.models.v1.folders.*;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

// 1. Create a folder for the course
FolderCreateResponse courseFolder = client.v1().folders().create(
    FolderCreateParams.builder()
        .name("Biology 101 - Spring 2024")
        .build()
);

// 2. Generate course outline
MaterialGenerateAndProcessParams outlineParams = 
    MaterialGenerateAndProcessParams.builder()
        .name("Biology 101 Course Outline")
        .topic("Introduction to Biology for first-year students")
        .type(MaterialGenerateAndProcessParams.Type.OUTLINE)
        .folderId(courseFolder._id())
        .level(MaterialGenerateAndProcessParams.Level.COLLEGE)
        .length(MaterialGenerateAndProcessParams.Length.LONG)
        .build();

GeneratedMaterialResponse outline = client.v1().materials()
    .generateAndProcess(outlineParams);

System.out.println("Course outline generated: " + outline.name());

// 3. Generate chapter materials
class Chapter {
    String name;
    String topic;
    
    Chapter(String name, String topic) {
        this.name = name;
        this.topic = topic;
    }
}

List<Chapter> chapters = List.of(
    new Chapter("Cell Biology", "Cell structure, organelles, and functions"),
    new Chapter("Genetics", "DNA, RNA, heredity, and gene expression"),
    new Chapter("Evolution", "Natural selection, speciation, and evidence")
);

List<GeneratedMaterialResponse> chapterMaterials = new ArrayList<>();
for (Chapter chapter : chapters) {
    MaterialGenerateParams params = MaterialGenerateParams.builder()
        .name(chapter.name + " - Study Notes")
        .topic(chapter.topic)
        .type(MaterialGenerateParams.Type.NOTES)
        .folderId(courseFolder._id())
        .level(MaterialGenerateParams.Level.COLLEGE)
        .length(MaterialGenerateParams.Length.MEDIUM)
        .context("Include diagrams descriptions and key terminology")
        .build();
    
    GeneratedMaterialResponse material = client.v1().materials().generate(params);
    chapterMaterials.add(material);
}

System.out.println("Generated " + chapterMaterials.size() + " chapter materials");

// 4. Generate quick summaries for review
List<GeneratedMaterialResponse> summaries = new ArrayList<>();
for (Chapter chapter : chapters) {
    MaterialGenerateAndProcessParams summaryParams = 
        MaterialGenerateAndProcessParams.builder()
            .name(chapter.name + " - Quick Review")
            .topic(chapter.topic)
            .type(MaterialGenerateAndProcessParams.Type.SUMMARY)
            .folderId(courseFolder._id())
            .level(MaterialGenerateAndProcessParams.Level.COLLEGE)
            .length(MaterialGenerateAndProcessParams.Length.SHORT)
            .build();
    
    GeneratedMaterialResponse summary = client.v1().materials()
        .generateAndProcess(summaryParams);
    summaries.add(summary);
}

// 5. List all materials in the folder
MaterialListParams listParams = MaterialListParams.builder()
    .folderId(courseFolder._id())
    .build();
MaterialListResponse allMaterials = client.v1().materials().list(listParams);

System.out.println("\nCourse materials:");
for (MaterialResponse material : allMaterials.materials().orElse(Collections.emptyList())) {
    System.out.println("- " + material.name() + " (" + material.contentType() + ")");
}
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Folders;
using StudyfetchSDK.Models.V1.Materials;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

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

        // 1. Create a folder for the course
        var courseFolder = await client.V1.Folders.Create(new()
        {
            Name = "Biology 101 - Spring 2024"
        });

        // 2. Generate course outline
        var outline = await client.V1.Materials.GenerateAndProcess(new()
        {
            Name = "Biology 101 Course Outline",
            Topic = "Introduction to Biology for first-year students",
            Type = StudyfetchSDK.Models.V1.Materials.MaterialGenerateAndProcessParamsProperties.Type.Outline,
            FolderID = courseFolder._ID,
            Level = StudyfetchSDK.Models.V1.Materials.MaterialGenerateAndProcessParamsProperties.Level.College,
            Length = StudyfetchSDK.Models.V1.Materials.MaterialGenerateAndProcessParamsProperties.Length.Long
        });

        Console.WriteLine($"Course outline generated: {outline.Name}");

        // 3. Generate chapter materials
        var chapters = new[]
        {
            new { Name = "Cell Biology", Topic = "Cell structure, organelles, and functions" },
            new { Name = "Genetics", Topic = "DNA, RNA, heredity, and gene expression" },
            new { Name = "Evolution", Topic = "Natural selection, speciation, and evidence" }
        };

        var chapterMaterials = new List<MaterialGenerateResponse>();
        foreach (var chapter in chapters)
        {
            var material = await client.V1.Materials.Generate(new()
            {
                Name = $"{chapter.Name} - Study Notes",
                Topic = chapter.Topic,
                Type = StudyfetchSDK.Models.V1.Materials.MaterialGenerateParamsProperties.Type.Notes,
                FolderID = courseFolder._ID,
                Level = StudyfetchSDK.Models.V1.Materials.MaterialGenerateParamsProperties.Level.College,
                Length = StudyfetchSDK.Models.V1.Materials.MaterialGenerateParamsProperties.Length.Medium,
                Context = "Include diagrams descriptions and key terminology"
            });
            chapterMaterials.Add(material);
        }

        Console.WriteLine($"Generated {chapterMaterials.Count} chapter materials");

        // 4. Generate quick summaries for review
        var summaries = new List<MaterialGenerateAndProcessResponse>();
        foreach (var chapter in chapters)
        {
            var summary = await client.V1.Materials.GenerateAndProcess(new()
            {
                Name = $"{chapter.Name} - Quick Review",
                Topic = chapter.Topic,
                Type = StudyfetchSDK.Models.V1.Materials.MaterialGenerateAndProcessParamsProperties.Type.Summary,
                FolderID = courseFolder._ID,
                Level = StudyfetchSDK.Models.V1.Materials.MaterialGenerateAndProcessParamsProperties.Level.College,
                Length = StudyfetchSDK.Models.V1.Materials.MaterialGenerateAndProcessParamsProperties.Length.Short
            });
            summaries.Add(summary);
        }

        // 5. List all materials in the folder
        var allMaterials = await client.V1.Materials.List(new()
        {
            FolderID = courseFolder._ID
        });

        Console.WriteLine("\nCourse materials:");
        foreach (var material in allMaterials.Materials ?? [])
        {
            Console.WriteLine($"- {material.Name} ({material.ContentType})");
        }
    }
}
```

## Introduction

Source: https://studyfetch-api.honen.com/docs/6acd32039de34cae55b272a0

The StudyFetch API enables you to integrate AI-powered study tools directly into your applications. Create interactive learning experiences with our comprehensive suite of educational components.

## What You Can Build

With the StudyFetch API, you can:

- **AI Chat Assistants** - Create conversational AI tutors that understand your educational materials
- **Smart Flashcards** - Generate adaptive flashcard sets from any content
- **Practice Tests** - Build comprehensive assessments with multiple question types
- **Audio Summaries** - Convert study materials into engaging audio recaps
- **Visual Explainers** - Create animated video explanations of complex topics
- **Interactive Scenarios** - Design immersive learning experiences

## Core Features

### 🎯 Material Management

Upload and organize educational content including PDFs, documents, videos, and more. Our API automatically processes and indexes your materials for optimal learning experiences.

### 🤖 AI-Powered Components

Every component leverages advanced AI models to provide personalized, adaptive learning experiences tailored to each user's needs.

### 🔧 Flexible Integration

Use our SDKs for JavaScript, Python, Java, and C#, or integrate directly with our REST API. Embed components seamlessly into your web applications.

### 📊 Usage Analytics

Track engagement, performance, and learning outcomes with comprehensive analytics for all your components. Access detailed metrics through our Analytics API endpoints.

## API Architecture

The StudyFetch API is organized into several key services:

1. **Materials API** - Upload and manage educational content
2. **Components API** - Create and configure learning components
3. **Embedding API** - Generate secure embed URLs for web integration
4. **Analytics API** - Access usage data and learning insights

## Getting Started

### Authentication

Set up API keys and configure authentication

### Quickstart Guide

Create your first component in 5 minutes

### Learn Components

Learn how to create and configure components

### REST API

Complete REST API documentation

## Managing Materials

Source: https://studyfetch-api.honen.com/docs/6acd2e3a94f05db978608d05

## Overview

Once materials are uploaded, you can manage them by:

- Moving materials between folders
- Renaming materials
- Deleting materials
- Bulk operations
- Retrieving material details
- Downloading materials

## Get Material Details

Retrieve details about a specific material including its content, metadata, and processing status.

JavaScript

```javascript
const material = await client.v1.materials.retrieve('mat_123abc');

console.log('Material:', material.name);
console.log('Status:', material.status);
console.log('Content type:', material.contentType);
console.log('Created at:', material.createdAt);
console.log('Updated at:', material.updatedAt);
```

Python

```python
material = client.v1.materials.retrieve("mat_123abc")

print(f"Material: {material.name}")
print(f"Status: {material.status}")
print(f"Content type: {material.content_type}")
print(f"Word count: {material.metadata.word_count}")
print(f"Images: {len(material.metadata.images)}")
```

Java

```java
import com.studyfetch.javasdk.models.v1.materials.MaterialResponse;

import com.studyfetch.javasdk.models.v1.materials.MaterialRetrieveParams;

MaterialResponse material = client.v1().materials().retrieve(
    MaterialRetrieveParams.builder()
        .id("mat_123abc")
        .build()
);

System.out.println("Material: " + material.name());
System.out.println("Status: " + material.status());
System.out.println("Content type: " + material.contentType());
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using System;
using System.Threading.Tasks;

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

        var material = await client.V1.Materials.Retrieve(new()
        {
            ID = "mat_123abc"
        });

        Console.WriteLine($"Material: {material.Name}");
        Console.WriteLine($"Status: {material.Status}");
        Console.WriteLine($"Content type: {material.ContentType}");
        Console.WriteLine($"Created at: {material.CreatedAt}");
        Console.WriteLine($"Updated at: {material.UpdatedAt}");
    }
}
```

## List All Materials

Get all materials in your organization, optionally filtered by folder.

JavaScript

```javascript
// Get all materials with pagination
const allMaterials = await client.v1.materials.list({
  limit: '50',
  page: '1'
});

// Get materials in a specific folder with pagination
const folderMaterials = await client.v1.materials.list({
  folderId: 'folder_123',
  limit: '20',
  page: '1'
});

console.log(`Found ${allMaterials.materials?.length || 0} materials`);
console.log(`Total materials: ${allMaterials.totalCount}`);
console.log(`Page ${allMaterials.page} of ${allMaterials.totalPages}`);
```

Python

```python
# Get all materials with pagination
all_materials = client.v1.materials.list(
    limit="50",
    page="1"
)

# Get materials in a specific folder with pagination
folder_materials = client.v1.materials.list(
    folder_id="folder_123",
    limit="20",
    page="1"
)

print(f"Found {len(all_materials.data)} materials")
print(f"Total materials: {all_materials.total}")
print(f"Page {all_materials.page} of {all_materials.total_pages}")
```

Java

```java
import com.studyfetch.javasdk.models.v1.materials.MaterialListParams;
import java.util.List;

// Get all materials with pagination
MaterialListParams allParams = MaterialListParams.builder()
    .limit("50")
    .page("1")
    .build();
MaterialListResponse allMaterials = client.v1().materials().list(allParams);

// Get materials in a specific folder with pagination
MaterialListParams folderParams = MaterialListParams.builder()
    .folderId("folder_123")
    .limit("20")
    .page("1")
    .build();
MaterialListResponse folderMaterials = client.v1().materials().list(folderParams);

System.out.println("Page " + allMaterials.page() + " of " + allMaterials.totalPages());
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using System;
using System.Threading.Tasks;

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

        // Get all materials with pagination
        var allMaterials = await client.V1.Materials.List(new()
        {
            Limit = "50",
            Page = "1"
        });

        // Get materials in a specific folder with pagination
        var folderMaterials = await client.V1.Materials.List(new()
        {
            FolderID = "folder_123",
            Limit = "20",
            Page = "1"
        });

        Console.WriteLine($"Found {allMaterials.Materials?.Count ?? 0} materials");
        Console.WriteLine($"Total materials: {allMaterials.TotalCount}");
        Console.WriteLine($"Page {allMaterials.Page} of {allMaterials.TotalPages}");
    }
}
```

## Move Material to Folder

Move a material to a different folder or to the root level.

JavaScript

```javascript
// Move to a folder
const movedMaterial = await client.v1.materials.move('mat_123abc', {
  folderId: 'folder_456def'
});

// Move to root (no folder)
const rootMaterial = await client.v1.materials.move('mat_123abc', {
  folderId: null
});

console.log('Material moved to:', movedMaterial.folderId || 'root');
```

Python

```python
# Move to a folder
moved_material = client.v1.materials.move(
    material_id="mat_123abc",
    folder_id="folder_456def"
)

# Move to root (no folder)
root_material = client.v1.materials.move(
    material_id="mat_123abc",
    folder_id=None
)

print(f"Material moved to: {moved_material.folder_id or 'root'}")
```

Java

```java
// The move method signature from the API docs shows it takes just an ID
// To move a material, you would typically:
// 1. Call move endpoint with material ID
// 2. Specify folder ID in request body

client.v1().materials().move(MaterialMoveParams.builder().id("mat_123abc").build());

System.out.println("Material moved successfully");
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using System;
using System.Threading.Tasks;

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

        // Move to a folder
        var movedMaterial = await client.V1.Materials.Move(new()
        {
            ID = "mat_123abc",
            FolderID = "folder_456def"
        });

        // Move to root (no folder)
        var rootMaterial = await client.V1.Materials.Move(new()
        {
            ID = "mat_123abc",
            FolderID = null
        });

        Console.WriteLine($"Material moved to: {movedMaterial.FolderID ?? "root"}");
    }
}
```

## Rename Material

Change the name of an existing material.

JavaScript

```javascript
const renamedMaterial = await client.v1.materials.rename('mat_123abc', {
  name: 'Biology Chapter 2 - Updated'
});

console.log('Material renamed to:', renamedMaterial.name);
```

Python

```python
renamed_material = client.v1.materials.rename(
    material_id="mat_123abc",
    name="Biology Chapter 2 - Updated"
)

print(f"Material renamed to: {renamed_material.name}")
```

Java

```java
import com.studyfetch.javasdk.models.v1.materials.MaterialRenameParams;

// The rename method takes a params object with builder
client.v1().materials().rename(
    MaterialRenameParams.builder()
        .id("mat_123abc")
        .name("New Material Name")
        .build()
);

System.out.println("Material renamed successfully");
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using System;
using System.Threading.Tasks;

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

        var renamedMaterial = await client.V1.Materials.Rename(new()
        {
            ID = "mat_123abc",
            Name = "Biology Chapter 2 - Updated"
        });

        Console.WriteLine($"Material renamed to: {renamedMaterial.Name}");
    }
}
```

## Update Material Reference

Update the reference information (title and URL) for an existing material. This is useful for adding or updating citation information.

JavaScript

```javascript
const updatedMaterial = await client.v1.materials.update('mat_123abc', {
  references: [
    {
      title: 'Introduction to Biology, 3rd Edition',
      url: 'https://example.com/biology-textbook'
    }
  ]
});

console.log('Material updated:', updatedMaterial._id);
```

Python

```python
updated_material = client.v1.materials.update(
    material_id="mat_123abc",
    references=[
        {
            "title": "Introduction to Biology, 3rd Edition",
            "url": "https://example.com/biology-textbook"
        }
    ]
)

print(f"Material updated: {updated_material._id}")
```

Java

```java
import com.studyfetch.javasdk.models.v1.materials.MaterialUpdateParams;
import com.studyfetch.javasdk.models.v1.materials.Reference;
import java.util.List;

MaterialResponse updatedMaterial = client.v1().materials().update(
    MaterialUpdateParams.builder()
        .id("mat_123abc")
        .references(List.of(
            Reference.builder()
                .title("Introduction to Biology, 3rd Edition")
                .url("https://example.com/biology-textbook")
                .build()
        ))
        .build()
);

System.out.println("Material updated: " + updatedMaterial._id());
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using StudyfetchSDK.Models.V1.Materials.MaterialUpdateParamsProperties;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

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

        // Update material references
        var updatedMaterial = await client.V1.Materials.Update(new()
        {
            ID = "mat_123abc",
            References = new List<StudyfetchSDK.Models.V1.Materials.MaterialUpdateParamsProperties.Reference>
            {
                new() { Title = "Updated Reference", URL = "https://example.com/updated" },
                new() { Title = "New Reference", URL = "https://example.com/new" }
            }
        });

        Console.WriteLine($"Material updated: {updatedMaterial._ID}");

        // Alternative: Create a new material with references
        var materialWithReferences = await client.V1.Materials.Create(new()
        {
            Name = "Biology Material with References",
            Content = new()
            {
                Type = StudyfetchSDK.Models.V1.Materials.ContentProperties.Type.Text,
                Text = "Material content with source citations"
            },
            References = new List<StudyfetchSDK.Models.V1.Materials.Reference>
            {
                new("Introduction to Biology, 3rd Edition")
                {
                    URL = "https://example.com/biology-textbook"
                }
            }
        });

        Console.WriteLine($"Material created with references: {materialWithReferences._ID}");
    }
}
```

## Delete Material

Permanently delete a material. This is a soft delete - the material is marked as deleted but retained for audit purposes.

JavaScript

```javascript
await client.v1.materials.delete('mat_123abc');

console.log('Material deleted successfully');
```

Python

```python
client.v1.materials.delete("mat_123abc")

print("Material deleted successfully")
```

Java

```java
import com.studyfetch.javasdk.models.v1.materials.MaterialDeleteParams;

// Delete material by ID
client.v1().materials().delete(
    MaterialDeleteParams.builder()
        .id("mat_123abc")
        .build()
);

System.out.println("Material deleted successfully");
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using System;
using System.Threading.Tasks;

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

        await client.V1.Materials.Delete(new()
        {
            ID = "mat_123abc"
        });

        Console.WriteLine("Material deleted successfully");
    }
}
```

## Bulk Move Materials

Move multiple materials to a folder in a single operation.

JavaScript

```javascript
const materialIds = ['mat_123abc', 'mat_456def', 'mat_789ghi'];

const result = await client.v1.materials.bulk.move({
  materialIds,
  folderId: 'folder_999xyz'
});

console.log(`Moved ${result.movedCount} materials`);
```

Python

```python
material_ids = ["mat_123abc", "mat_456def", "mat_789ghi"]

result = client.v1.materials.bulk.move({
    'materialIds': material_ids,
    'folderId': "folder_999xyz"
})

print(f"Moved {result['movedCount']} materials")
```

Java

```java
import com.studyfetch.javasdk.models.v1.materials.bulk.BulkMoveParams;
import java.util.Arrays;
import java.util.List;

List<String> materialIds = Arrays.asList(
    "mat_123abc", 
    "mat_456def", 
    "mat_789ghi"
);

BulkMoveParams params = BulkMoveParams.builder()
    .materialIds(materialIds)
    .folderId("folder_999xyz")
    .build();

client.v1().materials().bulk().move(params);

System.out.println("Materials moved successfully");
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials.Bulk;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

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

        var materialIds = new List<string> { "mat_123abc", "mat_456def", "mat_789ghi" };

        var result = await client.V1.Materials.Bulk.Move(new()
        {
            MaterialIDs = materialIds,
            FolderID = "folder_999xyz"
        });

        Console.WriteLine($"Moved {result.MovedCount} materials");
    }
}
```

## Download Material

Get a temporary download URL for a material stored in S3.

JavaScript

```javascript
// Get download URL (expires in 1 hour by default)
const { downloadUrl } = await client.v1.materials.getDownloadURL('mat_123abc', {
  expiresIn: 3600
});

// Get download URL with custom expiry (in seconds)
const { downloadUrl: customUrl } = await client.v1.materials.getDownloadURL('mat_123abc', {
  expiresIn: 7200 // 2 hours
});

console.log('Download URL:', downloadUrl);
```

Python

```python
# Get download URL (expires in 1 hour by default)
download_info = client.v1.materials.get_download_url("mat_123abc", {
    'expiresIn': '3600'
})

# Get download URL with custom expiry (in seconds)
custom_info = client.v1.materials.get_download_url("mat_123abc", {
    'expiresIn': '7200'  # 2 hours
})

print(f"Download URL: {download_info['downloadUrl']}")
```

Java

```java
import com.studyfetch.javasdk.models.v1.materials.MaterialGetDownloadUrlParams;

// Get download URL (expires in 1 hour by default)
MaterialGetDownloadUrlParams params = MaterialGetDownloadUrlParams.builder()
    .id("mat_123abc")
    .expiresIn(3600)
    .build();
client.v1().materials().getDownloadUrl(params);

// Get download URL with custom expiry (in seconds)
MaterialGetDownloadUrlParams customParams = MaterialGetDownloadUrlParams.builder()
    .id("mat_123abc")
    .expiresIn(7200) // 2 hours
    .build();
client.v1().materials().getDownloadUrl(customParams);

System.out.println("Download URL retrieved successfully");
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using System;
using System.Threading.Tasks;

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

        // Get download URL (expires in 1 hour by default)
        var downloadInfo = await client.V1.Materials.GetDownloadURL(new()
        {
            ID = "mat_123abc"
        });

        // Get download URL with custom expiry (in seconds)
        var customInfo = await client.V1.Materials.GetDownloadURL(new()
        {
            ID = "mat_123abc"
        });

        Console.WriteLine($"Download URL: {downloadInfo.DownloadURL}");
    }
}
```

## Example: Complete Material Management Flow

Here's a complete example showing the typical lifecycle of material management:

JavaScript

```javascript
// 1. Upload a material
const fileBuffer = await fs.promises.readFile(
  path.join(process.cwd(), 'biology-notes.pdf'),
);
const file = new File([fileBuffer], 'biology-notes.pdf', {
  type: 'application/pdf',
});

const material = await client.v1.materials.upload.uploadFile({
  file,
  name: 'Biology Notes v1'
});
console.log('Uploaded:', material._id);

// 2. Wait for processing
let status = 'processing';
while (status === 'processing') {
  await new Promise(resolve => setTimeout(resolve, 2000));
  const updated = await client.v1.materials.retrieve(material._id);
  status = updated.status;
}

// 3. Rename the material
const renamed = await client.v1.materials.rename(material._id, {
  name: 'Biology Notes - Final Version'
});

// 4. Move to a folder
const moved = await client.v1.materials.move(material._id, {
  folderId: 'folder_biology'
});

// 5. Get download URL
const { downloadUrl } = await client.v1.materials.getDownloadURL(material._id, {
  expiresIn: 3600
});
console.log('Download from:', downloadUrl);

// 6. Eventually delete
// await client.v1.materials.delete(material._id);
```

Python

```python
import time

# 1. Upload a material
with open('biology-notes.pdf', 'rb') as file:
    material = client.v1.materials.upload(
        file=file,
        name="Biology Notes v1"
    )
print(f"Uploaded: {material._id}")

# 2. Wait for processing
status = "processing"
while status == "processing":
    time.sleep(2)
    updated = client.v1.materials.retrieve(material._id)
    status = updated.status

# 3. Rename the material
renamed = client.v1.materials.rename(
    material_id=material._id,
    name="Biology Notes - Final Version"
)

# 4. Move to a folder
moved = client.v1.materials.move(
    material_id=material._id,
    folder_id="folder_biology"
)

# 5. Get download URL
download_info = client.v1.materials.get_download_url(material._id, {
    'expiresIn': '3600'
})
print(f"Download from: {download_info['downloadUrl']}")

# 6. Eventually delete
# client.v1.materials.delete(material._id)
```

Java

```java
import com.studyfetch.javasdk.models.v1.materials.upload.UploadUploadFileParams;
import com.studyfetch.javasdk.models.v1.materials.MaterialResponse;
import com.studyfetch.javasdk.models.v1.materials.MaterialRetrieveParams;
import com.studyfetch.javasdk.models.v1.materials.MaterialRenameParams;
import com.studyfetch.javasdk.models.v1.materials.MaterialMoveParams;
import com.studyfetch.javasdk.models.v1.materials.MaterialGetDownloadUrlParams;
import com.studyfetch.javasdk.models.v1.materials.MaterialDeleteParams;
import java.io.File;
import java.io.FileInputStream;

// 1. Upload a material
File file = new File("biology-notes.pdf");
UploadUploadFileParams uploadParams = UploadUploadFileParams.builder()
    .file(new FileInputStream(file))
    .name("Biology Notes v1")
    .build();
MaterialResponse material = client.v1().materials().upload().uploadFile(uploadParams);
System.out.println("Uploaded: " + material._id());

// 2. Wait for processing
MaterialResponse.Status status = MaterialResponse.Status.PROCESSING;
while (status.equals(MaterialResponse.Status.PROCESSING)) {
    Thread.sleep(2000);
    MaterialResponse updated = client.v1().materials().retrieve(
        MaterialRetrieveParams.builder()
            .id(material._id())
            .build()
    );
    status = updated.status();
}

// 3. Rename the material
client.v1().materials().rename(
    MaterialRenameParams.builder()
        .id(material._id())
        .name("Biology Notes v2")
        .build()
);

// 4. Move to a folder
client.v1().materials().move(
    MaterialMoveParams.builder()
        .id(material._id())
        .folderId("folder_456def")
        .build()
);

// 5. Get download URL
MaterialGetDownloadUrlParams downloadParams = MaterialGetDownloadUrlParams.builder()
    .id(material._id())
    .expiresIn(3600)
    .build();
client.v1().materials().getDownloadUrl(downloadParams);

// 6. Eventually delete
// client.v1().materials().delete(
//     MaterialDeleteParams.builder()
//         .id(material._id())
//         .build()
// );
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using System;
using System.IO;
using System.Threading.Tasks;

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

        // 1. Upload a material
        var fileBytes = await File.ReadAllBytesAsync("biology-notes.pdf");
        var material = await client.V1.Materials.Upload.UploadFile(new()
        {
            File = fileBytes,
            FileName = "biology-notes.pdf",
            Name = "Biology Notes v1"
        });
        Console.WriteLine($"Uploaded: {material._ID}");

        // 2. Wait for processing
        var status = "processing";
        while (status == "processing")
        {
            await Task.Delay(2000);
            var updated = await client.V1.Materials.Retrieve(new()
            {
                ID = material._ID
            });
            status = updated.Status.Raw();
        }

        // 3. Rename the material
        var renamed = await client.V1.Materials.Rename(new()
        {
            ID = material._ID,
            Name = "Biology Notes - Final Version"
        });

        // 4. Move to a folder
        var moved = await client.V1.Materials.Move(new()
        {
            ID = material._ID,
            FolderID = "folder_biology"
        });

        // 5. Get download URL
        var downloadInfo = await client.V1.Materials.GetDownloadURL(new()
        {
            ID = material._ID
        });
        Console.WriteLine($"Download from: {downloadInfo.DownloadURL}");

        // 6. Eventually delete
        // await client.V1.Materials.Delete(new() { ID = material._ID });
    }
}
```

## Materials API Overview

Source: https://studyfetch-api.honen.com/docs/6acd2e3994f05db978608d00

## Overview

The Materials API allows you to upload, manage, and organize various types of study materials including documents, videos, audio files, and more. Materials can be organized into folders and serve as the foundation for generating study components like flashcards, quizzes, and practice tests.

## Supported File Types

The Materials API supports the following content types:

- **Documents**: PDF, EPUB, TXT
- **Videos**: MP4, AVI, MOV, MKV
- **Audio**: MP3, WAV, M4A
- **Images**: JPG, PNG, GIF
- **Text**: Plain text content
- **URLs**: Web content fetching

## Material Object

```json
{
  "_id": "mat_123abc",
  "name": "Biology Chapter 1",
  "organizationId": "org_456def",
  "folderId": "folder_789ghi",
  "contentType": "pdf",
  "content": {
    "s3Key": "materials/org_456def/file.pdf",
    "s3Url": "https://s3.amazonaws.com/...",
    "filename": "biology-ch1.pdf",
    "fileSize": 2048576,
    "mimeType": "application/pdf"
  },
  "metadata": {
    "title": "Biology Chapter 1",
    "wordCount": 5420,
    "pageCount": 12,
    "embeddingCount": 45,
    "chunks": 15,
    "extractedText": "...",
    "images": [
      {
        "id": "img_001",
        "s3Key": "materials/org_456def/images/img_001.jpg",
        "s3Url": "https://s3.amazonaws.com/...",
        "description": "Cell structure diagram"
      }
    ]
  },
  "references": [
    {
      "title": "Biology Textbook - 5th Edition",
      "url": "https://example.com/biology-textbook"
    }
  ],
  "status": "active",
  "createdAt": "2024-01-15T10:00:00Z",
  "updatedAt": "2024-01-15T10:05:00Z"
}
```

## Status Values

Materials go through different status values during their lifecycle:

- `pending_upload` - Waiting for file upload (S3 presigned URL)
- `processing` - Being processed (OCR, transcription, embeddings)
- `active` - Ready to use
- `error` - Processing failed
- `deleted` - Soft deleted

Warning

**Important**: Materials must have `status: 'active'` before they can be used in components or for grading. Attempting to use materials with `status: 'processing'` will result in errors. Always implement polling to check material status after upload.

## Next Steps

### Upload Files

Learn how to upload different file types

### Manage Materials

Move, rename, and delete materials

### Work with Folders

Organize materials into folders

### Search Materials

Search materials using RAG

## PDF Generator

Source: https://studyfetch-api.honen.com/docs/6acd32349de34cae55b272d6

## Overview

The PDF Generator service allows you to create PDF presentations on any topic. This is useful for:

- Generating educational presentation slides
- Creating topic-specific presentations
- Producing multilingual presentations
- Building quick presentation outlines

## Create PDF Presentation

Generate a new PDF presentation on a specific topic.

JavaScript

```javascript
import StudyfetchSDK from '@studyfetch/sdk';

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

const pdfResponse = await client.v1.pdfGenerator.create({
  topic: 'Biology Chapter 1 Summary',
  numberOfSlides: 10,
  locale: 'en'
});

console.log('PDF created:', pdfResponse.presentation?._id);
console.log('Success:', pdfResponse.success);
```

Python

```python
from studyfetch_sdk import StudyfetchSDK

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

pdf_response = client.v1.pdf_generator.create({
    "topic": "Biology Chapter 1 Summary",
    "number_of_slides": 10,
    "locale": "en"
})

print(f"PDF created: {pdf_response.presentation._id if pdf_response.presentation else 'None'}")
print(f"Success: {pdf_response.success}")
```

Java

```java
import com.studyfetch.javasdk.client.StudyfetchSdkClient;
import com.studyfetch.javasdk.client.okhttp.StudyfetchSdkOkHttpClient;
import com.studyfetch.javasdk.models.v1.pdfgenerator.PdfGeneratorCreateParams;
import com.studyfetch.javasdk.models.v1.pdfgenerator.PdfGeneratorCreateResponse;
import java.util.List;

StudyfetchSdkClient client = StudyfetchSdkOkHttpClient.builder()
    .apiKey(System.getenv("STUDYFETCH_API_KEY"))
    .baseUrl("https://studyfetchapi.com")
    .build();

PdfGeneratorCreateParams params = PdfGeneratorCreateParams.builder()
    .topic("Biology Chapter 1 Summary")
    .numberOfSlides(10)
    .locale("en")
    .build();

PdfGeneratorCreateResponse pdfResponse = client.v1().pdfGenerator().create(params);

System.out.println("PDF created: " + pdfResponse.presentation()._id());
System.out.println("Success: " + pdfResponse.success());
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.PdfGenerator;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

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

        var pdfResponse = await client.V1.PdfGenerator.Create(new()
        {
            Topic = "Biology Chapter 1 Summary",
            NumberOfSlides = 10,
            Locale = "en"
        });

        Console.WriteLine($"PDF created: {pdfResponse.Presentation?._ID}");
        Console.WriteLine($"Success: {pdfResponse.Success}");
    }
}
```

## Get All PDFs

Retrieve a list of all generated PDFs.

JavaScript

```javascript
const pdfs = await client.v1.pdfGenerator.getAll();

pdfs.forEach(pdf => {
  console.log(`${pdf.topic} - ${pdf.createdAt}`);
  console.log(`ID: ${pdf._id}`);
  console.log(`Slides: ${pdf.numberOfSlides}`);
});
```

Python

```python
pdfs = client.v1.pdf_generator.get_all()

for pdf in pdfs:
    print(f"{pdf.topic} - {pdf.created_at}")
    print(f"ID: {pdf._id}")
    print(f"Slides: {pdf.number_of_slides}")
```

Java

```java
import com.studyfetch.javasdk.models.v1.pdfgenerator.PdfResponse;
import java.util.List;

List<PdfResponse> pdfs = client.v1().pdfGenerator().getAll();

for (PdfResponse pdf : pdfs) {
    System.out.println(pdf.topic() + " - " + pdf.createdAt());
    System.out.println("ID: " + pdf._id());
    System.out.println("Slides: " + pdf.numberOfSlides());
}
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.PdfGenerator;
using StudyfetchSDK.Models.V1.PdfGenerator.Get;
using System;
using System.Threading.Tasks;

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

        var pdfs = await client.V1.PdfGenerator.GetAll();

        foreach (var pdf in pdfs)
        {
            Console.WriteLine($"{pdf.Topic} - {pdf.CreatedAt}");
            Console.WriteLine($"ID: {pdf._ID}");
            Console.WriteLine($"Slides: {pdf.NumberOfSlides}");
        }
    }
}
```

## Get PDF by ID

Retrieve details of a specific PDF presentation.

JavaScript

```javascript
const pdfId = 'pdf_123abc';
const pdf = await client.v1.pdfGenerator.getById({
  id: pdfId
});

console.log('Topic:', pdf.topic);
console.log('Created:', pdf.createdAt);
console.log('ID:', pdf._id);
console.log('Slides:', pdf.numberOfSlides);
console.log('Locale:', pdf.locale);
```

Python

```python
pdf_id = "pdf_123abc"
pdf = client.v1.pdf_generator.get_by_id({
    "id": pdf_id
})

print(f"Topic: {pdf.topic}")
print(f"Created: {pdf.created_at}")
print(f"ID: {pdf._id}")
print(f"Slides: {pdf.number_of_slides}")
print(f"Locale: {pdf.locale}")
```

Java

```java
import com.studyfetch.javasdk.models.v1.pdfgenerator.PdfGeneratorGetByIdParams;
import com.studyfetch.javasdk.models.v1.pdfgenerator.PdfResponse;

String pdfId = "pdf_123abc";
PdfGeneratorGetByIdParams params = PdfGeneratorGetByIdParams.builder()
    .id(pdfId)
    .build();

PdfResponse pdf = client.v1().pdfGenerator().getById(params);

System.out.println("Topic: " + pdf.topic());
System.out.println("Created: " + pdf.createdAt());
System.out.println("ID: " + pdf._id());
System.out.println("Slides: " + pdf.numberOfSlides());
System.out.println("Locale: " + pdf.locale());
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.PdfGenerator;
using StudyfetchSDK.Models.V1.PdfGenerator.Get;
using System;
using System.Threading.Tasks;

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

        string pdfId = "pdf_123abc";
        var pdf = await client.V1.PdfGenerator.GetByID(new()
        {
            ID = pdfId
        });

        Console.WriteLine($"Topic: {pdf.Topic}");
        Console.WriteLine($"Created: {pdf.CreatedAt}");
        Console.WriteLine($"ID: {pdf._ID}");
        Console.WriteLine($"Slides: {pdf.NumberOfSlides}");
        Console.WriteLine($"Locale: {pdf.Locale}");
    }
}
```

## Delete PDF

Delete a PDF presentation.

JavaScript

```javascript
const pdfId = 'pdf_123abc';
const response = await client.v1.pdfGenerator.delete({
  id: pdfId
});

console.log('PDF deleted:', response.success);
```

Python

```python
pdf_id = "pdf_123abc"
response = client.v1.pdf_generator.delete({
    "id": pdf_id
})

print(f"PDF deleted: {response.success}")
```

Java

```java
import com.studyfetch.javasdk.models.v1.pdfgenerator.PdfGeneratorDeleteParams;
import com.studyfetch.javasdk.models.v1.pdfgenerator.PdfGeneratorDeleteResponse;

String pdfId = "pdf_123abc";
PdfGeneratorDeleteParams params = PdfGeneratorDeleteParams.builder()
    .id(pdfId)
    .build();

PdfGeneratorDeleteResponse response = client.v1().pdfGenerator().delete(params);

System.out.println("PDF deleted: " + response.success());
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.PdfGenerator;
using System;
using System.Threading.Tasks;

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

        string pdfId = "pdf_123abc";
        var response = await client.V1.PdfGenerator.Delete(new()
        {
            ID = pdfId
        });

        Console.WriteLine($"PDF deleted: {response.Success}");
    }
}
```

## Complete Example

Here's a complete example that creates a PDF presentation and retrieves its details:

JavaScript

```javascript
import StudyfetchSDK from '@studyfetch/sdk';
import fs from 'fs';
import https from 'https';

async function createAndCheckPdf() {
  const client = new StudyfetchSDK({
    apiKey: process.env.STUDYFETCH_API_KEY,
    baseURL: 'https://studyfetchapi.com',
  });

  // 1. Create PDF presentation
  const pdfResponse = await client.v1.pdfGenerator.create({
    topic: 'Complete Biology Study Guide',
    numberOfSlides: 15,
    locale: 'en'
  });

  console.log('PDF created:', pdfResponse.presentation?._id);
  console.log('Success:', pdfResponse.success);

  // 2. Check the created presentation
  if (pdfResponse.presentation) {
    const pdf = await client.v1.pdfGenerator.getById({ 
      id: pdfResponse.presentation._id 
    });
    
    console.log('Presentation details:');
    console.log('- Topic:', pdf.topic);
    console.log('- Slides:', pdf.numberOfSlides);
    console.log('- Locale:', pdf.locale);
  }
}

createAndCheckPdf();
```

Python

```python
import time
import requests
from studyfetch_sdk import StudyfetchSDK

def create_and_check_pdf():
    client = StudyfetchSDK(
        api_key=os.environ.get("STUDYFETCH_API_KEY"),
        base_url="https://studyfetchapi.com",
    )

    # 1. Create PDF presentation
    pdf_response = client.v1.pdf_generator.create({
        "topic": "Complete Biology Study Guide",
        "number_of_slides": 15,
        "locale": "en"
    })

    print(f"PDF created: {pdf_response.id}")

    # 2. Wait for processing to complete
    pdf = client.v1.pdf_generator.get_by_id({"id": pdf_response.id})
    while pdf.status == "processing":
        time.sleep(2)
        pdf = client.v1.pdf_generator.get_by_id({"id": pdf_response.id})

    if pdf.status == "completed":
        print(f"PDF ready for download: {pdf.download_url}")
        
        # 3. Download the PDF
        response = requests.get(pdf.download_url)
        with open("study-guide.pdf", "wb") as f:
            f.write(response.content)
        print("PDF downloaded successfully")
    else:
        print("PDF generation failed")

create_and_check_pdf()
```

Java

```java
import com.studyfetch.javasdk.client.StudyfetchSdkClient;
import com.studyfetch.javasdk.client.okhttp.StudyfetchSdkOkHttpClient;
import com.studyfetch.javasdk.models.v1.pdfgenerator.*;
import java.io.*;
import java.net.URL;
import java.nio.file.*;
import java.util.List;

public class CreateAndCheckPdf {
    public static void main(String[] args) throws Exception {
        StudyfetchSdkClient client = StudyfetchSdkOkHttpClient.builder()
            .apiKey(System.getenv("STUDYFETCH_API_KEY"))
            .baseUrl("https://studyfetchapi.com")
            .build();

        // 1. Create PDF presentation
        PdfGeneratorCreateParams createParams = PdfGeneratorCreateParams.builder()
            .topic("Complete Biology Study Guide")
            .numberOfSlides(15)
            .locale("en")
            .build();

        PdfGeneratorCreateResponse pdfResponse = client.v1().pdfGenerator().create(createParams);
        System.out.println("PDF created: " + pdfResponse.id());

        // 2. Wait for processing to complete
        PdfResponse pdf = client.v1().pdfGenerator().getById(
            PdfGeneratorGetByIdParams.builder().id(pdfResponse.id()).build()
        );
        
        while ("processing".equals(pdf.status())) {
            Thread.sleep(2000);
            pdf = client.v1().pdfGenerator().getById(
                PdfGeneratorGetByIdParams.builder().id(pdfResponse.id()).build()
            );
        }

        if ("completed".equals(pdf.status())) {
            System.out.println("PDF ready for download: " + pdf.downloadUrl());
            
            // 3. Download the PDF
            try (InputStream in = new URL(pdf.downloadUrl()).openStream()) {
                Files.copy(in, Paths.get("study-guide.pdf"), 
                    StandardCopyOption.REPLACE_EXISTING);
            }
            System.out.println("PDF downloaded successfully");
        } else {
            System.err.println("PDF generation failed");
        }
    }
}
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.PdfGenerator;
using StudyfetchSDK.Models.V1.PdfGenerator.Get;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

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

        // 1. Create PDF presentation
        var pdfResponse = await client.V1.PdfGenerator.Create(new()
        {
            Topic = "Complete Biology Study Guide",
            NumberOfSlides = 15,
            Locale = "en"
        });

        Console.WriteLine($"PDF created: {pdfResponse.Presentation?._ID}");

        // 2. Check the created presentation
        if (pdfResponse.Presentation != null)
        {
            var pdf = await client.V1.PdfGenerator.GetByID(new() { 
                ID = pdfResponse.Presentation._ID 
            });
            
            Console.WriteLine("Presentation details:");
            Console.WriteLine($"- Topic: {pdf.Topic}");
            Console.WriteLine($"- Slides: {pdf.NumberOfSlides}");
            Console.WriteLine($"- Locale: {pdf.Locale}");
        }
    }
}
```

## Practice Test

Source: https://studyfetch-api.honen.com/docs/6acd32029de34cae55b2729b

## Overview

The Practice Test component creates comprehensive tests from your study materials with multiple question types, difficulty levels, and customizable settings. Perfect for exam preparation and knowledge assessment.

Questions are generated in the background after the component is created. Learners take the test in the embed, which shows generation progress while questions are being written and auto-submits when the time limit runs out.

## Creating a Practice Test Component

JavaScript

```javascript
import StudyfetchSDK from '@studyfetch/sdk';

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

const testComponent = await client.v1.components.create({
  name: 'Biology Midterm Practice',
  type: 'practice_test',
  config: {
    materials: ['mat-123', 'mat-456'],
    folders: ['folder-789'],
    questionTypes: ['multiplechoice', 'truefalse', 'shortanswer'],
    questionsPerTest: 25,
    difficulty: 'medium',
    timeLimit: 45,
    randomizeQuestions: true,
    passingScore: 75,
  },
});

console.log('Practice test component created:', testComponent._id, testComponent.componentId);
```

Python

```python
from studyfetch_sdk import StudyfetchSDK

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

test_component = client.v1.components.create(
    name="Biology Midterm Practice",
    type="practice_test",
    config={
        "materials": ["mat-123", "mat-456"],
        "folders": ["folder-789"],
        "question_types": ["multiplechoice", "truefalse", "shortanswer"],
        "questions_per_test": 25,
        "difficulty": "medium",
        "time_limit": 45,
        "randomize_questions": True,
        "passing_score": 75,
    },
)

print(f"Practice test component created: {test_component.api_id} {test_component.component_id}")
```

Java

```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 CreatePracticeTestComponent {
    public static void main(String[] args) {
        StudyfetchSdkClient client = StudyfetchSdkOkHttpClient.builder()
            .fromEnv()
            .baseUrl("https://studyfetchapi.com")
            .build();

        ComponentCreateParams params = ComponentCreateParams.builder()
            .name("Biology Midterm Practice")
            .type(ComponentCreateParams.Type.PRACTICE_TEST)
            .config(ComponentCreateParams.Config.PracticeTestConfigDto.builder()
                .materials(List.of("mat-123", "mat-456"))
                .folders(List.of("folder-789"))
                .questionTypes(List.of("multiplechoice", "truefalse", "shortanswer"))
                .questionsPerTest(25)
                .difficulty(ComponentCreateParams.Config.PracticeTestConfigDto.Difficulty.MEDIUM)
                .timeLimit(45)
                .randomizeQuestions(true)
                .passingScore(75)
                .build())
            .build();

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

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Components;
using StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.ConfigProperties.PracticeTestConfigDtoProperties;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

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

        var testComponent = await client.V1.Components.Create(new()
        {
            Name = "Biology Midterm Practice",
            Type = StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.Type.PracticeTest,
            Config = new StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.ConfigProperties.PracticeTestConfigDto()
            {
                Materials = new List<string> { "mat-123", "mat-456" },
                Folders = new List<string> { "folder-789" },
                QuestionTypes = new List<string> { "multiplechoice", "truefalse", "shortanswer" },
                QuestionsPerTest = 25,
                Difficulty = Difficulty.Medium,
                TimeLimit = 45,
                RandomizeQuestions = true,
                PassingScore = 75,
            }
        });

        Console.WriteLine($"Practice test component created: {testComponent._ID}");
    }
}
```

## Configuration Parameters

#### `name`

string · required

Name of the practice test component

#### `type`

string · required

Must be `"practice_test"`

#### `config`

object · required

Practice test configuration object. `questionTypes` is required, and you need at least one material or folder. Unknown keys are rejected with `400`.

### Configuration Properties

These properties go inside `config`.

#### `questionTypes`

array · required

Types of questions to include. At least one:

- `multiplechoice` - Multiple choice questions
- `truefalse` - True/false questions
- `shortanswer` - Short answer questions
- `frq` - Free-response questions
- `fillinblank` - Fill in the blank questions

Values are case-insensitive and underscores are ignored, so `multiple_choice` also works. Questions are split evenly across the types you choose.

#### `materials`

array

Material IDs to generate questions from. Provide `materials`, `folders`, or both.

#### `folders`

array

Folder IDs whose materials questions are generated from

#### `questionsPerTest`

number · default: 10

Number of questions per test

#### `difficulty`

string

Test difficulty level: `easy`, `medium`, `hard`, or `mixed`. When omitted, questions are generated at mixed difficulty.

#### `timeLimit`

number · default: 30

Time limit in minutes. The embed auto-submits the test when time runs out. There is no "no limit" option; `0` is treated as the default.

#### `passingScore`

number · default: 70

Minimum passing percentage (0-100) used to show a pass or fail result. `0` is treated as the default.

#### `randomizeQuestions`

boolean · default: false

Shuffle question order for each attempt

#### `model`

string · default: gpt-4o-mini-2024-07-18

AI model used to generate questions

#### `allowRetakes`

boolean

Accepted and stored, but not currently enforced; learners can always retake a test.

#### `maxAttempts`

number

Accepted and stored, but not currently enforced

#### `showCorrectAnswers`

boolean

Accepted and stored, but not currently used

#### `showExplanations`

boolean

Accepted and stored, but not currently used. Graded answers include an explanation when one is available.

#### `randomizeAnswers`

boolean

Accepted and stored, but not currently used

#### `aiGenerationMode`

string

`balanced`, `comprehensive`, `focused`, or `adaptive`. Accepted and stored, but not currently used.

#### `questionDistribution`

object

Per-type question counts (`multiplechoice`, `truefalse`, `shortanswer`, `frq`, `fillinblank`). Accepted and stored, but not currently used; questions are split evenly across `questionTypes`.

## Response

```json
{
  "_id": "6650f1c2a9b3e4d5f6a7b8c9",
  "componentId": "practice_test_V1StGXR8_Z",
  "name": "Biology Midterm Practice",
  "type": "practice_test",
  "status": "active",
  "origin": "api",
  "organization": "64f0a1b2c3d4e5f6a7b8c9d0",
  "materials": ["mat-123", "mat-456"],
  "folders": ["folder-789"],
  "config": {
    "materials": ["mat-123", "mat-456"],
    "folders": ["folder-789"],
    "questionTypes": ["multiplechoice", "truefalse", "shortanswer"],
    "questionsPerTest": 25,
    "difficulty": "medium",
    "timeLimit": 45,
    "randomizeQuestions": true,
    "passingScore": 75
  },
  "usageCount": 0,
  "createdAt": "2026-10-06T10:00:00.000Z",
  "updatedAt": "2026-10-06T10:00:00.000Z"
}
```

## Embedding This Component

Once you've created a Practice Test component, you can embed it on your website using the embedding API. The component must be `active`. Attempts and results are stored per `userId`, so generate each learner's embed with their own `userId`.

### Generate Embed URL

JavaScript

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

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

console.log(embedResponse.embedUrl);
```

Python

```python
embed_response = client.v1.components.generate_embed(
    test_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",
    width="100%",
    height="700px",
)

print(embed_response.embed_url)
```

Java

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

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

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

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

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

C#

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

public class GeneratePracticeTestEmbed
{
    public static async Task GenerateEmbed(string testComponentId)
    {
        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 = testComponentId,
            UserID = "user-456",
            StudentName = "Jane Smith",  // Student name for display
            GroupIDs = new List<string> { "class-101", "class-102" },
            SessionID = "session-789",
            Width = "100%",
            Height = "700px"
        });

        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`).

The practice test embed doesn't use any `features` settings, and always shows the learner's past attempts.

Warning

**Embed tokens are valid for 1 hour.** `expiryHours` only changes the `expiresAt` value in the response; the token itself always expires 1 hour after it is issued, and the embed does not renew it. If learners keep the page open longer, load the iframe with a fresh embed URL. See Chat for the token refresh endpoint.

### 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.

```html
<iframe
  src="EMBED_URL_FROM_RESPONSE"
  width="100%"
  height="700px"
  frameborder="0"
  style="border: 1px solid #e5e5e5; border-radius: 8px;">
</iframe>
```

## Taking Tests from Your Server

To run tests in your own interface, send actions to `POST /api/v1/components/{id}/interact`, where `{id}` is the component's `_id`:

| `action` | Body fields | Returns |
| --- | --- | --- |
| `create_test` | `userId` | A new attempt: `testId`, `test`, and `questions`. While questions are still being generated, `generating: true` with `progress` instead. |
| `get_test` | `testId` | The attempt and its questions |
| `submit_answer` | `testId`, `questionId`, `answer`, `userId` | Whether the answer is `correct`, its `grade`, and an `explanation` |
| `submit_test` | `testId`, `answers` (array of `questionId` and `answer`), `userId` | `grade`, `correctAnswers`, `totalQuestions`, and per-question `results` |
| `get_results` | `testId` | Results of a submitted attempt |
| `retake_test` | `testId` | A fresh attempt at the same questions |
| `list_tests` | `userId` | The user's attempts |

The SDKs' `interact` methods don't send a request body yet, so use cURL or an HTTP client.

```bash
curl -X POST "https://studyfetchapi.com/api/v1/components/6650f1c2a9b3e4d5f6a7b8c9/interact" \
  -H "x-api-key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{ "action": "create_test", "userId": "user-456" }'
```

## React Native

The React Native SDK (`@studyfetch/react-native-sdk`) renders Practice Tests natively with embed tokens instead of an iframe. Import it from `@studyfetch/react-native-sdk/tests` and wrap your app in `EmbedTokenProvider` with a `getToken` function that asks your backend to mint an embed token for a component.

```tsx
import { EmbedTokenProvider } from '@studyfetch/react-native-sdk';
import { StudyFetchTest, useTest } from '@studyfetch/react-native-sdk/tests';

// Full test UI: questions, submission, results
<EmbedTokenProvider getToken={fetchToken}>
  <StudyFetchTest componentId="practice_test_V1StGXR8_Z" userId="user-456" />
</EmbedTokenProvider>

// Headless: build your own UI
const { questions, answers, setAnswer, submit, result, passed, retake, generating } = useTest({
  componentId: 'practice_test_V1StGXR8_Z',
  userId: 'user-456',
});
```

- `useTest` starts an attempt automatically and polls while questions are being generated.
- `passed` compares the result with the component's `passingScore`.

## Quickstart

Source: https://studyfetch-api.honen.com/docs/6acd32029de34cae55b27295

Get up and running with StudyFetch in just 5 minutes. You'll learn how to:

1. **Create or Upload Material**: Create text-based materials or upload files (PDFs, documents, etc.) to build your knowledge base
2. **Create Component**: Create an AI component (chat, flashcards, tests, etc.) that uses your materials
3. **Embed Component**: Generate an embed URL and add it to your website with a simple iframe

## Prerequisites

Before you begin, make sure you have:

- An API key ([get one from your console](https://console.studyfetch.com))
- Your preferred development environment set up:
  - **JavaScript**: Node.js 16+ installed
  - **Python**: Python 3.7+ installed
  - **Java**: Java 8+ and Maven/Gradle
  - **C#**: .NET 6.0+ installed

Note

**Material Processing Time**: When you upload or create materials, they need time to process before they can be used in components. Attempting to create a component immediately after uploading materials will fail. Always check that materials have `status: 'active'` before using them. The examples below include proper waiting logic.

## Language-Specific Quickstart

### JavaScript

#### Install the SDK

```bash
npm install @studyfetch/sdk
```

#### Initialize the Client

```javascript
import StudyfetchSDK from '@studyfetch/sdk';
import fs from 'fs';
import path from 'path';

const client = new StudyfetchSDK({
  apiKey: process.env.STUDYFETCH_API_KEY,
  baseURL: 'https://studyfetchapi.com',
});
```

#### Step 1: Create or Upload Study Materials

```javascript
// Option 1: Create a text-based material
const textMaterial = await client.v1.materials.create({
  content: {
    type: 'text',
    text: 'Chapter 1: Introduction to Biology\n\nBiology is the study of life...'
  },
  name: 'Biology Chapter 1 - Introduction',
  folderId: 'optional-folder-id', // Optional: organize in folders
  references: [
    {
      title: 'Biology Textbook Chapter 1',
      url: 'https://example.com/biology-ch1'
    }
  ] // Optional: source references
});

// Option 2: Upload a PDF file
const fileBuffer = await fs.promises.readFile(
  path.join(process.cwd(), 'biology-textbook.pdf'),
);
const file = new File([fileBuffer], 'biology-textbook.pdf', {
  type: 'application/pdf',
});

const uploadedMaterial = await client.v1.materials.upload.uploadFile({
  file,
  name: 'Biology Textbook',
  folderId: 'optional-folder-id' // Optional: organize in folders
});

// Option 3: Upload material from a URL
const urlMaterial = await client.v1.materials.upload.uploadFromURL({
  url: 'https://example.com/biology-notes.pdf',
  name: 'Online Biology Notes',
  folderId: 'optional-folder-id' // Optional: organize in folders
});

console.log('Materials created:', {
  text: textMaterial._id,
  uploaded: uploadedMaterial._id,
  url: urlMaterial._id
});
```

#### Step 2: Create a Chat Component

Warning

**Important**: Materials need time to process after upload. If you create a material and immediately try to use it in a component, the component creation will fail. Always ensure materials have `status: 'active'` before using them. For production applications, implement a polling mechanism to check material status.

```javascript
// Wait for materials to finish processing
// In production, implement proper polling with exponential backoff
let materialStatus = 'processing';
while (materialStatus !== 'active') {
  const material = await client.v1.materials.retrieve(textMaterial._id);
  materialStatus = material.status;
  if (materialStatus === 'error') {
    throw new Error('Material processing failed');
  }
  if (materialStatus === 'processing') {
    await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds
  }
}

const chatComponent = await client.v1.components.create({
  name: 'Biology Study Assistant',
  type: 'chat',
  config: {
    model: 'gpt-4o-mini-2024-07-18',
    materials: [textMaterial._id, uploadedMaterial._id], // Use any material IDs
    temperature: 0.7,
    enableWebSearch: true
  }
});

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

#### Step 3: Embed the Chat Component

```javascript
// Basic embed with minimal configuration
const basicEmbed = await client.v1.components.generateEmbed(chatComponent._id, {
  userId: 'student-123'
});

// Advanced embed with all available options
const advancedEmbed = await client.v1.components.generateEmbed(chatComponent._id, {
  // User tracking (optional)
  userId: 'student-123',
  studentName: 'John Doe',              // Student name for display
  groupIds: ['class-101', 'biology-spring-2024'],
  
  // Display dimensions
  width: '100%',
  height: '600px',
  
  // Chat-specific features
  features: {
    enableWebSearch: true,        // Allow users to search the web
    enableHistory: true,          // View and continue previous conversations
    enableVoice: true,           // Voice input with microphone
    enableFollowUps: true,       // AI-suggested follow-up questions
    enableComponentCreation: true, // AI can create flashcards, tests, etc.
    placeholderText: 'Ask your Biology tutor anything...',
    enableBadWordsFilter: true
  }
});

console.log('Basic Embed URL:', basicEmbed.embedUrl);
console.log('Advanced Embed URL:', advancedEmbed.embedUrl);

// Note: Theme and branding are configured at the organization level
// StudyFetch branding is automatically hidden for all embeds
```

#### Complete Example

```javascript
import StudyfetchSDK from '@studyfetch/sdk';
import fs from 'fs';

async function createStudyAssistant() {
  const client = new StudyfetchSDK({
    apiKey: process.env.STUDYFETCH_API_KEY,
    baseURL: 'https://studyfetchapi.com',
  });

  // 1. Create or upload materials
  const materials = [];
  
  // Create text material
  const textMaterial = await client.v1.materials.create({
    content: {
      type: 'text',
      text: 'Course introduction and key concepts...'
    },
    name: 'Course Introduction',
    references: [
      {
        title: 'Course Syllabus',
        url: 'https://example.com/syllabus'
      }
    ] // Optional: source references
  });
  materials.push(textMaterial._id);
  
  // Upload PDF file
  const fileBuffer = await fs.promises.readFile(
    path.join(process.cwd(), 'course-content.pdf'),
  );
  const file = new File([fileBuffer], 'course-content.pdf', {
    type: 'application/pdf',
  });
  
  const uploadedMaterial = await client.v1.materials.upload.uploadFile({
    file,
    name: 'Course Materials',
  });
  materials.push(uploadedMaterial._id);

  // IMPORTANT: Wait for materials to finish processing
  // Materials need time to process. Using them immediately will cause component creation to fail.
  for (const materialId of materials) {
    let materialStatus = 'processing';
    while (materialStatus !== 'active') {
      const material = await client.v1.materials.retrieve(materialId);
      materialStatus = material.status;
      if (materialStatus === 'error') {
        throw new Error(`Material ${materialId} processing failed`);
      }
      if (materialStatus === 'processing') {
        await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds
      }
    }
  }

  // 2. Create chat assistant
  const chatComponent = await client.v1.components.create({
    name: 'Course Assistant',
    type: 'chat',
    config: {
      model: 'gpt-4o-mini-2024-07-18',
      materials: materials,
      temperature: 0.7,
      enableWebSearch: true
    }
  });

  // 3. Generate embed URL
  const embed = await client.v1.components.generateEmbed(chatComponent._id, {
    userId: 'student-123',
    studentName: 'John Doe',    // Student name for display
    groupIds: ['course-101'],
    width: '100%',
    height: '600px',
    features: {
      enableWebSearch: true,
      enableHistory: true,
      enableVoice: true,
      enableFollowUps: true,
      enableComponentCreation: true,
      placeholderText: 'Ask me anything about the course materials...',
      enableBadWordsFilter: true
    }
  });

  console.log('Study assistant created!');
  console.log('Embed URL:', embed.embedUrl);
  console.log('Component ID:', chatComponent._id);
  
  return { chatComponent, embed };
}

createStudyAssistant();
```

### Python

#### Install the SDK

```bash
pip install studyfetch-sdk
```

#### Initialize the Client

```python
import os
from studyfetch_sdk import StudyfetchSDK

client = StudyfetchSDK(
    api_key=os.environ.get("STUDYFETCH_API_KEY"),
    base_url="https://studyfetchapi.com",
)
```

#### Step 1: Create or Upload Study Materials

```python
# Option 1: Create a text-based material
text_material = client.v1.materials.create(
    content={
        "type": "text",
        "text": "Chapter 1: Introduction to Biology\n\nBiology is the study of life..."
    },
    name="Biology Chapter 1 - Introduction",
    folder_id="optional-folder-id",  # Optional: organize in folders
    references=[
        {
            "title": "Biology Textbook Chapter 1",
            "url": "https://example.com/biology-ch1"
        }
    ]  # Optional: source references
)

# Option 2: Upload a PDF file
with open('biology-textbook.pdf', 'rb') as f:
    uploaded_material = client.v1.materials.upload.upload_file(
        file=f.read(),
        name="Biology Textbook",
        folder_id="optional-folder-id"  # Optional: organize in folders
    )

# Option 3: Upload material from a URL
url_material = client.v1.materials.upload.upload_from_url(
    url="https://example.com/biology-notes.pdf",
    name="Online Biology Notes",
    folder_id="optional-folder-id"  # Optional: organize in folders
)

print("Materials created:", {
    "text": text_material.api_id,
    "uploaded": uploaded_material.api_id,
    "url": url_material.api_id
})
```

#### Step 2: Create a Chat Component

Warning

**Important**: Materials need time to process after upload. If you create a material and immediately try to use it in a component, the component creation will fail. Always ensure materials have `status: 'active'` before using them. For production applications, implement a polling mechanism to check material status.

```python
import time

# Wait for materials to finish processing
# In production, implement proper polling with exponential backoff
material_status = "processing"
while material_status != "active":
    material = client.v1.materials.retrieve(text_material.api_id)
    material_status = material.status
    if material_status == "error":
        raise Exception("Material processing failed")
    if material_status == "processing":
        time.sleep(2)  # Wait 2 seconds

chat_component = client.v1.components.create(
    name="Biology Study Assistant",
    type="chat",
    config={
        "model": "gpt-4o-mini-2024-07-18",
        "materials": [text_material.api_id, uploaded_material.api_id],  # Use any material IDs
        "temperature": 0.7,
        "enable_web_search": True
    }
)

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

#### Step 3: Embed the Chat Component

```python
# Basic embed with minimal configuration
basic_embed = client.v1.components.generate_embed(
    chat_component.api_id,
    user_id="student-123"
)

# Advanced embed with all available options
advanced_embed = client.v1.components.generate_embed(
    chat_component.api_id,
    # User tracking (optional)
    user_id="student-123",
    student_name="John Doe",              # Student name for display
    group_ids=["class-101", "biology-spring-2024"],
    
    # Display dimensions
    width="100%",
    height="600px",
    
    # Chat-specific features
    features={
        "enable_bad_words_filter": True, # Required by the SDK type
        "enable_web_search": True,       # Allow users to search the web
        "enable_history": True,          # View and continue previous conversations
        "enable_voice": True,            # Voice input with microphone
        "enable_follow_ups": True,       # AI-suggested follow-up questions
        "enable_component_creation": True, # AI can create flashcards, tests, etc.
        "placeholder_text": "Ask your Biology tutor anything..."
    }
)

print(f"Basic Embed URL: {basic_embed.embed_url}")
print(f"Advanced Embed URL: {advanced_embed.embed_url}")

# Note: Theme and branding are configured at the organization level
# StudyFetch branding is automatically hidden for all embeds
```

#### Complete Example

```python
import os
from studyfetch_sdk import StudyfetchSDK

def create_study_assistant():
    client = StudyfetchSDK(
        api_key=os.environ.get("STUDYFETCH_API_KEY"),
        base_url="https://studyfetchapi.com",
    )

    # 1. Create or upload materials
    materials = []
    
    # Create text material
    text_material = client.v1.materials.create(
        content={
            "type": "text",
            "text": "Course introduction and key concepts..."
        },
        name="Course Introduction",
        references=[
            {
                "title": "Course Syllabus",
                "url": "https://example.com/syllabus"
            }
        ]  # Optional: source references
    )
    materials.append(text_material.api_id)
    
    # Upload PDF file
    with open('course-content.pdf', 'rb') as f:
        uploaded_material = client.v1.materials.upload.upload_file(
            file=f.read(),
            name="Course Materials"
        )
    materials.append(uploaded_material.api_id)

    # IMPORTANT: Wait for materials to finish processing
    # Materials need time to process. Using them immediately will cause component creation to fail.
    import time
    for material_id in materials:
        material_status = "processing"
        while material_status != "active":
            material = client.v1.materials.retrieve(material_id)
            material_status = material.status
            if material_status == "error":
                raise Exception(f"Material {material_id} processing failed")
            if material_status == "processing":
                time.sleep(2)  # Wait 2 seconds

    # 2. Create chat assistant
    chat_component = client.v1.components.create(
        name="Course Assistant",
        type="chat",
        config={
            "model": "gpt-4o-mini-2024-07-18",
            "materials": materials,
            "temperature": 0.7,
            "enable_web_search": True
        }
    )

    # 3. Generate embed URL
    embed = client.v1.components.generate_embed(
        chat_component.api_id,
        user_id="student-123",
        student_name="John Doe",    # Student name for display
        group_ids=["course-101"],
        width="100%",
        height="600px",
        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": True,
            "placeholder_text": "Ask me anything about the course materials..."
        }
    )

    print("Study assistant created!")
    print(f"Embed URL: {embed.embed_url}")
    print(f"Component ID: {chat_component.api_id}")
    
    return chat_component, embed

create_study_assistant()
```

### Java

#### Install the SDK

Maven:

```xml
<dependency>
  <groupId>com.studyfetch.javasdk</groupId>
  <artifactId>studyfetch-sdk</artifactId>
  <version>0.1.0-alpha.33</version>
</dependency>
```

Gradle:

```gradle
implementation group: 'com.studyfetch.javasdk', name: 'studyfetch-sdk', version: '0.1.0-alpha.33'
```

#### Initialize the Client

```java
import com.studyfetch.javasdk.client.StudyfetchSdkClient;
import com.studyfetch.javasdk.client.okhttp.StudyfetchSdkOkHttpClient;

StudyfetchSdkClient client = StudyfetchSdkOkHttpClient.builder()
    .apiKey(System.getenv("STUDYFETCH_API_KEY"))
    .baseUrl("https://studyfetchapi.com")
    .build();
```

#### Step 1: Create or Upload Study Materials

```java
import com.studyfetch.javasdk.models.v1.materials.MaterialResponse;
import com.studyfetch.javasdk.models.v1.materials.MaterialCreateParams;
import com.studyfetch.javasdk.models.v1.materials.Content;
import com.studyfetch.javasdk.models.v1.materials.Reference;
import com.studyfetch.javasdk.models.v1.materials.upload.UploadUploadFileParams;
import com.studyfetch.javasdk.models.v1.materials.upload.UploadUploadFromUrlParams;
import java.io.FileInputStream;
import java.util.List;

// Option 1: Create a text-based material
MaterialCreateParams createParams = MaterialCreateParams.builder()
    .content(Content.builder()
        .type(Content.Type.TEXT)
        .text("Chapter 1: Introduction to Biology\n\nBiology is the study of life...")
        .build())
    .name("Biology Chapter 1 - Introduction")
    .folderId("optional-folder-id") // Optional: organize in folders
    .references(List.of(
        Reference.builder()
            .title("Biology Textbook Chapter 1")
            .url("https://example.com/biology-ch1")
            .build()
    )) // Optional: source references
    .build();
MaterialResponse textMaterial = client.v1().materials().create(createParams);

// Option 2: Upload a PDF file
FileInputStream fileStream = new FileInputStream("biology-textbook.pdf");
UploadUploadFileParams uploadParams = UploadUploadFileParams.builder()
    .file(fileStream)
    .name("Biology Textbook")
    .folderId("optional-folder-id") // Optional: organize in folders
    .build();
MaterialResponse uploadedMaterial = client.v1().materials().upload().uploadFile(uploadParams);

// Option 3: Upload material from a URL
UploadUploadFromUrlParams urlParams = UploadUploadFromUrlParams.builder()
    .url("https://example.com/biology-notes.pdf")
    .name("Online Biology Notes")
    .folderId("optional-folder-id") // Optional: organize in folders
    .build();
MaterialResponse urlMaterial = client.v1().materials().upload().uploadFromUrl(urlParams);

System.out.println("Materials created:");
System.out.println("Text: " + textMaterial._id());
System.out.println("Uploaded: " + uploadedMaterial._id());
System.out.println("URL: " + urlMaterial._id());
```

#### Step 2: Create a Chat Component

```java
import com.studyfetch.javasdk.models.v1.components.ComponentResponse;
import com.studyfetch.javasdk.models.v1.components.ComponentCreateParams;

ComponentCreateParams chatParams = ComponentCreateParams.builder()
    .name("Biology Study Assistant")
    .type(ComponentCreateParams.Type.CHAT)
    .config(ComponentCreateParams.Config.ChatConfigDto.builder()
        .model("gpt-4o-mini-2024-07-18")
        .materials(List.of(textMaterial._id(), uploadedMaterial._id())) // Use any material IDs
        .temperature(0.7)
        .enableWebSearch(true)
        .build())
    .build();
    
ComponentResponse chatComponent = client.v1().components().create(chatParams);
System.out.println("Chat component created: " + chatComponent._id());
```

#### Step 3: Embed the Chat Component

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

// Basic embed with minimal configuration
ComponentGenerateEmbedParams basicEmbedParams = ComponentGenerateEmbedParams.builder()
    .id(chatComponent._id())
    .userId("student-123")
    .build();
    
ComponentGenerateEmbedResponse basicEmbed = client.v1().components()
    .generateEmbed(basicEmbedParams);

// Advanced embed with all available options
ComponentGenerateEmbedParams advancedEmbedParams = ComponentGenerateEmbedParams.builder()
    .id(chatComponent._id())
    // User tracking (optional)
    .userId("student-123")
    .studentName("John Doe")              // Student name for display
    .groupIds(List.of("class-101", "biology-spring-2024"))
    
    // Display dimensions
    .width("100%")
    .height("600px")
    
    // Chat-specific features
    .features(ComponentGenerateEmbedParams.Features.builder()
        .enableBadWordsFilter(true)   // Required: build() throws without it
        .enableWebSearch(true)        // Allow users to search the web
        .enableHistory(true)          // View and continue previous conversations
        .enableVoice(true)           // Voice input with microphone
        .enableFollowUps(true)       // AI-suggested follow-up questions
        .enableComponentCreation(true) // AI can create flashcards, tests, etc.
        .placeholderText("Ask your Biology tutor anything...")
        .build())
    .build();
    
ComponentGenerateEmbedResponse advancedEmbed = client.v1().components()
    .generateEmbed(advancedEmbedParams);
    
System.out.println("Basic Embed URL: " + basicEmbed.embedUrl());
System.out.println("Advanced Embed URL: " + advancedEmbed.embedUrl());

// Note: Theme and branding are configured at the organization level
// StudyFetch branding is automatically hidden for all embeds
```

#### Complete Example

```java
package com.example;

import com.studyfetch.javasdk.client.StudyfetchSdkClient;
import com.studyfetch.javasdk.client.okhttp.StudyfetchSdkOkHttpClient;
import com.studyfetch.javasdk.models.v1.materials.MaterialResponse;
import com.studyfetch.javasdk.models.v1.materials.MaterialCreateParams;
import com.studyfetch.javasdk.models.v1.materials.MaterialRetrieveParams;
import com.studyfetch.javasdk.models.v1.materials.Content;
import com.studyfetch.javasdk.models.v1.materials.Reference;
import com.studyfetch.javasdk.models.v1.materials.upload.UploadUploadFileParams;
import com.studyfetch.javasdk.models.v1.components.ComponentResponse;
import com.studyfetch.javasdk.models.v1.components.ComponentCreateParams;
import com.studyfetch.javasdk.models.v1.components.ComponentGenerateEmbedParams;
import com.studyfetch.javasdk.models.v1.components.ComponentGenerateEmbedResponse;
import java.io.FileInputStream;
import java.util.List;
import java.util.ArrayList;

public class StudyAssistant {
    public static void main(String[] args) throws Exception {
                StudyfetchSdkClient client = StudyfetchSdkOkHttpClient.builder()
        .apiKey(System.getenv("STUDYFETCH_API_KEY"))
        .baseUrl("https://studyfetchapi.com")
        .build();

        // 1. Create or upload materials
        List<String> materials = new ArrayList<>();
        
        // Create text material
        MaterialCreateParams createParams = MaterialCreateParams.builder()
            .content(Content.builder()
                .type(Content.Type.TEXT)
                .text("Course introduction and key concepts...")
                .build())
            .name("Course Introduction")
            .references(List.of(
                Reference.builder()
                    .title("Course Syllabus")
                    .url("https://example.com/syllabus")
                    .build()
            )) // Optional: source references
            .build();
        MaterialResponse textMaterial = client.v1().materials().create(createParams);
        materials.add(textMaterial._id());
        
        // Upload PDF file
        FileInputStream fileStream = new FileInputStream("course-content.pdf");
        UploadUploadFileParams uploadParams = UploadUploadFileParams.builder()
            .file(fileStream)
            .name("Course Materials")
            .build();
        MaterialResponse uploadedMaterial = client.v1().materials().upload().uploadFile(uploadParams);
        materials.add(uploadedMaterial._id());

        // IMPORTANT: Wait for materials to finish processing
        // Materials need time to process. Using them immediately will cause component creation to fail.
        for (String materialId : materials) {
            MaterialResponse.Status status = MaterialResponse.Status.PROCESSING;
            while (status.equals(MaterialResponse.Status.PROCESSING)) {
                Thread.sleep(2000); // Wait 2 seconds
                MaterialResponse material = client.v1().materials().retrieve(
                    MaterialRetrieveParams.builder()
                        .id(materialId)
                        .build()
                );
                status = material.status();
                if (status.equals(MaterialResponse.Status.ERROR)) {
                    throw new Exception("Material processing failed");
                }
            }
        }

        // 2. Create chat assistant
        ComponentCreateParams chatParams = ComponentCreateParams.builder()
            .name("Course Assistant")
            .type(ComponentCreateParams.Type.CHAT)
            .config(ComponentCreateParams.Config.ChatConfigDto.builder()
                .model("gpt-4o-mini-2024-07-18")
                .materials(materials)
                .temperature(0.7)
                .enableWebSearch(true)
                .build())
            .build();
        ComponentResponse chatComponent = client.v1().components().create(chatParams);

        // 3. Generate embed URL
        ComponentGenerateEmbedParams embedParams = ComponentGenerateEmbedParams.builder()
            .id(chatComponent._id())
            .userId("student-123")
            .studentName("John Doe")    // Student name for display
            .groupIds(List.of("course-101"))
            .width("100%")
            .height("600px")
            .features(ComponentGenerateEmbedParams.Features.builder()
                .enableBadWordsFilter(true) // Required: build() throws without it
                .enableWebSearch(true)
                .enableHistory(true)
                .enableVoice(true)
                .enableFollowUps(true)
                .enableComponentCreation(true)
                .placeholderText("Ask me anything about the course materials...")
                .build())
            .build();
        ComponentGenerateEmbedResponse embed = client.v1().components()
            .generateEmbed(embedParams);

        System.out.println("Study assistant created!");
        System.out.println("Embed URL: " + embed.embedUrl());
        System.out.println("Component ID: " + chatComponent._id());
    }
}
```

### C#

#### Install the SDK

```bash
dotnet add package StudyfetchSDK --version 0.8.1
```

#### Initialize the Client

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using StudyfetchSDK.Models.V1.Materials.Upload;
using StudyfetchSDK.Models.V1.Components;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

var client = new StudyfetchSDKClient()
{
    APIKey = Environment.GetEnvironmentVariable("STUDYFETCH_API_KEY"),
    BaseUrl = new Uri("https://studyfetchapi.com")
};
```

#### Step 1: Create or Upload Study Materials

```csharp
// Option 1: Create a text-based material
var textMaterial = await client.V1.Materials.Create(new()
{
    Content = new()
    {
        Type = StudyfetchSDK.Models.V1.Materials.ContentProperties.Type.Text,
        Text = "Chapter 1: Introduction to Biology\n\nBiology is the study of life..."
    },
    Name = "Biology Chapter 1 - Introduction",
    FolderID = "optional-folder-id", // Optional: organize in folders
    References = new List<StudyfetchSDK.Models.V1.Materials.Reference>
    {
        new("Biology Textbook Chapter 1")
        {
            URL = "https://example.com/biology-ch1"
        }
    } // Optional: source references
});

// Option 2: Upload a PDF file
var fileBytes = await File.ReadAllBytesAsync("biology-textbook.pdf");
var uploadedMaterial = await client.V1.Materials.Upload.UploadFile(new()
{
    File = fileBytes,
    FileName = "biology-textbook.pdf",
    Name = "Biology Textbook",
    FolderID = "optional-folder-id" // Optional: organize in folders
});

// Option 3: Upload material from a URL
var urlMaterial = await client.V1.Materials.Upload.UploadFromURL(new()
{
    URL = "https://example.com/biology-notes.pdf",
    Name = "Online Biology Notes",
    FolderID = "optional-folder-id" // Optional: organize in folders
});

Console.WriteLine("Materials created:");
Console.WriteLine($"Text: {textMaterial._ID}");
Console.WriteLine($"Uploaded: {uploadedMaterial._ID}");
Console.WriteLine($"URL: {urlMaterial._ID}");
```

#### Step 2: Create a Chat Component

```csharp
// Wait for materials to finish processing
// In production, implement proper polling with exponential backoff
var materialStatus = "processing";
while (materialStatus != "active")
{
    var material = await client.V1.Materials.Retrieve(new()
    {
        ID = textMaterial._ID
    });
    materialStatus = material.Status.Raw();
    if (materialStatus == "error")
        throw new Exception("Material processing failed");
    if (materialStatus == "processing")
        await Task.Delay(2000); // Wait 2 seconds
}

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 = "gpt-4o-mini-2024-07-18",
        Materials = new List<string> { textMaterial._ID, uploadedMaterial._ID }, // Use any material IDs
        Temperature = 0.7,
        EnableWebSearch = true
    }
});

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

#### Step 3: Embed the Chat Component

```csharp
// Basic embed with minimal configuration
var basicEmbed = await client.V1.Components.GenerateEmbed(new()
{
    ID = chatComponent._ID,
    UserID = "student-123"
});

// Advanced embed with all available options
var advancedEmbed = await client.V1.Components.GenerateEmbed(new()
{
    ID = chatComponent._ID,
    // User tracking (optional)
    UserID = "student-123",
    StudentName = "John Doe",              // Student name for display
    GroupIDs = new List<string> { "class-101", "biology-spring-2024" },
    
    // Display dimensions
    Width = "100%",
    Height = "600px",
    
    // Chat-specific features
    Features = new StudyfetchSDK.Models.V1.Components.ComponentGenerateEmbedParamsProperties.Features(enableBadWordsFilter: true)
    {
        EnableWebSearch = true,        // Allow users to search the web
        EnableHistory = true,          // View and continue previous conversations
        EnableVoice = true,           // Voice input with microphone
        EnableFollowUps = true,       // AI-suggested follow-up questions
        EnableComponentCreation = true, // AI can create flashcards, tests, etc.
        PlaceholderText = "Ask your Biology tutor anything..."
    }
});

Console.WriteLine($"Basic Embed URL: {basicEmbed.EmbedURL}");
Console.WriteLine($"Advanced Embed URL: {advancedEmbed.EmbedURL}");

// Note: Theme and branding are configured at the organization level
// StudyFetch branding is automatically hidden for all embeds
```

#### Complete Example

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using StudyfetchSDK.Models.V1.Materials.Upload;
using StudyfetchSDK.Models.V1.Components;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

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

        // 1. Create or upload materials
        var materials = new List<string>();
        
        // Create text material
        var textMaterial = await client.V1.Materials.Create(new()
        {
            Content = new()
            {
                Type = StudyfetchSDK.Models.V1.Materials.ContentProperties.Type.Text,
                Text = "Course introduction and key concepts..."
            },
            Name = "Course Introduction",
            References = new List<StudyfetchSDK.Models.V1.Materials.Reference>
            {
                new("Course Syllabus")
                {
                    URL = "https://example.com/syllabus"
                }
            } // Optional: source references
        });
        materials.Add(textMaterial._ID);
        
        // Upload PDF file
        var fileBytes = await File.ReadAllBytesAsync("course-content.pdf");
        var uploadedMaterial = await client.V1.Materials.Upload.UploadFile(new()
        {
            File = fileBytes,
            FileName = "course-content.pdf",
            Name = "Course Materials"
        });
        materials.Add(uploadedMaterial._ID);

        // IMPORTANT: Wait for materials to finish processing
        // Materials need time to process. Using them immediately will cause component creation to fail.
        foreach (var materialId in materials)
        {
            var materialStatus = "processing";
            while (materialStatus != "active")
            {
                var material = await client.V1.Materials.Retrieve(new()
                {
                    ID = materialId
                });
                materialStatus = material.Status.Raw();
                if (materialStatus == "error")
                    throw new Exception($"Material {materialId} processing failed");
                if (materialStatus == "processing")
                    await Task.Delay(2000); // Wait 2 seconds
            }
        }

        // 2. Create chat assistant
        var chatComponent = await client.V1.Components.Create(new()
        {
            Name = "Course Assistant",
            Type = StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.Type.Chat,
            Config = new StudyfetchSDK.Models.V1.Components.ComponentCreateParamsProperties.ConfigProperties.ChatConfigDto()
            {
                Model = "gpt-4o-mini-2024-07-18",
                Materials = materials,
                Temperature = 0.7,
                EnableWebSearch = true
            }
        });

        // 3. Generate embed URL
        var embed = await client.V1.Components.GenerateEmbed(new()
        {
        ID = chatComponent._ID,
        UserID = "student-123",
        StudentName = "John Doe",    // Student name for display
        GroupIDs = new List<string> { "course-101" },
        Width = "100%",
        Height = "600px",
        Features = new StudyfetchSDK.Models.V1.Components.ComponentGenerateEmbedParamsProperties.Features(enableBadWordsFilter: true)
        {
            EnableWebSearch = true,
            EnableHistory = true,
            EnableVoice = true,
            EnableFollowUps = true,
            EnableComponentCreation = true,
            PlaceholderText = "Ask me anything about the course materials..."
        }
        });

        Console.WriteLine("Study assistant created!");
        Console.WriteLine($"Embed URL: {embed.EmbedURL}");
        Console.WriteLine($"Component ID: {chatComponent._ID}");
    }
}
```

## Embedding Components

After creating components, embed them in your application:

```html
<iframe
  src="YOUR_EMBED_URL"
  width="100%"
  height="600px"
  frameborder="0"
  allow="microphone"
></iframe>
```

## What's Next?

Now that you've created your first components, explore more features:

### Learn More About Components

- Chat Components - Build conversational AI tutors
- Flashcards - Create adaptive study cards
- Practice Tests - Generate assessments
- Audio Recaps - Convert materials to audio
- Scenarios - Design immersive learning experiences
- Explainers - Create animated video explanations
- Uploads - Manage and organize materials

### Advanced Features

- Embedding Components - Integrate into your app
- Analytics API - Track usage and performance (see REST API tab)

### Resources

- REST API Reference - Complete API documentation (see REST API tab)

## Need Help?

- 📧 Email: support@studyfetch.com
- 💬 Discord: [Join our community](https://discord.gg/studyfetch)

## Scenarios

Source: https://studyfetch-api.honen.com/docs/6acd32019de34cae55b27290

## Overview

The Scenarios component creates interactive learning experiences through case studies, role-playing exercises, and problem-solving scenarios. Learners talk with AI-played characters, use the tools you define, and are graded against your criteria. These immersive experiences help students apply theoretical knowledge in practical contexts.

You only need to describe the scenario. At create time, the AI fills in anything you leave blank: `context`, `goal`, `greetingMessage`, `characters`, `criteria`, and, when `requiresFinalAnswer` is `true`, `finalAnswerPrompt`. `tools` and `format` are never generated.

Note

**Required fields.** Every scenarios component needs a top-level **`name`** and **`description`** (the request fails with `Scenario description is required` otherwise).

**Validation on create and update.** When you provide **`characters`**, **`tools`**, or **`criteria`**, every entry must include its required fields:

- **Character**N/A`id`, `name`, `role`
- **Tool**N/A`id`, `name`, `description`, `type`
- **Evaluation criterion**N/A`id`, `description`, `weight` (number, 1–10)

Send an empty array (or omit the field) to let the AI generate characters or criteria at create time. Updates are not auto-filled, so any item you send must already be complete.

## Creating a Scenarios Component

JavaScript

```javascript
import StudyfetchSDK from '@studyfetch/sdk';

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

const scenarioComponent = await client.v1.components.create({
  name: 'Medical Emergency Room Scenario',
  description: 'Assess and diagnose a patient presenting with chest pain',
  type: 'scenarios',
  // TypeScript: the SDK's config type doesn't include scenarios yet, so cast this object (`as any`).
  config: {
    context: 'You are a medical resident in a busy emergency room. A patient has just arrived with chest pain.',
    goal: 'Properly assess the patient, order appropriate tests, and make a diagnosis',
    format: 'Interactive dialogue with patient and medical staff',
    greetingMessage: 'Welcome Dr. [Student Name]. Your patient, Mr. Johnson, has just arrived in room 3 complaining of chest pain.',
    greetingCharacterId: 'nurse-jane',
    requiresFinalAnswer: true,
    finalAnswerPrompt: 'Based on your assessment, what is your diagnosis and treatment plan?',
    criteria: [
      { id: 'history-taking', description: 'Structured history and focused questions', weight: 7 },
      { id: 'clinical-reasoning', description: 'Sound differential and next steps', weight: 8 },
    ],
    characters: [
      {
        id: 'patient-johnson',
        name: 'Mr. Johnson',
        role: 'Patient',
        description: '58-year-old male with chest pain',
      },
      {
        id: 'nurse-jane',
        name: 'Nurse Jane',
        role: 'Emergency Room Nurse',
        description: 'Experienced ER nurse who assists with procedures',
      },
    ],
    tools: [
      {
        id: 'ekg-machine',
        name: 'EKG Machine',
        description: 'Performs 12-lead electrocardiogram',
        type: 'diagnostic',
        dataFormat: 'EKG readings with intervals and interpretation',
      },
      {
        id: 'lab-results',
        name: 'Lab Results System',
        description: 'Provides blood test results',
        type: 'diagnostic',
        dataFormat: 'Complete blood count, cardiac enzymes, etc.',
      },
    ],
  },
});

console.log('Scenario component created:', scenarioComponent._id, scenarioComponent.componentId);
```

Python

```python
from studyfetch_sdk import StudyfetchSDK

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

# The SDK's config type doesn't include scenarios yet, so snake_case keys are not
# converted. Use the API's camelCase keys as shown.
scenario_component = client.v1.components.create(
    name="Medical Emergency Room Scenario",
    description="Assess and diagnose a patient presenting with chest pain",
    type="scenarios",
    config={
        "context": "You are a medical resident in a busy emergency room. A patient has just arrived with chest pain.",
        "goal": "Properly assess the patient, order appropriate tests, and make a diagnosis",
        "format": "Interactive dialogue with patient and medical staff",
        "greetingMessage": "Welcome Dr. [Student Name]. Your patient, Mr. Johnson, has just arrived in room 3 complaining of chest pain.",
        "greetingCharacterId": "nurse-jane",
        "requiresFinalAnswer": True,
        "finalAnswerPrompt": "Based on your assessment, what is your diagnosis and treatment plan?",
        "criteria": [
            {"id": "history-taking", "description": "Structured history and focused questions", "weight": 7},
            {"id": "clinical-reasoning", "description": "Sound differential and next steps", "weight": 8},
        ],
        "characters": [
            {
                "id": "patient-johnson",
                "name": "Mr. Johnson",
                "role": "Patient",
                "description": "58-year-old male with chest pain",
            },
            {
                "id": "nurse-jane",
                "name": "Nurse Jane",
                "role": "Emergency Room Nurse",
                "description": "Experienced ER nurse who assists with procedures",
            },
        ],
        "tools": [
            {
                "id": "ekg-machine",
                "name": "EKG Machine",
                "description": "Performs 12-lead electrocardiogram",
                "type": "diagnostic",
                "dataFormat": "EKG readings with intervals and interpretation",
            },
            {
                "id": "lab-results",
                "name": "Lab Results System",
                "description": "Provides blood test results",
                "type": "diagnostic",
                "dataFormat": "Complete blood count, cardiac enzymes, etc.",
            },
        ],
    },
)

print(f"Scenario component created: {scenario_component.api_id} {scenario_component.component_id}")
```

Java

```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 CreateScenariosComponent {
    public static void main(String[] args) {
        StudyfetchSdkClient client = StudyfetchSdkOkHttpClient.builder()
                .fromEnv()
                .baseUrl("https://studyfetchapi.com")
                .build();

        ComponentCreateParams params = ComponentCreateParams.builder()
                .name("Medical Emergency Room Scenario")
                .description("Assess and diagnose a patient presenting with chest pain")
                .type(ComponentCreateParams.Type.SCENARIOS)
                .config(ComponentCreateParams.Config.ScenariosConfigDto.builder()
                        .context("You are a medical resident in a busy emergency room. A patient has just arrived with chest pain.")
                        .goal("Properly assess the patient, order appropriate tests, and make a diagnosis")
                        .format("Interactive dialogue with patient and medical staff")
                        .greetingMessage("Welcome Dr. [Student Name]. Your patient, Mr. Johnson, has just arrived in room 3 complaining of chest pain.")
                        .greetingCharacterId("nurse-jane")
                        .requiresFinalAnswer(true)
                        .finalAnswerPrompt("Based on your assessment, what is your diagnosis and treatment plan?")
                        .criteria(List.of(
                                ComponentCreateParams.Config.ScenariosConfigDto.Criterion.builder()
                                        .id("history-taking")
                                        .description("Structured history and focused questions")
                                        .weight(7.0)
                                        .build(),
                                ComponentCreateParams.Config.ScenariosConfigDto.Criterion.builder()
                                        .id("clinical-reasoning")
                                        .description("Sound differential and next steps")
                                        .weight(8.0)
                                        .build()
                        ))
                        .characters(List.of(
                                ComponentCreateParams.Config.ScenariosConfigDto.Character.builder()
                                        .id("patient-johnson")
                                        .name("Mr. Johnson")
                                        .role("Patient")
                                        .description("58-year-old male with chest pain")
                                        .build(),
                                ComponentCreateParams.Config.ScenariosConfigDto.Character.builder()
                                        .id("nurse-jane")
                                        .name("Nurse Jane")
                                        .role("Emergency Room Nurse")
                                        .description("Experienced ER nurse who assists with procedures")
                                        .build()
                        ))
                        .tools(List.of(
                                ComponentCreateParams.Config.ScenariosConfigDto.Tool.builder()
                                        .id("ekg-machine")
                                        .name("EKG Machine")
                                        .description("Performs 12-lead electrocardiogram")
                                        .type("diagnostic")
                                        .dataFormat("EKG readings with intervals and interpretation")
                                        .build(),
                                ComponentCreateParams.Config.ScenariosConfigDto.Tool.builder()
                                        .id("lab-results")
                                        .name("Lab Results System")
                                        .description("Provides blood test results")
                                        .type("diagnostic")
                                        .dataFormat("Complete blood count, cardiac enzymes, etc.")
                                        .build()
                        ))
                        .build())
                .build();

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

cURL

```curl
# The C# SDK can't create scenarios components yet (its scenario config type is disabled).
# Use the REST API directly from C#, or this request.
curl -X POST "https://studyfetchapi.com/api/v1/components" \
  -H "x-api-key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Medical Emergency Room Scenario",
    "description": "Assess and diagnose a patient presenting with chest pain",
    "type": "scenarios",
    "config": {
      "context": "You are a medical resident in a busy emergency room. A patient has just arrived with chest pain.",
      "goal": "Properly assess the patient, order appropriate tests, and make a diagnosis",
      "greetingCharacterId": "nurse-jane",
      "characters": [
        { "id": "patient-johnson", "name": "Mr. Johnson", "role": "Patient" },
        { "id": "nurse-jane", "name": "Nurse Jane", "role": "Emergency Room Nurse" }
      ],
      "tools": [
        { "id": "ekg-machine", "name": "EKG Machine", "description": "Performs 12-lead electrocardiogram", "type": "diagnostic" }
      ],
      "criteria": []
    }
  }'
```

## Configuration Parameters

#### `name`

string · required

Name of the scenarios component

#### `description`

string · required

Short description of the scenario. Required for scenarios, even though other component types treat it as optional.

#### `type`

string · required

Must be `"scenarios"`

#### `config`

object · required

Scenarios configuration object. Unknown keys are rejected with `400`.

### Configuration Properties

These properties go inside `config`.

#### `context`

string

Overall context and setting of the scenario. Generated if blank.

#### `goal`

string

Primary goal or learning objective of the interaction. Generated if blank.

#### `format`

string

Interaction format and step-by-step instructions. Never generated; when empty, the scenario runs as a general conversation.

#### `greetingMessage`

string

Initial greeting message displayed to users. Generated if blank.

#### `greetingCharacterId`

string

ID of the character who delivers the greeting. If blank or not one of the `characters`, the first character is used.

#### `requiresFinalAnswer`

boolean · default: false

Whether the learner must submit a final answer to finish the scenario

#### `finalAnswerPrompt`

string

Prompt shown when asking for the final answer. Generated if blank and `requiresFinalAnswer` is `true`.

#### `criteria`

array

Evaluation criteria for grading. Each entry needs a stable **`id`**, a human-readable **`description`**, and a **`weight`** from **1–10** (relative importance). Generated if omitted or empty at create time.

### Criterion object

These properties go inside `criteria`.

#### `id`

string · required

Stable identifier for this criterion

#### `description`

string · required

What this criterion measures

#### `weight`

number · required

Importance weight from 1 to 10

#### `characters`

array

Characters in the scenario. Generated if omitted or empty at create time.

### Character Properties

These properties go inside `characters`.

#### `id`

string · required

Unique identifier for the character

#### `name`

string · required

Character's name

#### `role`

string · required

Character's role in the scenario

#### `description`

string

Character's description and personality

#### `tools`

array

Tools available in the scenario. Never generated.

### Tool Properties

These properties go inside `tools`.

#### `id`

string · required

Unique identifier for the tool

#### `name`

string · required

Tool's name

#### `description`

string · required

What the tool does or shows

#### `type`

string · required

Type of tool (e.g., "Database", "Document", "diagnostic")

#### `dataFormat`

string

Format of data the tool provides

#### `model`

string · default: gpt-4o-mini-2024-07-18

Model that plays the characters and grades the learner. Generating blank fields at create time uses a fixed model.

#### `materials`

array

Accepted and stored, but not currently used by scenarios

#### `folders`

array

Accepted and stored, but not currently used by scenarios

#### `enableHistory`

boolean

Stored on the component. The embed reads this setting from the embed token; use `features.enableHistory`.

#### `enableVoice`

boolean

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

#### `placeholderText`

string

Stored on the component. Use `features.placeholderText` to control the embed.

## Response

```json
{
  "_id": "6650f1c2a9b3e4d5f6a7b8c9",
  "componentId": "scenarios_V1StGXR8_Z",
  "name": "Medical Emergency Room Scenario",
  "description": "Assess and diagnose a patient presenting with chest pain",
  "type": "scenarios",
  "status": "active",
  "origin": "api",
  "organization": "64f0a1b2c3d4e5f6a7b8c9d0",
  "config": {
    "context": "You are a medical resident in a busy emergency room. A patient has just arrived with chest pain.",
    "goal": "Properly assess the patient, order appropriate tests, and make a diagnosis",
    "format": "Interactive dialogue with patient and medical staff",
    "greetingMessage": "Welcome Dr. [Student Name]. Your patient, Mr. Johnson, has just arrived in room 3 complaining of chest pain.",
    "greetingCharacterId": "nurse-jane",
    "requiresFinalAnswer": true,
    "finalAnswerPrompt": "Based on your assessment, what is your diagnosis and treatment plan?",
    "criteria": [
      { "id": "history-taking", "description": "Structured history and focused questions", "weight": 7 },
      { "id": "clinical-reasoning", "description": "Sound differential and next steps", "weight": 8 }
    ],
    "characters": [
      {
        "id": "patient-johnson",
        "name": "Mr. Johnson",
        "role": "Patient",
        "description": "58-year-old male with chest pain"
      },
      {
        "id": "nurse-jane",
        "name": "Nurse Jane",
        "role": "Emergency Room Nurse",
        "description": "Experienced ER nurse who assists with procedures"
      }
    ],
    "tools": [
      {
        "id": "ekg-machine",
        "name": "EKG Machine",
        "description": "Performs 12-lead electrocardiogram",
        "type": "diagnostic",
        "dataFormat": "EKG readings with intervals and interpretation"
      },
      {
        "id": "lab-results",
        "name": "Lab Results System",
        "description": "Provides blood test results",
        "type": "diagnostic",
        "dataFormat": "Complete blood count, cardiac enzymes, etc."
      }
    ]
  },
  "usageCount": 0,
  "createdAt": "2026-10-06T10:00:00.000Z",
  "updatedAt": "2026-10-06T10:00:00.000Z"
}
```

The response `config` includes any values the AI generated, and the `greetingCharacterId` the server chose.

## Updating a Scenario

Update a scenario with `PATCH /api/v1/components/{id}`, where `{id}` is the component's `_id`. **Include `"type": "scenarios"` whenever you send `config`**; without it the request is rejected with `400`.

The server merges your changes with the existing scenario. Partial updates (just `criteria`, just `tools`, and so on) are fine; the existing values fill in. The update fails with `400` if the merged result would leave any of these empty:

- `name` and `description`
- `context`, `goal`, `greetingMessage`
- `finalAnswerPrompt`, when `requiresFinalAnswer` is `true`

Updates are not auto-filled, so any `characters`, `tools`, or `criteria` you send must be complete. `format` can be empty.

The SDKs' `update` methods only change a component's `status` for now, so update scenario content with cURL or an HTTP client:

```bash
curl -X PATCH "https://studyfetchapi.com/api/v1/components/6650f1c2a9b3e4d5f6a7b8c9" \
  -H "x-api-key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "scenarios",
    "config": {
      "criteria": [
        { "id": "history-taking", "description": "Structured history and focused questions", "weight": 6 },
        { "id": "clinical-reasoning", "description": "Sound differential and next steps", "weight": 9 }
      ]
    }
  }'
```

## Embedding This Component

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

### Generate Embed URL

JavaScript

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

  // Scenarios-specific features
  features: {
    enableBadWordsFilter: true,  // Required by the SDK type; no effect on Scenarios
    enableHistory: true,
    enableVoice: true,
    placeholderText: 'Type your message...',
  },

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

console.log(embedResponse.embedUrl);
```

Python

```python
embed_response = client.v1.components.generate_embed(
    scenario_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; no effect on Scenarios
        "enable_history": True,
        "enable_voice": True,
        "placeholder_text": "Type your message...",
    },
    width="100%",
    height="600px",
)

print(embed_response.embed_url)
```

Java

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

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

    // Scenarios-specific features
    .features(ComponentGenerateEmbedParams.Features.builder()
        .enableBadWordsFilter(true)  // Required: build() throws without it
        .enableHistory(true)
        .enableVoice(true)
        .placeholderText("Type your message...")
        .build())

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

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

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

C#

```csharp
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 GenerateScenariosEmbed
{
    public static async Task GenerateEmbed(string scenarioComponentId)
    {
        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 = scenarioComponentId,
            UserID = "user-456",
            StudentName = "Jane Smith",  // Student name for display
            GroupIDs = new List<string> { "class-101", "class-102" },
            SessionID = "session-789",
            Features = new Features(enableBadWordsFilter: true)
            {
                EnableHistory = true,
                EnableVoice = true,
                PlaceholderText = "Type your message..."
            },
            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`).

Warning

**Embed tokens are valid for 1 hour.** `expiryHours` only changes the `expiresAt` value in the response; the token itself always expires 1 hour after it is issued, and the embed does not renew it. If learners keep the page open longer, load the iframe with a fresh embed URL. See Chat for the token refresh endpoint.

### Scenarios-Specific Embedding Features

#### `features.enableHistory`

boolean · default: true

Save scenario progress and allow users to resume where they left off

#### `features.enableVoice`

boolean · default: true

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

#### `features.placeholderText`

string · default: Type your message...

Placeholder text for the message input

### 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.

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

## React Native

The React Native SDK (`@studyfetch/react-native-sdk`) renders Scenarios natively with embed tokens instead of an iframe. Import it from `@studyfetch/react-native-sdk/scenarios` and wrap your app in `EmbedTokenProvider` with a `getToken` function that asks your backend to mint an embed token for a component.

```tsx
import { EmbedTokenProvider } from '@studyfetch/react-native-sdk';
import { StudyFetchScenario, useScenario } from '@studyfetch/react-native-sdk/scenarios';

// Full scenario UI: character dialogue, tools, grading
<EmbedTokenProvider getToken={fetchToken}>
  <StudyFetchScenario componentId="scenarios_V1StGXR8_Z" userId="user-456" />
</EmbedTokenProvider>

// Headless: build your own UI
const {
  scenario, messages, activeCharacterName,
  submission, sendMessage, submit, evaluate, restart,
} = useScenario({ componentId: 'scenarios_V1StGXR8_Z', userId: 'user-456' });
```

- Replies stream by default, and a single reply can include several characters speaking.
- `submit(finalAnswer)` grades and completes the session and populates `submission`. `evaluate(draft)` returns a preview grade without saving.
- Pass `stream={false}` to receive each reply in one response on networks where streaming is unreliable.

## Search

Source: https://studyfetch-api.honen.com/docs/6acd2e3894f05db978608cfb

## Overview

The Materials API includes powerful semantic search capabilities using Retrieval-Augmented Generation (RAG). This allows you to:

- Search materials by meaning, not just keywords
- Find relevant content across all your materials
- Get contextual results with relevance scores
- Search within specific materials or folders

## How It Works

1. **Automatic Indexing** - When materials are uploaded, they're automatically processed:
   - Text is extracted (OCR for PDFs, transcription for audio/video)
   - Content is split into chunks
   - Vector embeddings are generated
   - Embeddings are stored in a vector database
2. **Semantic Search** - When you search:
   - Your query is converted to a vector embedding
   - Similar content is found using vector similarity
   - Results are ranked by relevance

## Search Materials

Perform a semantic search across all your materials.

JavaScript

```javascript
const searchResults = await client.v1.materials.search({
  query: 'What is photosynthesis and how does it work?',
  topK: 5 // Number of results to return
});

searchResults.results.forEach(result => {
  console.log(`Score: ${result.score}`);
  console.log(`Material: ${result.material?.name}`);
  console.log(`Chunk: ${result.text.substring(0, 200)}...`);
  console.log('---');
});
```

Python

```python
search_results = client.v1.materials.search(
    query="What is photosynthesis and how does it work?",
    top_k=5  # Number of results to return
)

for result in search_results.results:
    print(f"Score: {result.score}")
    print(f"Material: {result.material.name}")
    print(f"Chunk: {result.text[:200]}...")
    print("---")
```

Java

```java
import com.studyfetch.javasdk.models.v1.materials.MaterialSearchParams;
import com.studyfetch.javasdk.models.v1.materials.MaterialSearchResponse;

MaterialSearchParams params = MaterialSearchParams.builder()
    .query("What is photosynthesis and how does it work?")
    .topK(5)  // Number of results to return
    .build();

MaterialSearchResponse searchResults = client.v1().materials().search(params);

for (MaterialSearchResponse.Result result : searchResults.results()) {
    System.out.println("Score: " + result.score());
    System.out.println("Material: " + result.material());
    System.out.println("Chunk: " + 
        result.text().substring(0, Math.min(200, result.text().length())) + "...");
    System.out.println("---");
}
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using System;
using System.Threading.Tasks;

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

        var searchResults = await client.V1.Materials.Search(new MaterialSearchParams()
        {
            Query = "What is photosynthesis and how does it work?",
            TopK = 5 // Number of results to return
        });

        foreach (var result in searchResults.Results)
        {
            Console.WriteLine($"Score: {result.Score}");
            Console.WriteLine($"Material: {result.Material.Name}");
            Console.WriteLine($"Chunk: {result.Text.Substring(0, Math.Min(200, result.Text.Length))}...");
            Console.WriteLine("---");
        }
    }
}
```

## Search Response Format

```json
{
  "query": "What is photosynthesis and how does it work?",
  "totalResults": 5,
  "results": [
    {
      "score": 0.92,
      "text": "Photosynthesis is the process by which plants convert light energy into chemical energy...",
      "chunkIndex": 3,
      "material": {
        "id": "mat_123abc",
        "name": "Biology Chapter 3 - Plant Processes",
        "contentType": "pdf",
        "references": [
          {
            "title": "Plant Biology Handbook",
            "url": "https://example.com/handbook"
          }
        ]
      }
    },
    {
      "score": 0.87,
      "text": "The light-dependent reactions of photosynthesis occur in the thylakoid membranes...",
      "chunkIndex": 5,
      "material": {
        "id": "mat_456def",
        "name": "Plant Biology Textbook",
        "contentType": "pdf",
        "references": [
          {
            "title": "Advanced Plant Science",
            "url": "https://example.com/advanced-plant-science"
          }
        ]
      }
    }
  ]
}
```

## Advanced Search Options

### Search with More Results

JavaScript

```javascript
// Get more results for comprehensive coverage
const results = await client.v1.materials.search({
  query: 'cellular respiration and ATP production',
  topK: 20 // Get top 20 results
});

console.log(`Found ${results.totalResults} relevant chunks`);
```

Python

```python
# Get more results for comprehensive coverage
results = client.v1.materials.search(
    query="cellular respiration and ATP production",
    top_k=20  # Get top 20 results
)

print(f"Found {results.total_results} relevant chunks")
```

Java

```java
// Get more results for comprehensive coverage
MaterialSearchParams params = MaterialSearchParams.builder()
    .query("cellular respiration and ATP production")
    .topK(20)  // Get top 20 results
    .build();

MaterialSearchResponse results = client.v1().materials().search(params);

System.out.println("Found " + results.results().size() + " relevant chunks");
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using System;
using System.Threading.Tasks;

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

        // Get more results for comprehensive coverage
        var results = await client.V1.Materials.Search(new MaterialSearchParams()
        {
            Query = "cellular respiration and ATP production",
            TopK = 20 // Get top 20 results
        });

        Console.WriteLine($"Found {results.TotalResults} relevant chunks");
    }
}
```

### Filter by Material Type

While the current API doesn't support filtering in the search endpoint, you can filter results after retrieval:

JavaScript

```javascript
const searchResults = await client.v1.materials.search({
  query: 'DNA replication process',
  topK: 10
});

// Filter for only PDF results
const pdfResults = searchResults.results.filter(
  result => result.material?.contentType === 'pdf'
);

console.log(`Found ${pdfResults.length} results in PDFs`);
```

Python

```python
search_results = client.v1.materials.search(
    query="DNA replication process",
    top_k=10
)

# Filter for only PDF results
pdf_results = [
    result for result in search_results.results 
    if result.material.content_type == "pdf"
]

print(f"Found {len(pdf_results)} results in PDFs")
```

Java

```java
import com.studyfetch.javasdk.models.v1.materials.MaterialSearchParams;
import com.studyfetch.javasdk.models.v1.materials.MaterialSearchResponse;
import java.util.List;
import java.util.stream.Collectors;

MaterialSearchResponse searchResults = client.v1().materials().search(
    MaterialSearchParams.builder()
        .query("DNA replication process")
        .topK(10)
        .build()
);

// Filter for only PDF results
List<MaterialSearchResponse.Result> pdfResults = searchResults.results().stream()
    .filter(result -> result.material().isPresent() && 
                     "pdf".equals(result.material().get().contentType()))
    .collect(Collectors.toList());

System.out.println("Found " + pdfResults.size() + " results in PDFs");
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using System;
using System.Linq;
using System.Threading.Tasks;

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

        var searchResults = await client.V1.Materials.Search(new MaterialSearchParams()
        {
            Query = "DNA replication process",
            TopK = 10
        });

        // Filter for only PDF results
        var pdfResults = searchResults.Results
            .Where(result => result.Material.ContentType == "pdf")
            .ToList();

        Console.WriteLine($"Found {pdfResults.Count} results in PDFs");
    }
}
```

## Uploading Files

Source: https://studyfetch-api.honen.com/docs/6acd2e3894f05db978608cf6

## Overview

StudyFetch supports multiple methods for uploading materials:

- **Direct file upload** - Upload files directly in the request
- **Upload and process** - Upload files and wait for processing to complete
- **URL import** - Import content from web URLs
- **Presigned URL upload** - Upload large files directly to S3
- **Create and process** - Create text materials and wait for processing to complete

Note

**Processing Time**: After uploading, materials require processing time before they can be used. Materials will have `status: 'processing'` initially and must reach `status: 'active'` before they can be used in components or for grading. Processing time varies based on file size and type (typically 5-30 seconds for PDFs, longer for videos).

Use the `uploadFileAndProcess` or `createAndProcess` methods if you need to use the material immediately after creation - they wait for processing to complete before returning.

## Direct File Upload

Upload files directly to the API. Best for files under 10MB.

JavaScript

```javascript
import StudyfetchSDK from '@studyfetch/sdk';
import fs from 'fs';
import path from 'path';

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

const fileBuffer = await fs.promises.readFile(
  path.join(process.cwd(), 'biology-notes.pdf'),
);
const file = new File([fileBuffer], 'biology-notes.pdf', {
  type: 'application/pdf',
});

const material = await client.v1.materials.upload.uploadFile({
  file,
  name: 'Biology Chapter 1 Notes',
  folderId: 'folder_123' // Optional
});

console.log('Material uploaded:', material._id);
console.log('Status:', material.status); // 'processing'
```

Python

```python
from studyfetch_sdk import StudyfetchSDK

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

# Upload a file
with open('biology-notes.pdf', 'rb') as file:
    material = client.v1.materials.upload.upload_file({
        'file': file,
        'name': "Biology Chapter 1 Notes",
        'folderId': "folder_123"  # Optional
    })

print(f"Material uploaded: {material._id}")
print(f"Status: {material.status}")  # 'processing'
```

Java

```java
import com.studyfetch.javasdk.client.StudyfetchSdkClient;
import com.studyfetch.javasdk.client.okhttp.StudyfetchSdkOkHttpClient;
import com.studyfetch.javasdk.models.v1.materials.MaterialResponse;
import com.studyfetch.javasdk.models.v1.materials.upload.UploadUploadFileParams;
import java.io.FileInputStream;

public class UploadMaterial {
    public static void main(String[] args) throws Exception {
        StudyfetchSdkClient client = StudyfetchSdkOkHttpClient.builder()
            .fromEnv()
            .baseUrl("https://studyfetchapi.com")
            .build();
        
        FileInputStream fileStream = new FileInputStream("biology-notes.pdf");
        
        UploadUploadFileParams params = UploadUploadFileParams.builder()
            .file(fileStream)
            .name("Biology Chapter 1 Notes")
            .folderId("folder_123")  // Optional
            .build();
        
        MaterialResponse material = client.v1().materials().upload().uploadFile(params);
        
        System.out.println("Material uploaded: " + material._id());
        System.out.println("Status: " + material.status()); // 'processing'
    }
}
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using System;
using System.IO;
using System.Threading.Tasks;

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

        var fileBytes = await File.ReadAllBytesAsync("biology-notes.pdf");
        
        var material = await client.V1.Materials.Upload.UploadFile(new()
        {
            File = fileBytes,
            FileName = "biology-notes.pdf",
            Name = "Biology Chapter 1 Notes",
            FolderID = "folder_123" // Optional
        });

        Console.WriteLine($"Material uploaded: {material._ID}");
        Console.WriteLine($"Status: {material.Status}"); // 'processing'
    }
}
```

## Upload and Process File

Upload a file and wait for processing to complete before returning. This is useful when you need to use the material immediately after upload.

Note

**Synchronous Processing**: Unlike the regular `uploadFile` method which returns immediately while processing happens in the background, `uploadFileAndProcess` waits until the material is fully processed and ready to use before returning.

JavaScript

```javascript
import StudyfetchSDK from '@studyfetch/sdk';
import fs from 'fs';
import path from 'path';

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

const fileBuffer = await fs.promises.readFile(
  path.join(process.cwd(), 'biology-notes.pdf'),
);
const file = new File([fileBuffer], 'biology-notes.pdf', {
  type: 'application/pdf',
});

// Upload and wait for processing to complete
const material = await client.v1.materials.upload.uploadFileAndProcess({
  file,
  name: 'Biology Chapter 1 Notes',
  folderId: 'folder_123' // Optional
});

// Material is ready to use immediately
console.log('Material uploaded and processed:', material._id);
console.log('Status:', material.status); // 'active'
```

Python

```python
from studyfetch_sdk import StudyfetchSDK

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

# Upload and wait for processing to complete
with open('biology-notes.pdf', 'rb') as file:
    material = client.v1.materials.upload.upload_file_and_process({
        'file': file,
        'name': "Biology Chapter 1 Notes",
        'folderId': "folder_123"  # Optional
    })

# Material is ready to use immediately
print(f"Material uploaded and processed: {material._id}")
print(f"Status: {material.status}")  # 'active'
print(f"Word count: {material.metadata.word_count}")
```

Java

```java
import com.studyfetch.javasdk.client.StudyfetchSdkClient;
import com.studyfetch.javasdk.client.okhttp.StudyfetchSdkOkHttpClient;
import com.studyfetch.javasdk.models.v1.materials.MaterialResponse;
import com.studyfetch.javasdk.models.v1.materials.upload.UploadUploadFileAndProcessParams;
import java.io.FileInputStream;

public class UploadAndProcessMaterial {
    public static void main(String[] args) throws Exception {
        StudyfetchSdkClient client = StudyfetchSdkOkHttpClient.builder()
            .fromEnv()
            .baseUrl("https://studyfetchapi.com")
            .build();
        
        FileInputStream fileStream = new FileInputStream("biology-notes.pdf");
        
        // Upload and wait for processing to complete
        UploadUploadFileAndProcessParams params = UploadUploadFileAndProcessParams.builder()
            .file(fileStream)
            .name("Biology Chapter 1 Notes")
            .folderId("folder_123")  // Optional
            .build();
        
        MaterialResponse material = client.v1().materials().upload().uploadFileAndProcess(params);
        
        // Material is ready to use immediately
        System.out.println("Material uploaded and processed: " + material._id());
        System.out.println("Status: " + material.status()); // ACTIVE
    }
}
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using System;
using System.IO;
using System.Threading.Tasks;

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

        var fileBytes = await File.ReadAllBytesAsync("biology-notes.pdf");
        
        // Upload and wait for processing to complete
        var material = await client.V1.Materials.Upload.UploadFileAndProcess(new()
        {
            File = fileBytes,
            FileName = "biology-notes.pdf",
            Name = "Biology Chapter 1 Notes",
            FolderID = "folder_123" // Optional
        });

        // Material is ready to use immediately
        Console.WriteLine($"Material uploaded and processed: {material._ID}");
        Console.WriteLine($"Status: {material.Status}"); // 'active'
        Console.WriteLine($"Material name: {material.Name}");
    }
}
```

## Upload from URL

Import content directly from web URLs. Supports documents, videos, and web pages.

JavaScript

```javascript
const material = await client.v1.materials.upload.uploadFromURL({
  url: 'https://example.com/biology-textbook.pdf',
  name: 'Biology Textbook',
  folderId: 'folder_123' // Optional
});

console.log('Material imported:', material._id);
```

Python

```python
material = client.v1.materials.upload.upload_from_url({
    'url': "https://example.com/biology-textbook.pdf",
    'name': "Biology Textbook",
    'folderId': "folder_123"  # Optional
})

print(f"Material imported: {material._id}")
```

Java

```java
import com.studyfetch.javasdk.models.v1.materials.upload.UploadUploadFromUrlParams;
import com.studyfetch.javasdk.models.v1.materials.MaterialResponse;

UploadUploadFromUrlParams params = UploadUploadFromUrlParams.builder()
    .url("https://example.com/biology-textbook.pdf")
    .name("Biology Textbook")
    .folderId("folder_123")  // Optional
    .build();

MaterialResponse material = client.v1().materials().upload().uploadFromUrl(params);

System.out.println("Material imported: " + material._id());
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using System;
using System.Threading.Tasks;

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

        var material = await client.V1.Materials.Upload.UploadFromURL(new()
        {
            URL = "https://example.com/biology-textbook.pdf",
            Name = "Biology Textbook",
            FolderID = "folder_123" // Optional
        });

        Console.WriteLine($"Material imported: {material._ID}");
    }
}
```

## Upload Text Content

Create materials from plain text content without file upload.

JavaScript

```javascript
const material = await client.v1.materials.create({
  name: 'Biology Notes',
  content: {
    type: 'text',
    text: 'Cell Structure: The cell is the basic unit of life...'
  },
  folderId: 'folder_123', // Optional
  references: [
    {
      title: 'Introduction to Biology Textbook',
      url: 'https://example.com/textbook'
    }
  ] // Optional: source references
});

console.log('Text material created:', material._id);
```

Python

```python
material = client.v1.materials.create(
    name="Biology Notes",
    content={
        "type": "text",
        "text": "Cell Structure: The cell is the basic unit of life..."
    },
    folder_id="folder_123",  # Optional
    references=[
        {
            "title": "Introduction to Biology Textbook",
            "url": "https://example.com/textbook"
        }
    ]  # Optional: source references
)

print(f"Text material created: {material._id}")
```

Java

```java
import com.studyfetch.javasdk.models.v1.materials.MaterialCreateParams;
import com.studyfetch.javasdk.models.v1.materials.MaterialResponse;
import com.studyfetch.javasdk.models.v1.materials.Content;
import com.studyfetch.javasdk.models.v1.materials.Reference;
import java.util.List;

MaterialCreateParams params = MaterialCreateParams.builder()
    .name("Biology Notes")
    .content(Content.builder()
        .type(Content.Type.TEXT)
        .text("Cell Structure: The cell is the basic unit of life...")
        .build())
    .folderId("folder_123")  // Optional
    .references(List.of(
        Reference.builder()
            .title("Introduction to Biology Textbook")
            .url("https://example.com/textbook")
            .build()
    )) // Optional: source references
    .build();

MaterialResponse material = client.v1().materials().create(params);

System.out.println("Text material created: " + material._id());

// Access references if any were added
if (material.references().isPresent() && !material.references().get().isEmpty()) {
    System.out.println("References:");
    for (var reference : material.references().get()) {
        System.out.println("  - " + reference.title() + ": " + reference.url().orElse(""));
    }
}
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

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

        var material = await client.V1.Materials.Create(new()
        {
            Name = "Biology Notes",
            Content = new()
            {
                Type = StudyfetchSDK.Models.V1.Materials.ContentProperties.Type.Text,
                Text = "Cell Structure: The cell is the basic unit of life..."
            },
            FolderID = "folder_123", // Optional
            References = new List<StudyfetchSDK.Models.V1.Materials.Reference>
            {
                new("Introduction to Biology Textbook")
                {
                    URL = "https://example.com/textbook"
                }
            } // Optional: Add source references
        });

        Console.WriteLine($"Text material created: {material._ID}");
        
        // Access references if any were added
        if (material.References?.Count > 0)
        {
            Console.WriteLine("References:");
            foreach (var reference in material.References)
            {
                Console.WriteLine($"  - {reference.Title}: {reference.URL}");
            }
        }
    }
}
```

## Create and Process Material

The `createAndProcess` method creates a material and waits for it to be fully processed before returning. This is useful when you need to use the material immediately after creation.

Note

**Synchronous Processing**: Unlike the regular `create` method which returns immediately while processing happens in the background, `createAndProcess` waits until the material is fully processed and ready to use before returning.

JavaScript

```javascript
import StudyfetchSDK from '@studyfetch/sdk';

const client = new StudyfetchSDK({
  apiKey: 'your-api-key',
});

// Create and wait for processing to complete
const material = await client.v1.materials.createAndProcess({
  content: { 
    type: 'text',
    text: 'Chapter 1: Introduction to Biology...'
  },
  name: 'Chapter 1 - Introduction',
  references: [
    {
      title: 'Biology Course Materials',
      url: 'https://example.com/course-materials'
    }
  ] // Optional: source references
});

// Material is ready to use immediately
console.log('Material ID:', material._id);
console.log('Status:', material.status); // 'active'
```

Python

```python
# Create and wait for processing to complete
material = client.v1.materials.create_and_process({
    'content': {
        'type': 'text',
        'text': 'Chapter 1: Introduction to Biology...'
    },
    'name': 'Chapter 1 - Introduction',
    'references': [
        {
            'title': 'Biology Course Materials',
            'url': 'https://example.com/course-materials'
        }
    ]  # Optional: source references
})

# Material is ready to use immediately
print(f"Material ID: {material._id}")
print(f"Status: {material.status}")  # 'active'
print(f"Word count: {material.metadata.word_count}")
```

Java

```java
import com.studyfetch.javasdk.StudyfetchSdkClient;
import com.studyfetch.javasdk.StudyfetchSdkOkHttpClient;
import com.studyfetch.javasdk.models.v1.materials.MaterialCreateAndProcessParams;
import com.studyfetch.javasdk.models.v1.materials.MaterialResponse;
import com.studyfetch.javasdk.models.v1.materials.Content;
import com.studyfetch.javasdk.models.v1.materials.Reference;
import java.util.List;

StudyfetchSdkClient client = StudyfetchSdkOkHttpClient.builder()
    .apiKey("your-api-key")
    .baseUrl("https://studyfetchapi.com")
    .build();

// Create and wait for processing to complete
MaterialCreateAndProcessParams params = MaterialCreateAndProcessParams.builder()
    .content(Content.builder()
        .type(Content.Type.TEXT)
        .text("Chapter 1: Introduction to Biology...")
        .build())
    .name("Chapter 1 - Introduction")
    .references(List.of(
        Reference.builder()
            .title("Biology Course Materials")
            .url("https://example.com/course-materials")
            .build()
    )) // Optional: source references
    .build();

MaterialResponse material = client.v1().materials().createAndProcess(params);

// Material is ready to use immediately
System.out.println("Material ID: " + material._id());
System.out.println("Status: " + material.status()); // ACTIVE

// Access references if any were added
if (material.references().isPresent() && !material.references().get().isEmpty()) {
    System.out.println("References:");
    for (var reference : material.references().get()) {
        System.out.println("  - " + reference.title() + ": " + reference.url().orElse(""));
    }
}
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Materials;
using System;
using System.Threading.Tasks;

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

        // Create and wait for processing to complete
        var material = await client.V1.Materials.CreateAndProcess(new()
        {
            Content = new()
            {
                Type = StudyfetchSDK.Models.V1.Materials.ContentProperties.Type.Text,
                Text = "Chapter 1: Introduction to Biology..."
            },
            Name = "Chapter 1 - Introduction"
        });

        // Material is ready to use immediately
        Console.WriteLine($"Material ID: {material._ID}");
        Console.WriteLine($"Status: {material.Status}"); // 'active'
        Console.WriteLine($"Material name: {material.Name}");
    }
}
```

## File Type Examples

### PDF Documents

```javascript
const fileBuffer = await fs.promises.readFile(
  path.join(process.cwd(), 'textbook.pdf'),
);
const file = new File([fileBuffer], 'textbook.pdf', {
  type: 'application/pdf',
});

const material = await client.v1.materials.upload.uploadFile({
  file,
  name: 'Biology Textbook'
});
```

### Audio Files (MP3, WAV)

```javascript
const fileBuffer = await fs.promises.readFile(
  path.join(process.cwd(), 'podcast.mp3'),
);
const file = new File([fileBuffer], 'podcast.mp3', {
  type: 'audio/mpeg',
});

const material = await client.v1.materials.upload.uploadFile({
  file,
  name: 'Biology Podcast Episode 1'
});
```

### EPUB Books

```javascript
// EPUB files are automatically processed into chapters
const fileBuffer = await fs.promises.readFile(
  path.join(process.cwd(), 'textbook.epub'),
);
const file = new File([fileBuffer], 'textbook.epub', {
  type: 'application/epub+zip',
});

const material = await client.v1.materials.upload.uploadFile({
  file,
  name: 'Digital Textbook'
});
```

## Processing Status

Check material status:

```javascript
const material = await client.v1.materials.retrieve(materialId);

if (material.status === 'active') {
  console.log('Processing complete!');
} else if (material.status === 'processing') {
  console.log('Still processing...');
} else if (material.status === 'error') {
  console.log('Processing failed');
}
```

## User Tracking

Source: https://studyfetch-api.honen.com/docs/6acd32339de34cae55b272d1

The StudyFetch Usage Tracking API provides comprehensive analytics and monitoring capabilities to track user activity, measure engagement, and analyze usage patterns across your organization.

## Understanding User ID and Group ID

**User ID** is a unique identifier for each user that comes from your platform. StudyFetch accepts any user ID format that your system uses, allowing seamless integration with your existing user management system. This flexibility enables:

- **Platform Integration**: Use your existing user IDs without modification - whether they're UUIDs, email addresses, student IDs, or any other format
- **Individual Activity Tracking**: Monitor specific user interactions with materials, components, and features
- **Personalized Analytics**: Generate user-specific reports on study patterns, performance, and engagement
- **Progress Monitoring**: Track individual learning progress across different materials and assessments
- **Usage Attribution**: Link specific events and actions to individual users for detailed analysis

**Important**: StudyFetch doesn't generate user IDs - it uses whatever user identifiers your platform provides, ensuring consistency across your systems.

**Group ID** represents an organizational unit such as a class, school, district, or any custom grouping. This ID allows:

- **Aggregate Analytics**: View combined usage statistics for all users within a group
- **Organizational Reporting**: Generate reports at the class, school, or district level
- **Performance Comparison**: Compare usage patterns and outcomes across different groups
- **Resource Allocation**: Understand how different groups utilize StudyFetch features to optimize resource distribution

## Usage Event Types

The API tracks various event types to provide comprehensive insights:

### Material Events

- `material_created` - When new study material is created
- `material_uploaded` - When files are uploaded to the platform
- `material_processed` - When materials are processed by AI
- `material_deleted` - When materials are removed

### Component Events

- `component_created` - When study components (flashcards, tests, etc.) are generated
- `component_accessed` - When users view or interact with components
- `component_deleted` - When components are removed
- `component_usage` - General component interaction events

### Chat & Communication Events

- `chat_message_sent` - When users send messages in AI chat
- `chat_session_started` - When new chat sessions begin
- `chat_session_ended` - When chat sessions conclude

### Assessment Events

- `test_created` - When practice tests are generated
- `test_started` - When users begin assessments
- `test_completed` - When tests are finished
- `test_question_answered` - Individual question responses
- `test_retaken` - When users retake assessments

### Feature Usage Events

- `audio_recap_create` - Audio summary generation
- `assignment_grader_create` - Assignment grading events
- `student_performance` - Performance tracking events

### System Events

- `api_call` - API endpoint usage
- `cache_hit` - Cached response usage
- `sso_login` - Single sign-on authentication
- `sso_logout` - User logout events

## Example Usage

JavaScript

```javascript
import StudyfetchSDK from '@studyfetch/sdk';

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

// Track individual user progress
const userEvents = await client.v1.usage.listEvents({
  userId: '[john.doe@school.edu](mailto:john.doe@school.edu)',
  eventType: 'test_completed',
  startDate: '2024-01-01T00:00:00Z',
  endDate: '2024-12-31T23:59:59Z',
  limit: 100,
  offset: 0
});

// Generate monthly group summary
const groupSummary = await client.v1.usage.getSummary({
  period: 'monthly',
  startDate: '2024-01-01T00:00:00Z',
  endDate: '2024-12-31T23:59:59Z',
  groupBy: 'group'
});

// Monitor feature adoption
const featureUsage = await client.v1.usage.listEvents({
  eventType: 'audio_recap_create',
  limit: 1000,
  groupId: 'class-101'
});

// Get usage statistics
const stats = await client.v1.usage.getStats({
  startDate: '2024-01-01T00:00:00Z',
  endDate: '2024-12-31T23:59:59Z',
  userId: '[john.doe@school.edu](mailto:john.doe@school.edu)'
});
```

Python

```python
from studyfetch_sdk import StudyfetchSDK

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

# Track individual user progress
user_events = client.v1.usage.list_events(
    user_id="john.doe@school.edu",
    event_type="test_completed",
    start_date="2024-01-01T00:00:00Z",
    end_date="2024-12-31T23:59:59Z",
    limit=100,
    offset=0
)

# Generate monthly group summary
group_summary = client.v1.usage.get_summary(
    period="monthly",
    start_date="2024-01-01T00:00:00Z",
    end_date="2024-12-31T23:59:59Z",
    group_by="group"
)

# Monitor feature adoption
feature_usage = client.v1.usage.list_events(
    event_type="audio_recap_create",
    limit=1000,
    group_id="class-101"
)

# Get usage statistics
stats = client.v1.usage.get_stats(
    start_date="2024-01-01T00:00:00Z",
    end_date="2024-12-31T23:59:59Z",
    user_id="john.doe@school.edu"
)
```

Java

```java
import com.studyfetch.javasdk.client.StudyfetchSdkClient;
import com.studyfetch.javasdk.client.okhttp.StudyfetchSdkOkHttpClient;
import com.studyfetch.javasdk.models.v1.usage.UsageListEventsParams;
import com.studyfetch.javasdk.models.v1.usage.UsageGetSummaryParams;
import com.studyfetch.javasdk.models.v1.usage.UsageGetStatsParams;

StudyfetchSdkClient client = StudyfetchSdkOkHttpClient.builder()
    .apiKey("your-api-key")
    .baseUrl("https://studyfetchapi.com")
    .build();

// Track individual user progress
UsageListEventsParams eventParams = UsageListEventsParams.builder()
    .userId("john.doe@school.edu")
    .eventType(UsageListEventsParams.EventType.TEST_COMPLETED)
    .startDate("2024-01-01T00:00:00Z")
    .endDate("2024-12-31T23:59:59Z")
    .limit(100.0)
    .offset(0.0)
    .build();
client.v1().usage().listEvents(eventParams);

// Generate monthly group summary
UsageGetSummaryParams summaryParams = UsageGetSummaryParams.builder()
    .period(UsageGetSummaryParams.Period.MONTHLY)
    .startDate("2024-01-01T00:00:00Z")
    .endDate("2024-12-31T23:59:59Z")
    .groupBy(UsageGetSummaryParams.GroupBy.GROUP)
    .build();
client.v1().usage().getSummary(summaryParams);

// Monitor feature adoption
UsageListEventsParams featureParams = UsageListEventsParams.builder()
    .eventType(UsageListEventsParams.EventType.AUDIO_RECAP_CREATE)
    .limit(1000.0)
    .groupId("class-101")
    .build();
client.v1().usage().listEvents(featureParams);

// Get usage statistics
UsageGetStatsParams statsParams = UsageGetStatsParams.builder()
    .startDate("2024-01-01T00:00:00Z")
    .endDate("2024-12-31T23:59:59Z")
    .userId("john.doe@school.edu")
    .build();
client.v1().usage().getStats(statsParams);
```

C#

```csharp
using StudyfetchSDK;
using StudyfetchSDK.Models.V1.Usage;
using System;
using System.Threading.Tasks;

public class UsageExample
{
    public static async Task Main()
    {
        var client = new StudyfetchSDKClient("your-api-key");

        // Track individual user progress
        await client.V1.Usage.ListEvents(new UsageListEventsParams
        {
            UserID = "john.doe@school.edu",
            EventType = UsageListEventsParams.EventType.TestCompleted,
            StartDate = "2024-01-01T00:00:00Z",
            EndDate = "2024-12-31T23:59:59Z",
            Limit = 100,
            Offset = 0
        });

        // Generate monthly group summary
        await client.V1.Usage.GetSummary(new UsageGetSummaryParams
        {
            Period = UsageGetSummaryParams.Period.Monthly,
            StartDate = "2024-01-01T00:00:00Z",
            EndDate = "2024-12-31T23:59:59Z",
            GroupBy = UsageGetSummaryParams.GroupBy.Group
        });

        // Monitor feature adoption
        await client.V1.Usage.ListEvents(new UsageListEventsParams
        {
            EventType = UsageListEventsParams.EventType.AudioRecapCreate,
            Limit = 1000,
            GroupID = "class-101"
        });

        // Get usage statistics with optional filters
        await client.V1.Usage.GetStats(new UsageGetStatsParams
        {
            StartDate = "2024-01-01T00:00:00Z",
            EndDate = "2024-12-31T23:59:59Z",
            UserID = "john.doe@school.edu",
            ComponentType = "chat",       // Optional: Filter by component type
            ResourceID = "comp_abc123"    // Optional: Filter by component ID
        });
    }
}
```

