Skip to main content
RapidDev - Software Development Agency
replit-integrationsStandard API Integration

How to Integrate Replit with Canvas LMS

To integrate Replit with Canvas LMS, generate a personal access token in Canvas, store it in Replit Secrets (lock icon ๐Ÿ”’) as CANVAS_API_TOKEN, and call the Canvas REST API from your Replit server. You can read courses, assignments, and student grades, or post grades and announcements programmatically. Use an Autoscale deployment for webhook receivers and a Reserved VM for polling-based sync jobs.

What you'll learn

  • How to generate a Canvas API access token and store it securely in Replit Secrets
  • How to make authenticated Canvas REST API calls from Node.js and Python
  • How to handle Canvas API pagination using Link headers
  • How to read course enrollments and post grades programmatically
  • When to use personal access tokens versus OAuth 2.0 for Canvas integrations
Book a free consultation
4.9Clutch rating โญ
600+Happy partners
17+Countries served
190+Team members
Intermediate15 min read20 minutesOtherMarch 2026RapidDev Engineering Team
TL;DR

To integrate Replit with Canvas LMS, generate a personal access token in Canvas, store it in Replit Secrets (lock icon ๐Ÿ”’) as CANVAS_API_TOKEN, and call the Canvas REST API from your Replit server. You can read courses, assignments, and student grades, or post grades and announcements programmatically. Use an Autoscale deployment for webhook receivers and a Reserved VM for polling-based sync jobs.

Canvas LMS API Integration from Replit

Canvas LMS is the dominant enterprise learning management system in higher education, used by thousands of universities and school districts worldwide. Its REST API gives developers access to the full course management data model: institutions, courses, sections, enrollments, assignments, submissions, grades, discussions, pages, and files. For Replit developers, Canvas integration opens up use cases from automated grading tools and attendance dashboards to custom student analytics and learning management automation.

The Canvas API uses Bearer token authentication. For server-side integrations where you control both the Canvas account and the Replit application, a personal access token is the quickest path โ€” generate one in Canvas Account Settings and use it for all API calls. For apps where students or teachers authenticate themselves (third-party LTI tools, OAuth apps), you need to configure an OAuth 2.0 developer key in Canvas Admin, but that is a more complex setup. Most Replit-based integrations start with a personal access token.

Canvas's REST API returns paginated results for list endpoints. Instead of returning all 500 students in one response, Canvas returns 10-100 records per page with a Link header containing the URL for the next page. Your integration code must follow these pagination headers to retrieve complete data sets. Failing to handle pagination is the most common issue with Canvas integrations โ€” you get partial results that look complete for small classes but silently miss records in large ones.

Integration method

Standard API Integration

Canvas LMS provides a REST API authenticated with a personal access token or OAuth 2.0. For server-to-server integrations on Replit, a personal access token is the simplest approach โ€” generate one in Canvas Account Settings, store it in Replit Secrets, and include it as a Bearer token in HTTP request headers. The Canvas API supports reading courses, assignments, enrollments, grades, discussions, and pages, and writing grades, submissions, and announcements. All API responses are paginated using Link headers.

Prerequisites

  • A Replit account with a Node.js or Python Repl ready
  • Access to a Canvas LMS instance (your institution's Canvas URL, e.g., yourschool.instructure.com)
  • A Canvas user account with Teacher or Admin role in the relevant courses
  • A personal access token generated in Canvas Account Settings (instructions in Step 1)

Step-by-step guide

1

Generate a Canvas API Access Token

Canvas personal access tokens are generated within the Canvas web interface. Log into your Canvas instance and click your profile picture or account icon in the top right corner. Select 'Account' or 'Settings' from the menu. On the Settings page, scroll down to the 'Approved Integrations' section and click 'New Access Token'. In the dialog, enter a purpose (e.g., 'Replit Integration') and an optional expiration date. For development, you can leave expiration blank; for production integrations, set an expiration and rotate tokens periodically. Click 'Generate Token'. Canvas shows you the token value once โ€” it will not be displayed again. Copy the full token string immediately. It typically looks like a long alphanumeric string starting with digits. Note your Canvas base URL (e.g., https://yourschool.instructure.com or https://canvas.instructure.com for the hosted version). You will need this as the API base URL. All Canvas REST API endpoints follow the pattern: {base_url}/api/v1/{resource}. For institutional Canvas instances where you are an admin, you can also generate access tokens with specific scopes from Admin โ†’ Developer Keys. This is more secure for production as it limits what the token can access. For getting started, a personal access token with your own permissions is the simplest approach.

Pro tip: If you are an instructor or admin at an institution, your personal access token will have the same permissions as your Canvas account. Be careful using admin tokens in automated tools โ€” a bug that writes incorrect grades to the wrong courses can affect real students. Test in a sandbox course first.

Expected result: You have a Canvas personal access token string and your Canvas base URL. The token grants API access equivalent to your Canvas account's role and permissions.

2

Store Canvas Credentials in Replit Secrets

Your Canvas API token must never appear in source code. Click the lock icon (๐Ÿ”’) in the left Replit sidebar to open the Secrets pane and add the following secrets: CANVAS_API_TOKEN: the full access token string generated in Step 1. CANVAS_BASE_URL: your Canvas instance URL without a trailing slash (e.g., https://yourschool.instructure.com or https://canvas.instructure.com). In your code, read these values from environment variables using process.env.CANVAS_API_TOKEN (Node.js) or os.environ['CANVAS_API_TOKEN'] (Python). Include the token as a Bearer token in the HTTP Authorization header of every API request: Authorization: Bearer {token}. Replit's Secret Scanner monitors your code files for credential patterns. If you accidentally paste a Canvas token into a code file, Replit will warn you. Always use Secrets as the authoritative source for credentials. For production deployments with multiple users or courses, consider storing the token in a database with encryption rather than as a single environment variable โ€” this allows you to support multiple Canvas instances or rotate tokens without redeploying.

check-canvas-secrets.js
1// Verify Canvas secrets are set before starting server
2const required = ['CANVAS_API_TOKEN', 'CANVAS_BASE_URL'];
3for (const key of required) {
4 if (!process.env[key]) {
5 throw new Error(`Missing secret: ${key}. Set it in Replit Secrets (lock icon ๐Ÿ”’).`);
6 }
7}
8console.log('Canvas config OK. Base URL:', process.env.CANVAS_BASE_URL);

Pro tip: Canvas access tokens carry full account permissions. Use a dedicated Canvas account with only the course-level access your integration needs rather than an admin account. This limits the impact if the token is ever compromised.

Expected result: Both CANVAS_API_TOKEN and CANVAS_BASE_URL appear in the Replit Secrets pane. The startup check prints the base URL without errors.

3

Read Courses and Handle Pagination (Node.js)

Install the required packages in the Shell tab: npm install axios express. Axios handles HTTP requests and the Link header parsing for Canvas pagination. All Canvas list endpoints are paginated โ€” the API returns a Link header in each response with URLs for the next, prev, first, and last pages. You must follow the next link until no next page is returned to retrieve all records. The Canvas API returns per-page limits that vary by endpoint (typically 10-100 items). You can request up to 100 items per page with the ?per_page=100 query parameter. Always include this parameter to minimize the number of round trips for large courses. Authentication is simple: include Authorization: Bearer {token} as a header on every request. The base URL for all API calls is {CANVAS_BASE_URL}/api/v1/. For example, to list all courses for the authenticated user: GET /api/v1/courses. To list enrollments in course 12345: GET /api/v1/courses/12345/enrollments. Error handling is important: Canvas returns HTTP 401 for invalid tokens, 403 for permission errors (the token does not have access to that resource), and 404 for resources that do not exist (wrong course ID, deleted assignment). Always check the status code and handle these cases explicitly in production code.

canvas.js
1// canvas.js โ€” Canvas LMS REST API integration for Node.js on Replit
2const axios = require('axios');
3const express = require('express');
4
5const BASE_URL = process.env.CANVAS_BASE_URL;
6const TOKEN = process.env.CANVAS_API_TOKEN;
7
8const canvasApi = axios.create({
9 baseURL: `${BASE_URL}/api/v1`,
10 headers: { Authorization: `Bearer ${TOKEN}` }
11});
12
13// Follow Canvas pagination to get all results
14async function getAllPages(url, params = {}) {
15 const results = [];
16 let nextUrl = url;
17 let nextParams = { ...params, per_page: 100 };
18
19 while (nextUrl) {
20 const response = await canvasApi.get(nextUrl, { params: nextParams });
21 results.push(...response.data);
22
23 // Parse Link header for next page URL
24 const linkHeader = response.headers['link'] || '';
25 const nextMatch = linkHeader.match(/<([^>]+)>;\s*rel="next"/);
26 nextUrl = nextMatch ? nextMatch[1] : null;
27 nextParams = {}; // URL already includes params after first page
28 }
29
30 return results;
31}
32
33const app = express();
34app.use(express.json());
35
36// List all courses for the authenticated user
37app.get('/api/courses', async (req, res) => {
38 try {
39 const courses = await getAllPages('/courses', { enrollment_type: 'teacher' });
40 res.json({ courses: courses.map(c => ({ id: c.id, name: c.name, code: c.course_code })) });
41 } catch (err) {
42 res.status(err.response?.status || 500).json({ error: err.message });
43 }
44});
45
46// Get all enrollments for a course
47app.get('/api/courses/:courseId/students', async (req, res) => {
48 try {
49 const enrollments = await getAllPages(`/courses/${req.params.courseId}/enrollments`, {
50 type: ['StudentEnrollment'],
51 state: ['active']
52 });
53 const students = enrollments.map(e => ({
54 id: e.user_id,
55 name: e.user?.name,
56 email: e.user?.login_id
57 }));
58 res.json({ students, total: students.length });
59 } catch (err) {
60 res.status(err.response?.status || 500).json({ error: err.message });
61 }
62});
63
64// Get assignments for a course
65app.get('/api/courses/:courseId/assignments', async (req, res) => {
66 try {
67 const assignments = await getAllPages(`/courses/${req.params.courseId}/assignments`);
68 res.json({
69 assignments: assignments.map(a => ({
70 id: a.id,
71 name: a.name,
72 due_at: a.due_at,
73 points_possible: a.points_possible
74 }))
75 });
76 } catch (err) {
77 res.status(err.response?.status || 500).json({ error: err.message });
78 }
79});
80
81// Post a grade for a student submission
82app.put('/api/courses/:courseId/assignments/:assignmentId/grade', async (req, res) => {
83 const { courseId, assignmentId } = req.params;
84 const { studentId, score, comment } = req.body;
85 try {
86 const response = await canvasApi.put(
87 `/courses/${courseId}/assignments/${assignmentId}/submissions/${studentId}`,
88 {
89 submission: { posted_grade: score },
90 comment: comment ? { text_comment: comment } : undefined
91 }
92 );
93 res.json({ success: true, submission: response.data });
94 } catch (err) {
95 res.status(err.response?.status || 500).json({ error: err.message });
96 }
97});
98
99app.listen(3000, '0.0.0.0', () => console.log('Canvas server running on port 3000'));

Pro tip: Always use the getAllPages helper for list endpoints โ€” many instructors have 200+ students and Canvas defaults to returning only 10-100 at a time. Forgetting to paginate is the most common source of incomplete data bugs in Canvas integrations.

Expected result: GET /api/courses returns the authenticated user's courses. GET /api/courses/{id}/students returns all enrolled students with pagination handled. PUT /api/courses/{courseId}/assignments/{assignmentId}/grade posts a grade to Canvas.

4

Python Integration with Canvas API

For Python Replit projects, install requests in the Shell tab: pip install requests flask. The canvasapi Python library is a higher-level wrapper that handles pagination automatically, but the requests library gives more direct control and is easier to understand. Create a Canvas session using requests.Session() with the Authorization header set once on the session object. All subsequent requests from that session automatically include the auth header. Use the same getAllPages pattern as in Node.js โ€” check the Link header in the response for rel='next' to follow pagination. The Canvas gradebook is accessible through the Submissions API: GET /api/v1/courses/{course_id}/submissions/summary for an overview, and PUT /api/v1/courses/{course_id}/assignments/{assignment_id}/submissions/{user_id} to update a grade. The grade value can be a number (score out of points_possible) or a letter grade if the assignment uses letter grading. For background grade sync jobs that run on a schedule, deploy the Replit app as a Reserved VM so it stays running continuously. For webhook-based integrations (Canvas can send webhooks for submission events via its live events system), deploy as Autoscale โ€” the server only needs to handle incoming HTTP requests when Canvas sends events.

canvas_api.py
1# canvas_api.py โ€” Canvas LMS REST API integration for Python on Replit
2import os
3import requests
4from flask import Flask, request, jsonify
5
6BASE_URL = os.environ['CANVAS_BASE_URL']
7TOKEN = os.environ['CANVAS_API_TOKEN']
8
9# Create a session with Canvas auth header
10session = requests.Session()
11session.headers.update({'Authorization': f'Bearer {TOKEN}'})
12
13def get_all_pages(path: str, params: dict = None) -> list:
14 """Fetch all pages from a paginated Canvas API endpoint."""
15 results = []
16 url = f'{BASE_URL}/api/v1{path}'
17 req_params = {'per_page': 100, **(params or {})}
18
19 while url:
20 response = session.get(url, params=req_params)
21 response.raise_for_status()
22 results.extend(response.json())
23
24 # Follow Link header for next page
25 link_header = response.headers.get('Link', '')
26 next_url = None
27 for part in link_header.split(','):
28 if 'rel="next"' in part:
29 next_url = part.split(';')[0].strip().strip('<>')
30 break
31 url = next_url
32 req_params = {} # Params embedded in next URL
33
34 return results
35
36def post_grade(course_id: int, assignment_id: int, student_id: int, score, comment: str = None):
37 """Post a grade for a student submission."""
38 url = f'{BASE_URL}/api/v1/courses/{course_id}/assignments/{assignment_id}/submissions/{student_id}'
39 payload = {'submission': {'posted_grade': score}}
40 if comment:
41 payload['comment'] = {'text_comment': comment}
42 response = session.put(url, json=payload)
43 response.raise_for_status()
44 return response.json()
45
46app = Flask(__name__)
47
48@app.route('/api/courses')
49def list_courses():
50 try:
51 courses = get_all_pages('/courses', {'enrollment_type': 'teacher'})
52 return jsonify({'courses': [{'id': c['id'], 'name': c['name']} for c in courses]})
53 except requests.HTTPError as e:
54 return jsonify({'error': str(e)}), e.response.status_code
55
56@app.route('/api/courses/<int:course_id>/students')
57def list_students(course_id):
58 try:
59 enrollments = get_all_pages(f'/courses/{course_id}/enrollments',
60 {'type[]': 'StudentEnrollment', 'state[]': 'active'})
61 students = [{'id': e['user_id'], 'name': e.get('user', {}).get('name')} for e in enrollments]
62 return jsonify({'students': students, 'total': len(students)})
63 except requests.HTTPError as e:
64 return jsonify({'error': str(e)}), e.response.status_code
65
66@app.route('/api/grade', methods=['POST'])
67def grade_submission():
68 data = request.get_json()
69 try:
70 result = post_grade(
71 data['course_id'], data['assignment_id'],
72 data['student_id'], data['score'],
73 data.get('comment')
74 )
75 return jsonify({'success': True, 'submission': result})
76 except requests.HTTPError as e:
77 return jsonify({'error': str(e)}), e.response.status_code
78
79if __name__ == '__main__':
80 app.run(host='0.0.0.0', port=3000)

Pro tip: The canvasapi Python library (pip install canvasapi) provides a higher-level object-oriented interface that handles pagination automatically. It is useful for complex integrations but adds a dependency. For simple read/write operations, plain requests with the get_all_pages helper is more transparent.

Expected result: GET /api/courses returns the instructor's course list. GET /api/courses/{id}/students returns all students. POST /api/grade posts the grade to Canvas and returns the updated submission.

Common use cases

Automated Grade Sync

Build a Replit server that reads quiz or assignment results from an external tool (a coding platform, assessment system, or custom app) and posts grades directly to Canvas through the Submissions API. Students see their grades in Canvas without manual data entry by instructors.

Replit Prompt

Build a grade sync API that accepts a student ID, assignment ID, and score, then posts the grade to Canvas using the Submissions API and returns the updated submission record.

Copy this prompt to try it in Replit

Course Analytics Dashboard

Pull enrollment data, assignment completion rates, and grade distributions from Canvas to build a custom analytics dashboard. Give instructors visibility into student progress that Canvas's built-in analytics does not provide, such as cross-course comparisons or engagement trends.

Replit Prompt

Create an Express endpoint that fetches all enrollments and assignment submission data for a given course, calculates completion percentages per assignment, and returns the data formatted for a chart.

Copy this prompt to try it in Replit

Assignment Auto-Grader

Listen for Canvas submission webhooks or poll the Submissions API for new submissions, run automated grading logic (code execution, NLP scoring, rubric matching), and post scores back to Canvas. Useful for programming assignments where code can be evaluated automatically.

Replit Prompt

Build an auto-grader that polls Canvas for ungraded submissions on a specific assignment, runs each submission through a test suite, and posts the test results as the grade with a feedback comment.

Copy this prompt to try it in Replit

Troubleshooting

401 Unauthorized on every Canvas API request

Cause: The CANVAS_API_TOKEN in Replit Secrets is invalid, expired, or the token has been revoked. Canvas tokens can also be invalidated if the user changes their Canvas password.

Solution: Log into Canvas โ†’ Account Settings โ†’ Approved Integrations and check if your token still appears. If it is missing or shows as expired, generate a new token and update CANVAS_API_TOKEN in Replit Secrets. Restart the Repl after updating secrets.

typescript
1// Quick auth test
2const response = await canvasApi.get('/users/self');
3console.log('Auth OK. Logged in as:', response.data.name);

API returns only 10-20 students but the course has hundreds

Cause: Canvas paginates all list endpoints. Without following the Link header's next page URL, you only receive the first page of results.

Solution: Use the getAllPages / get_all_pages helper shown in the code examples. Always include per_page=100 to minimize round trips. Check response headers for the Link field containing rel='next'.

typescript
1// Check if response has more pages
2console.log('Link header:', response.headers['link']);
3// Should show: <url>; rel="next", <url>; rel="last"

403 Forbidden when trying to post grades

Cause: The Canvas token belongs to a student account or an account without Teacher or TA role in the course. Only instructors and TAs can post grades via the Submissions API.

Solution: Ensure the Canvas account that generated the token is enrolled as a Teacher or TA in the course. If you need to grade on behalf of multiple courses, consider using a Canvas admin account token or configuring OAuth with the appropriate scope (url:PUT|/api/v1/courses/:course_id/assignments/:assignment_id/submissions/:user_id).

Course ID or assignment ID not found โ€” getting 404 errors

Cause: Canvas internal IDs are numeric and environment-specific. A course ID from your development Canvas sandbox will be different from production. Also, students cannot access courses they are not enrolled in.

Solution: Use the /api/v1/courses endpoint to list accessible courses and extract the correct numeric IDs programmatically rather than hardcoding them. Log the full API response to see the exact field names and verify you are using the id field (not sis_course_id or uuid).

typescript
1// List courses to find correct IDs
2const courses = await getAllPages('/courses');
3console.log(courses.map(c => ({ id: c.id, name: c.name, code: c.course_code })));

Best practices

  • Store CANVAS_API_TOKEN and CANVAS_BASE_URL in Replit Secrets (lock icon ๐Ÿ”’) โ€” never hardcode credentials or commit them to version control
  • Always implement pagination using the Link header โ€” Canvas limits list responses and silently returns partial results if you do not follow next-page links
  • Include per_page=100 on all list requests to minimize API round trips for large courses
  • Use a dedicated Canvas service account with minimal permissions rather than a personal admin account to limit blast radius if credentials are compromised
  • Test in a Canvas sandbox course with a small number of students before running automations against real courses with real student data
  • Handle HTTP 401, 403, and 404 responses explicitly โ€” Canvas returns these for common scenarios like expired tokens and permission mismatches
  • Deploy grade-posting integrations as Autoscale for event-driven patterns, or Reserved VM for polling-based batch jobs that run on a schedule
  • Log all grade-posting operations with student ID, assignment ID, score, and timestamp โ€” audit trails are essential for academic systems

Alternatives

Frequently asked questions

How do I connect Replit to Canvas LMS?

Generate a personal access token in Canvas Account Settings โ†’ Approved Integrations, store it in Replit Secrets as CANVAS_API_TOKEN, and make authenticated HTTP requests to your Canvas instance's REST API at {base_url}/api/v1/. Include the token as a Bearer token in the Authorization header of every request.

Does Replit work with Canvas LMS?

Yes. Canvas provides a REST API that works with any HTTP client. From Replit, use axios (Node.js) or the requests library (Python) to call Canvas API endpoints. Store your API token in Replit Secrets and include it in request headers. The integration works with any Canvas instance โ€” cloud-hosted (instructure.com) or self-hosted.

How do I store my Canvas API token in Replit?

Click the lock icon (๐Ÿ”’) in the Replit sidebar to open the Secrets panel. Add CANVAS_API_TOKEN with your token value and CANVAS_BASE_URL with your institution's Canvas URL. Access them in code with process.env.CANVAS_API_TOKEN (Node.js) or os.environ['CANVAS_API_TOKEN'] (Python). Never paste the token directly into your code files.

Can I post grades to Canvas from Replit?

Yes, using the Submissions API. Send a PUT request to /api/v1/courses/{course_id}/assignments/{assignment_id}/submissions/{student_id} with the grade in the submission.posted_grade field. Your Canvas token must belong to an account with Teacher or TA role in that course. You can also include a text comment alongside the grade.

Why is the Canvas API only returning some of my students?

Canvas paginates all list endpoints. The default page size is typically 10-100 items. You must follow the Link response header's rel='next' URL to retrieve subsequent pages. Always pass per_page=100 to minimize round trips, and use a pagination loop that follows next-page links until the Link header no longer contains a next relation.

What deployment type should I use on Replit for Canvas integrations?

Use Autoscale for webhook receivers and on-demand API endpoints (most integrations). Use Reserved VM if you have a background process that continuously polls Canvas for new submissions or syncs data on a fixed schedule. Reserved VM ensures the process keeps running without cold start delays.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation โ€” no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.