Class Forge exposes a REST API under the /api/v1 prefix. All request and
response bodies use JSON.
Returns the server health status.
Response 200 OK
{
"status": "ok"
}Create a new classroom. The organization (Forgejo) or group (GitLab) must already exist on the platform.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Classroom name (1-255 chars) |
description |
string | no | Classroom description |
organization_name |
string | yes | Platform organization/group name |
public |
boolean | no | Whether the classroom is publicly visible (default: false) |
Response 201 Created
{
"id": 1,
"name": "Introduction to CS",
"slug": "introduction-to-cs",
"description": "CS 101",
"organization_id": 42,
"public": false,
"archived": false,
"created_at": "2026-03-28T10:00:00Z",
"updated_at": "2026-03-28T10:00:00Z"
}Errors
| Status | Code | Cause |
|---|---|---|
| 400 | VALIDATION_ERROR | Missing or invalid fields |
| 409 | RESOURCE_ALREADY_EXISTS | Classroom with same slug exists |
| 502 | INTEGRATION_PLATFORM_API_ERROR | Platform API call failed |
List all classrooms with pagination.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 | Page number |
per_page |
integer | 20 | Items per page (max 100) |
Response 200 OK
{
"classrooms": [...],
"total": 15,
"page": 1,
"per_page": 20,
"total_pages": 1
}Get a classroom by ID.
Response 200 OK - Classroom object
Errors 404 if not found
Update a classroom. Only provided fields are modified.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | New name |
description |
string | no | New description |
public |
boolean | no | New visibility |
Response 200 OK - Updated classroom object
Delete a classroom.
Response 204 No Content
Archive a classroom. Archived classrooms are read-only.
Response 200 OK - Archived classroom object
Get classroom statistics.
Response 200 OK
{
"classroom_id": 1,
"assignment_count": 5,
"student_count": 30,
"submission_count": 120
}Create a new assignment in a classroom.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Assignment name (1-255 chars) |
description |
string | no | Assignment description |
template_repository |
string | yes | Template repo in owner/name format |
max_team_size |
integer | no | Max team size (default: 1 for individual) |
deadline |
string (RFC3339) | no | Submission deadline |
Response 201 Created
{
"id": 1,
"classroom_id": 1,
"name": "Homework 1",
"slug": "homework-1",
"description": "Implement a linked list",
"template_repository_id": 100,
"max_team_size": 1,
"deadline": "2026-04-15T23:59:59Z",
"created_at": "2026-03-28T10:00:00Z",
"updated_at": "2026-03-28T10:00:00Z"
}Errors
| Status | Code | Cause |
|---|---|---|
| 400 | VALIDATION_ERROR | Missing or invalid fields |
| 404 | RESOURCE_NOT_FOUND | Classroom does not exist |
| 409 | RESOURCE_ALREADY_EXISTS | Assignment with same slug exists in classroom |
List assignments in a classroom.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 | Page number |
per_page |
integer | 20 | Items per page (max 100) |
Response 200 OK
{
"assignments": [...],
"total": 5,
"page": 1,
"per_page": 20,
"total_pages": 1
}Get an assignment by ID.
Response 200 OK - Assignment object
Update an assignment. Only provided fields are modified.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | New name |
description |
string | no | New description |
max_team_size |
integer | no | New max team size |
deadline |
string (RFC3339) | no | New deadline |
Response 200 OK - Updated assignment object
Delete an assignment.
Response 204 No Content
Get assignment statistics.
Response 200 OK
{
"assignment_id": 1,
"submission_count": 25,
"team_count": 5,
"accepted_count": 20
}Add a student to a classroom roster.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
student_id |
string | yes | Institutional student identifier |
name |
string | yes | Student full name |
email |
string | yes | Student email address |
Response 201 Created
{
"id": 1,
"classroom_id": 1,
"student_id": "alice123",
"name": "Alice Smith",
"email": "alice@university.edu",
"platform_username": "",
"platform_user_id": 0,
"created_at": "2026-03-28T10:00:00Z",
"updated_at": "2026-03-28T10:00:00Z"
}List students in a classroom roster.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 | Page number |
per_page |
integer | 20 | Items per page (max 100) |
Response 200 OK
{
"students": [...],
"total": 30,
"page": 1,
"per_page": 20,
"total_pages": 2
}Link a roster entry to a platform account. This enables the student to accept assignments and receive repository access.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
platform_username |
string | yes | Username on the Git platform |
Response 200 OK - Updated roster entry
Remove a student from the roster.
Response 204 No Content
Accept an assignment for a student. This creates a repository from the template and grants the student access.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
student_id |
integer | yes | Roster entry ID of the student |
team_id |
integer | no | Team ID (for team assignments) |
Response 201 Created
{
"id": 1,
"assignment_id": 1,
"student_id": 1,
"repository_id": 999,
"repository_url": "https://forgejo.example.com/org/hw1-alice-smith.git",
"status": "active",
"created_at": "2026-03-28T10:05:00Z"
}Errors
| Status | Code | Cause |
|---|---|---|
| 400 | BUSINESS_DEADLINE_PASSED | Assignment deadline has passed |
| 404 | RESOURCE_NOT_FOUND | Assignment or student not found |
| 409 | RESOURCE_ALREADY_EXISTS | Student already accepted this assignment |
Get a submission by ID.
Response 200 OK - Submission object
List submissions for an assignment.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 | Page number |
per_page |
integer | 20 | Items per page (max 100) |
Response 200 OK
{
"submissions": [...],
"total": 25,
"page": 1,
"per_page": 20,
"total_pages": 2
}List all submissions across all assignments in a classroom.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 | Page number |
per_page |
integer | 20 | Items per page (max 100) |
Response 200 OK - Same format as above
Create a team for a team-based assignment. The creator becomes the team leader.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Team name (1-255 chars) |
description |
string | no | Team description |
leader_student_id |
integer | yes | Roster entry ID of the team leader |
Response 201 Created
{
"id": 1,
"assignment_id": 2,
"name": "Team Alpha",
"slug": "team-alpha",
"description": "",
"leader_id": 1,
"member_count": 1,
"created_at": "2026-03-28T10:00:00Z"
}Errors
| Status | Code | Cause |
|---|---|---|
| 400 | BUSINESS_OPERATION_NOT_ALLOWED | Assignment is individual-only |
| 404 | RESOURCE_NOT_FOUND | Assignment not found |
| 409 | RESOURCE_ALREADY_EXISTS | Team with same slug exists |
Get a team with its member list.
Response 200 OK
{
"team": {
"id": 1,
"assignment_id": 2,
"name": "Team Alpha",
"slug": "team-alpha",
"leader_id": 1,
"member_count": 3
},
"members": [
{"student_id": 1, "role": "leader", "name": "Alice Smith"},
{"student_id": 2, "role": "member", "name": "Bob Jones"},
{"student_id": 3, "role": "member", "name": "Carol Lee"}
]
}Add a student to a team.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
student_id |
integer | yes | Roster entry ID of the student |
Response 200 OK
Errors
| Status | Code | Cause |
|---|---|---|
| 400 | BUSINESS_TEAM_SIZE_EXCEEDED | Team is full |
| 409 | RESOURCE_ALREADY_EXISTS | Student is already a member |
Remove a student from a team. Team leaders cannot leave; they must delete the team instead.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
student_id |
integer | yes | Roster entry ID of the student |
Response 200 OK
Errors
| Status | Code | Cause |
|---|---|---|
| 400 | BUSINESS_OPERATION_NOT_ALLOWED | Team leader cannot leave |
Delete a team and remove all members.
Response 204 No Content
List teams for an assignment.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 | Page number |
per_page |
integer | 20 | Items per page (max 100) |
Response 200 OK
{
"teams": [...],
"total": 8,
"page": 1,
"per_page": 20,
"total_pages": 1
}All error responses follow a consistent format:
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "classroom 42 not found"
}
}| Code | HTTP Status | Description |
|---|---|---|
VALIDATION_ERROR |
400 | Request validation failed |
RESOURCE_NOT_FOUND |
404 | Requested resource does not exist |
RESOURCE_ALREADY_EXISTS |
409 | Resource with same identifier exists |
BUSINESS_OPERATION_NOT_ALLOWED |
400 | Operation not permitted by business rules |
BUSINESS_DEADLINE_PASSED |
400 | Assignment deadline has passed |
BUSINESS_TEAM_SIZE_EXCEEDED |
400 | Team has reached maximum size |
BUSINESS_ROSTER_NOT_FOUND |
400 | Student not in classroom roster |
INTEGRATION_PLATFORM_API_ERROR |
502 | Git platform API call failed |
INTEGRATION_PLATFORM_RATE_LIMITED |
429 | Platform API rate limit exceeded |
INTEGRATION_PLATFORM_UNAVAILABLE |
503 | Platform is temporarily unavailable |
Validation errors include field-level details:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "validation failed",
"details": [
{"field": "name", "message": "Name is required"},
{"field": "email", "message": "Email must be a valid email address"}
]
}
}