---
# System prepended metadata

title: canvas-dev-suggestions

---

# canvas-dev-suggestions

Short answer: yes—you should run a backend for an LTI 1.3 tool, and have **your server** (not the browser) exchange the LTI launch for a **short-lived, scoped token** and then call only the Canvas REST endpoints you need (e.g., Classic Quizzes + Modules). Below is a crisp, end-to-end recipe you can follow.

# 0) What you’ll build (secure architecture)

* **Frontend**: a minimal page that renders after an LTI launch (course context in the JWT), with UI to “Generate quiz,” “Create module,” etc.
* **Backend**: handles OIDC login + LTI launch validation, exchanges for a **Canvas OAuth2 token** via the **LTI Advantage OAuth2 service**, and then calls Canvas APIs using **scoped developer keys**. Tokens are **short-lived**; refresh when needed. ([developerdocs.instructure.com][1])

---

# 1) Admin prep in Canvas (one-time)

1. **Create an LTI 1.3 Developer Key**
   Admin → **Developer Keys** → **+ LTI Key**. Record the **Client ID**. Enable **scope enforcement** for this key (least-privilege). ([Canvas Community][2])

2. **Choose minimal scopes** (tweak as you implement)

* Read course context and content you’ll analyze (e.g., pages, files, assignments, discussions).
* Write only what you create (Classic Quizzes, quiz questions, modules/items).
  Canvas exposes a machine-readable **list of applicable scopes** and the exact `url:GET|...` patterns; use that to verify you’ve enabled only what your code calls. ([developerdocs.instructure.com][3])

3. **Add placements** for your tool
   Typical: **Course Navigation** (launch your app in a course) and/or **Module item** selection. (Assignments placement if you’ll write grades via AGS later.) ([developerdocs.instructure.com][4])

---

# 2) Your LTI 1.3 server: launch & tokens

### A. Implement OpenID Connect login + LTI launch

* Support **OIDC login initiation** (Canvas → your tool), then **LTI launch** POST with an ID token (JWT).
* Validate: issuer, audience (your Client ID), nonce, message type, deployment ID, context claims.
* Store the **course\_id** (and optionally user/roles) from the launch claims.
  Canvas supports **dynamic registration** too, but static registration is fine to start. ([developerdocs.instructure.com][5])

### B. Exchange for an access token (server-to-server)

From your backend, call Canvas’s **OAuth2 token endpoint** with your LTI client credentials to obtain a **short-lived access token** to:

* Use **LTI Advantage services** (e.g., NRPS roster, AGS grading), and/or
* Call **Canvas REST API** endpoints that are permitted by your **scoped developer key**.
  Tokens expire (≈1 hour); use refresh tokens to renew. ([developerdocs.instructure.com][1])

### C. Optional LTI services you may want

* **NRPS (Names & Roles)** to pull the current roster *when needed* (e.g., for targeted announcements). Requires the NRPS scope in your tool config. ([developerdocs.instructure.com][6])
* **AGS (Assignment & Grade Services)** if you later want your quizzes to post grades to the gradebook. ([developerdocs.instructure.com][7], [IMS Global][8])

---

# 3) Reading course content for your AI pipeline

With a valid, scoped token, your backend can fetch only what you need for content analysis:

Common endpoints you’ll call (examples):

* **Pages**: `GET /api/v1/courses/:course_id/pages` (+ `/:url`)
* **Files**: `GET /api/v1/courses/:course_id/files` (then download file)
* **Assignments**: `GET /api/v1/courses/:course_id/assignments`
* **Discussions**: `GET /api/v1/courses/:course_id/discussion_topics`
* **Modules** (read, and later write): `GET /api/v1/courses/:course_id/modules`

Confirm the exact scope strings via the **Scopes API** list for your account before enabling. (E.g., `url:GET|/api/v1/courses/:course_id/pages`, etc.) ([developerdocs.instructure.com][3])

---

# 4) Writing Classic Quizzes & Modules (least privilege)

### Classic Quizzes (REST)

Typical write flow:

1. **Create a quiz**
   `POST /api/v1/courses/:course_id/quizzes`
2. **Add questions**
   `POST /api/v1/courses/:course_id/quizzes/:quiz_id/questions`
   (You can also create **question groups** if you’re assembling randomized pools.)
3. (Optional) **Publish** the quiz; set availability, time limit, etc.
4. (Optional) **Create a Module** and add the quiz as a module item.

For Classic Quizzes submissions/testing APIs, see “Quiz Submissions” docs. Ensure your scopes include the specific `url:POST|...quizzes` and `.../questions` patterns. ([documentation.instructure.com][9])

### Modules

* Create a module: `POST /api/v1/courses/:course_id/modules`
* Add items: `POST /api/v1/courses/:course_id/modules/:module_id/items`
  Again, enable only the `url:POST|...modules` scopes you call. ([developerdocs.instructure.com][3])

---

# 5) End-to-end request flow (what happens at runtime)

1. **Instructor** clicks your tool in **Course Navigation** → Canvas performs **OIDC → LTI launch** to your backend with a signed ID token (contains `course_id`, roles). ([developerdocs.instructure.com][4])
2. **Backend** validates the launch, stores context, then **exchanges for an OAuth2 access token** using your LTI client credentials. ([developerdocs.instructure.com][1])
3. **Backend** reads selected course content (pages/files/etc.), runs **AI processing** to propose quiz questions, module outlines, announcements.
4. **Backend** writes **Classic Quiz** + questions and/or **Module** + items via REST. Tokens are **short-lived and scoped**, reducing blast radius. ([developerdocs.instructure.com][10])
5. **Frontend** shows links to the created Canvas objects (quiz, module) in that same course.

---

# 6) Minimal security checklist (practical)

* **Use scoped developer keys**; verify every REST call maps to an enabled scope. (Canvas even provides an endpoint that lists scopes available in your account.) ([developerdocs.instructure.com][3])
* **Never** issue or store Personal Access Tokens for this tool. Keep all token exchange **server-side**. ([Canvas Community][2])
* Validate **JWTs** (issuer, audience, nonce), store per-launch **state**, and rotate your client secret.
* Log every write (who launched, which course, which endpoint).
* Prefer **per-course launches** (course nav or module placement) so your app naturally operates within one course context per session. ([developerdocs.instructure.com][4])

---

# 7) Exact scopes to start with (tune down or up)

Use the scopes list to find the precise strings for your instance; the pattern typically looks like:

*Read (content for AI analysis)*

* `url:GET|/api/v1/courses/:course_id`
* `url:GET|/api/v1/courses/:course_id/pages` (+ show)
* `url:GET|/api/v1/courses/:course_id/files` (+ file download)
* `url:GET|/api/v1/courses/:course_id/assignments`
* `url:GET|/api/v1/courses/:course_id/discussion_topics`

*Write (Classic quizzes & modules only)*

* `url:POST|/api/v1/courses/:course_id/quizzes`
* `url:POST|/api/v1/courses/:course_id/quizzes/:quiz_id/questions`
* `url:PUT|/api/v1/courses/:course_id/quizzes/:quiz_id` (to publish/tune settings)
* `url:POST|/api/v1/courses/:course_id/modules`
* `url:POST|/api/v1/courses/:course_id/modules/:module_id/items`

Then add/remove as your logs prove you don’t need certain calls. ([developerdocs.instructure.com][3])

---

# 8) Classic Quizzes vs. “New Quizzes”

You’re targeting **Classic Quizzes**, which are fully covered by the REST API documented in Canvas. If/when you move to **New Quizzes**, the APIs and scoping are different; check the evolving scopes/posts before you switch. ([Canvas Community][11])

---

# 9) Do you really need NRPS/AGS?

* For your described tool (generate quizzes/modules from existing content), **you can skip NRPS/AGS at first**—you only need course context + REST.
* Add **NRPS** if you want to personalize comms or target by role/section. Add **AGS** only if you want your tool to create gradebook columns and post grades. ([developerdocs.instructure.com][6])

---

If you want, I can sketch boilerplate for the **LTI 1.3 launch + token exchange** (Node or Python), plus a minimal “Create Classic Quiz + add questions + add to Module” sequence with the **exact REST calls** and recommended error handling.

[1]: https://developerdocs.instructure.com/services/canvas/oauth2/file.oauth?utm_source=chatgpt.com "OAuth2 Overview"
[2]: https://community.canvaslms.com/t5/Admin-Guide/How-do-I-add-a-developer-API-key-for-an-account/ta-p/259?utm_source=chatgpt.com "How do I add a developer API key for an account?"
[3]: https://developerdocs.instructure.com/services/canvas/resources/api_token_scopes?utm_source=chatgpt.com "API Token Scopes"
[4]: https://developerdocs.instructure.com/services/canvas/external-tools/lti/file.tools_intro?utm_source=chatgpt.com "Introduction"
[5]: https://developerdocs.instructure.com/services/canvas/external-tools/lti/file.registration?utm_source=chatgpt.com "Registration"
[6]: https://developerdocs.instructure.com/services/canvas/external-tools/lti/file.provisioning?utm_source=chatgpt.com "Provisioning"
[7]: https://developerdocs.instructure.com/services/canvas/external-tools/lti/file.assignment_tools?utm_source=chatgpt.com "Grading"
[8]: https://www.imsglobal.org/spec/lti-ags/v2p0?utm_source=chatgpt.com "Learning Tools Interoperability Assignment and Grade ..."
[9]: https://documentation.instructure.com/doc/api/quiz_submissions.html?utm_source=chatgpt.com "Quiz Submissions - Canvas LMS REST API Documentation"
[10]: https://developerdocs.instructure.com/services/canvas/oauth2/file.developer_keys?utm_source=chatgpt.com "Developer Keys"
[11]: https://community.canvaslms.com/t5/Canvas-Developers-Group/Using-API-endpoints-for-Quizzes/m-p/523333?utm_source=chatgpt.com "Solved: Using API endpoints for Quizzes - Canvas Community"
