> ## Documentation Index
> Fetch the complete documentation index at: https://www.studyfetch.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Assignment Grader

> AI-powered assignment grading with customizable rubrics, reusable templates, and educator analytics

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.

<iframe width="100%" height="400" src="https://studyfetchcdn.studyfetch.com/videos/features/assignment_grader.mp4" title="AssignmentGrader" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen />

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

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

<CodeGroup>
  ```javascript JavaScript theme={null}
  // 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 theme={null}
  # 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 theme={null}
  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
  ```

  ```csharp C# theme={null}
  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
  ```
</CodeGroup>

## 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`](#object-reference) | 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](#assignment-content). |
| `assignmentId`      | `string`                      | No              | Tag to group multiple submissions under one logical assignment (used by [educator reports](#generate-educator-report)).                                       |
| `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 theme={null}
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 theme={null}
{
  "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

<CodeGroup>
  ```javascript JavaScript theme={null}
  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", // 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 theme={null}
  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 theme={null}
  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());
  }
  ```

  ```csharp C# theme={null}
  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}");
      }
  }
  ```
</CodeGroup>

## Get All Assignment Graders

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

Returns every graded assignment for the authenticated organization.

### Example Request (curl)

```bash theme={null}
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`](#object-reference) objects.

### SDK Examples

<CodeGroup>
  ```javascript JavaScript theme={null}
  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 theme={null}
  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 theme={null}
  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());
  }
  ```

  ```csharp C# theme={null}
  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}");
          }
      }
  }
  ```
</CodeGroup>

## 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 theme={null}
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-reference) object. Returns `404 Not Found` if no assignment matches the ID.

### SDK Examples

<CodeGroup>
  ```javascript JavaScript theme={null}
  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 theme={null}
  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 theme={null}
  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());
  ```

  ```csharp C# theme={null}
  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}");
      }
  }
  ```
</CodeGroup>

## 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 theme={null}
curl -X DELETE https://api.yourapp.com/api/v1/assignment-grader/delete/665a1c0e8f3b1a0012abcdef \
  -H "x-api-key: YOUR_API_KEY"
```

### Response — `200 OK`

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

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

### SDK Examples

<CodeGroup>
  ```javascript JavaScript theme={null}
  const graderId = "grader_123456";
  await client.v1.assignmentGrader.delete(graderId);
  console.log("Assignment grader deleted successfully");
  ```

  ```python Python theme={null}
  grader_id = "grader_123456"
  client.v1.assignment_grader.delete(grader_id)
  print("Assignment grader deleted successfully")
  ```

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

  ```csharp C# theme={null}
  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");
      }
  }
  ```
</CodeGroup>

## 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 theme={null}
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 theme={null}
{
  "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

<CodeGroup>
  ```javascript JavaScript theme={null}
  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 theme={null}
  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 theme={null}
  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() + ")");
  }
  ```

  ```csharp C# theme={null}
  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})");
          }
      }
  }
  ```
</CodeGroup>

## 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[]`](#object-reference) | Yes      | Grading criteria. Must contain at least one entry.                                                        |

#### Example Request (curl)

```bash theme={null}
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`](#object-reference) document.

#### SDK Examples

<CodeGroup>
  ```javascript JavaScript theme={null}
  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 theme={null}
  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 theme={null}
  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());
  ```

  ```csharp C# theme={null}
  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}");
      }
  }
  ```
</CodeGroup>

### List Rubric Templates

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

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

#### Example Request (curl)

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

#### SDK Examples

<CodeGroup>
  ```javascript JavaScript theme={null}
  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 theme={null}
  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 theme={null}
  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());
  }
  ```

  ```csharp C# theme={null}
  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}");
          }
      }
  }
  ```
</CodeGroup>

### Get Rubric Template by ID

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

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

Returns a single [`RubricTemplate`](#object-reference). Returns `404 Not Found` if not found in your organization.

#### SDK Examples

<CodeGroup>
  ```javascript JavaScript theme={null}
  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 theme={null}
  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 theme={null}
  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());
  ```

  ```csharp C# theme={null}
  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}");
      }
  }
  ```
</CodeGroup>

### Delete Rubric Template

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

#### Response — `200 OK`

```json theme={null}
{ "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

<CodeGroup>
  ```javascript JavaScript theme={null}
  const templateId = "template-123";
  await client.v1.assignmentGrader.rubricTemplates.delete(templateId);
  console.log("Rubric template deleted successfully");
  ```

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

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

  ```csharp C# theme={null}
  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");
      }
  }
  ```
</CodeGroup>

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

## 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 theme={null}
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 theme={null}
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 theme={null}
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 theme={null}
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 theme={null}
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.
</Warning>

<CodeGroup>
  ```javascript JavaScript theme={null}
  // 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"
  });
  ```

  ```python Python theme={null}
  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 theme={null}
  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);
  ```

  ```csharp C# theme={null}
  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"
          });
      }
  }
  ```
</CodeGroup>

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

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

<CodeGroup>
  ```javascript JavaScript theme={null}
  // 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"
  });
  ```

  ```python Python theme={null}
  # 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 theme={null}
  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);
  ```

  ```csharp C# theme={null}
  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"
          });
      }
  }
  ```
</CodeGroup>

## 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 theme={null}
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`.

<CodeGroup>
  ```javascript JavaScript theme={null}
  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 theme={null}
  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}")
  ```
</CodeGroup>

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

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

```bash theme={null}
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."
  }'
```

<CodeGroup>
  ```javascript JavaScript theme={null}
  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 theme={null}
  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}")
  ```
</CodeGroup>

**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 theme={null}
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.

<CodeGroup>
  ```javascript JavaScript theme={null}
  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 theme={null}
  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}")
  ```
</CodeGroup>

## Inline Rubric with Assignment Content (No Template)

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

<CodeGroup>
  ```javascript JavaScript theme={null}
  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 theme={null}
  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}")
  ```
</CodeGroup>

## 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 theme={null}
{
  "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"*).
