Install
openclaw skills install google-classroomGoogle Classroom API integration with managed OAuth. Manage courses, assignments, students, teachers, and announcements. Use this skill when users want to create courses, manage coursework, track student submissions, or post announcements. For other third party apps, use the api-gateway skill (https://clawhub.ai/byungkyu/api-gateway). Requires network access and valid Maton API key.
openclaw skills install google-classroomAccess the Google Classroom API with managed OAuth authentication. Manage courses, coursework, students, teachers, announcements, and submissions.
# List all courses
python3 <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/google-classroom/v1/courses')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
https://api.maton.ai/google-classroom/{api-path}
The Google Classroom API uses the path pattern:
https://api.maton.ai/google-classroom/v1/{resource}
All requests require the Maton API key in the Authorization header:
Authorization: Bearer $MATON_API_KEY
Environment Variable: Set your API key as MATON_API_KEY:
export MATON_API_KEY="YOUR_API_KEY"
Manage your Google Classroom OAuth connections at https://api.maton.ai.
python3 <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections?app=google-classroom&status=ACTIVE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
python3 <<'EOF'
import urllib.request, os, json
data = json.dumps({'app': 'google-classroom'}).encode()
req = urllib.request.Request('https://api.maton.ai/connections', data=data, method='POST')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
python3 <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections/{connection_id}')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Response:
{
"connection": {
"connection_id": "{connection_id}",
"status": "ACTIVE",
"creation_time": "2026-02-14T00:00:00.000000Z",
"last_updated_time": "2026-02-14T00:00:00.000000Z",
"url": "https://connect.maton.ai/?session_token=...",
"app": "google-classroom",
"metadata": {}
}
}
Open the returned url in a browser to complete OAuth authorization.
python3 <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections/{connection_id}', method='DELETE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
If you have multiple Google Classroom connections, specify which one to use with the Maton-Connection header:
python3 <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/google-classroom/v1/courses')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Maton-Connection', '{connection_id}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
If you have multiple connections, always include this header to ensure requests go to the intended account.
GET /v1/courses
GET /v1/courses?courseStates=ACTIVE
GET /v1/courses?teacherId=me
GET /v1/courses?studentId=me
GET /v1/courses?pageSize=10
Query Parameters:
courseStates - Filter by state: ACTIVE, ARCHIVED, PROVISIONED, DECLINED, SUSPENDEDteacherId - Filter by teacher ID (use me for current user)studentId - Filter by student ID (use me for current user)pageSize - Number of results per page (max 100)pageToken - Token for next pageResponse:
{
"courses": [
{
"id": "825635865485",
"name": "Introduction to Programming",
"section": "Section A",
"descriptionHeading": "CS 101",
"description": "Learn the basics of programming",
"ownerId": "102753038276005039640",
"creationTime": "2026-02-14T01:53:58.991Z",
"updateTime": "2026-02-14T01:53:58.991Z",
"enrollmentCode": "3qsua37m",
"courseState": "ACTIVE",
"alternateLink": "https://classroom.google.com/c/ODI1NjM1ODY1NDg1",
"guardiansEnabled": false
}
],
"nextPageToken": "..."
}
GET /v1/courses/{courseId}
POST /v1/courses
Content-Type: application/json
{
"name": "Course Name",
"section": "Section A",
"descriptionHeading": "Course Title",
"description": "Course description",
"ownerId": "me"
}
Response:
{
"id": "825637533405",
"name": "Course Name",
"section": "Section A",
"ownerId": "102753038276005039640",
"courseState": "PROVISIONED",
"enrollmentCode": "abc123"
}
PATCH /v1/courses/{courseId}?updateMask=name,description
Content-Type: application/json
{
"name": "Updated Course Name",
"description": "Updated description"
}
Note: Use updateMask query parameter to specify which fields to update.
DELETE /v1/courses/{courseId}
Note: Courses must be archived before deletion. To archive, update the course with courseState: "ARCHIVED".
GET /v1/courses/{courseId}/courseWork
GET /v1/courses/{courseId}/courseWork?courseWorkStates=PUBLISHED
GET /v1/courses/{courseId}/courseWork?orderBy=dueDate
Query Parameters:
courseWorkStates - Filter by state: PUBLISHED, DRAFT, DELETEDorderBy - Sort by: dueDate, updateTimepageSize - Number of results per pagepageToken - Token for next pageGET /v1/courses/{courseId}/courseWork/{courseWorkId}
POST /v1/courses/{courseId}/courseWork
Content-Type: application/json
{
"title": "Assignment Title",
"description": "Assignment description",
"workType": "ASSIGNMENT",
"state": "PUBLISHED",
"maxPoints": 100,
"dueDate": {
"year": 2026,
"month": 3,
"day": 15
},
"dueTime": {
"hours": 23,
"minutes": 59
}
}
Work Types:
ASSIGNMENT - Regular assignmentSHORT_ANSWER_QUESTION - Short answer questionMULTIPLE_CHOICE_QUESTION - Multiple choice questionStates:
DRAFT - Not visible to studentsPUBLISHED - Visible to studentsPATCH /v1/courses/{courseId}/courseWork/{courseWorkId}?updateMask=title,description
Content-Type: application/json
{
"title": "Updated Title",
"description": "Updated description"
}
DELETE /v1/courses/{courseId}/courseWork/{courseWorkId}
GET /v1/courses/{courseId}/courseWork/{courseWorkId}/studentSubmissions
GET /v1/courses/{courseId}/courseWork/{courseWorkId}/studentSubmissions?states=TURNED_IN
Query Parameters:
states - Filter by state: NEW, CREATED, TURNED_IN, RETURNED, RECLAIMED_BY_STUDENTuserId - Filter by student IDpageSize - Number of results per pagepageToken - Token for next pageNote: Course work must be in PUBLISHED state to list submissions.
Response:
{
"studentSubmissions": [
{
"courseId": "825635865485",
"courseWorkId": "825637404958",
"id": "Cg4I8ufNwwYQ7tSZgYIB",
"userId": "102753038276005039640",
"creationTime": "2026-02-14T02:30:00.000Z",
"state": "NEW",
"alternateLink": "https://classroom.google.com/..."
}
]
}
GET /v1/courses/{courseId}/courseWork/{courseWorkId}/studentSubmissions/{submissionId}
PATCH /v1/courses/{courseId}/courseWork/{courseWorkId}/studentSubmissions/{submissionId}?updateMask=assignedGrade,draftGrade
Content-Type: application/json
{
"assignedGrade": 95,
"draftGrade": 95
}
POST /v1/courses/{courseId}/courseWork/{courseWorkId}/studentSubmissions/{submissionId}:return
Content-Type: application/json
{}
GET /v1/courses/{courseId}/teachers
Response:
{
"teachers": [
{
"courseId": "825635865485",
"userId": "102753038276005039640",
"profile": {
"id": "102753038276005039640",
"name": {
"givenName": "John",
"familyName": "Doe",
"fullName": "John Doe"
},
"emailAddress": "john.doe@example.com"
}
}
]
}
GET /v1/courses/{courseId}/teachers/{userId}
POST /v1/courses/{courseId}/teachers
Content-Type: application/json
{
"userId": "teacher@example.com"
}
DELETE /v1/courses/{courseId}/teachers/{userId}
GET /v1/courses/{courseId}/students
GET /v1/courses/{courseId}/students/{userId}
POST /v1/courses/{courseId}/students
Content-Type: application/json
{
"userId": "student@example.com"
}
DELETE /v1/courses/{courseId}/students/{userId}
GET /v1/courses/{courseId}/announcements
GET /v1/courses/{courseId}/announcements?announcementStates=PUBLISHED
GET /v1/courses/{courseId}/announcements/{announcementId}
POST /v1/courses/{courseId}/announcements
Content-Type: application/json
{
"text": "Announcement text content",
"state": "PUBLISHED"
}
States:
DRAFT - Not visible to studentsPUBLISHED - Visible to studentsPATCH /v1/courses/{courseId}/announcements/{announcementId}?updateMask=text
Content-Type: application/json
{
"text": "Updated announcement text"
}
DELETE /v1/courses/{courseId}/announcements/{announcementId}
GET /v1/courses/{courseId}/topics
GET /v1/courses/{courseId}/topics/{topicId}
POST /v1/courses/{courseId}/topics
Content-Type: application/json
{
"name": "Topic Name"
}
PATCH /v1/courses/{courseId}/topics/{topicId}?updateMask=name
Content-Type: application/json
{
"name": "Updated Topic Name"
}
DELETE /v1/courses/{courseId}/topics/{topicId}
GET /v1/courses/{courseId}/courseWorkMaterials
GET /v1/courses/{courseId}/courseWorkMaterials/{courseWorkMaterialId}
GET /v1/invitations?courseId={courseId}
GET /v1/invitations?userId=me
Note: Either courseId or userId is required.
POST /v1/invitations
Content-Type: application/json
{
"courseId": "825635865485",
"userId": "user@example.com",
"role": "STUDENT"
}
Roles:
STUDENTTEACHEROWNERPOST /v1/invitations/{invitationId}:accept
DELETE /v1/invitations/{invitationId}
GET /v1/userProfiles/me
Response:
{
"id": "102753038276005039640",
"name": {
"givenName": "John",
"familyName": "Doe",
"fullName": "John Doe"
},
"emailAddress": "john.doe@example.com",
"permissions": [
{
"permission": "CREATE_COURSE"
}
],
"verifiedTeacher": false
}
GET /v1/userProfiles/{userId}
GET /v1/courses/{courseId}/aliases
The Google Classroom API uses token-based pagination. Responses include a nextPageToken when more results are available.
GET /v1/courses?pageSize=10
Response:
{
"courses": [...],
"nextPageToken": "Ci8KLRIrEikKDmIMCLK8v8wGEIDQrsYBCgsI..."
}
To get the next page:
GET /v1/courses?pageSize=10&pageToken=Ci8KLRIrEikKDmIMCLK8v8wGEIDQrsYBCgsI...
// List all courses
const response = await fetch(
'https://api.maton.ai/google-classroom/v1/courses',
{
headers: {
'Authorization': `Bearer ${process.env.MATON_API_KEY}`
}
}
);
const data = await response.json();
console.log(data.courses);
import os
import requests
# List all courses
response = requests.get(
'https://api.maton.ai/google-classroom/v1/courses',
headers={'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}'}
)
data = response.json()
print(data['courses'])
import os
import requests
course_id = "825635865485"
# Create an assignment
assignment = {
"title": "Week 1 Homework",
"description": "Complete exercises 1-10",
"workType": "ASSIGNMENT",
"state": "PUBLISHED",
"maxPoints": 100,
"dueDate": {"year": 2026, "month": 3, "day": 15},
"dueTime": {"hours": 23, "minutes": 59}
}
response = requests.post(
f'https://api.maton.ai/google-classroom/v1/courses/{course_id}/courseWork',
headers={
'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}',
'Content-Type': 'application/json'
},
json=assignment
)
print(response.json())
updateMask query parameter specifying which fields to updatecourseState: "ARCHIVED") before they can be deletedPUBLISHED state to access student submissionsme to refer to the current authenticated user{year, month, day} format; times use {hours, minutes} formatjq or other commands, environment variables like $MATON_API_KEY may not expand correctly in some shell environments| Status | Meaning |
|---|---|
| 400 | Bad request, invalid argument, or precondition failed |
| 401 | Invalid API key or expired token |
| 403 | Permission denied |
| 404 | Resource not found |
| 409 | Conflict (e.g., user already enrolled) |
| 429 | Rate limited |
| 4xx/5xx | Passthrough error from Google Classroom API |
Precondition check failed (400)
Permission denied (403)