# LTIAAS documentation
Every guide, concatenated. LTIAAS implements the LTI 1.3 protocol on your
behalf: you do not implement OAuth, handle JWTs, or publish a key set.
The API reference is not included here. Use the OpenAPI description at
https://ltiaas.com/openapi.json, which is complete and machine-readable.
# Getting started
## Getting Started
Source: https://ltiaas.com/guides/introduction
# Getting Started
## What Is LTI®
LTI®, which stands for "learning tools interoperability", is a standard for integration of rich learning applications within educational environments.
Within the standard, there are many protocols and versions of protocols. The most popular of which is LTI® 1.3.
Implementing the LTI® 1.3 protocol and all of it's services can be a very difficult, time consuming task, but using LTIAAS any project can be easily turned into a fully LTI® 1.3 compliant learning tool, with minimal alterations to the project's codebase.
## Who We Are
LTIAAS was founded with the goal of dramatically decreasing the complexity of implementing the LTI® protocol. LTIAAS is not affiliated with 1EdTech Consortium, Inc.
## What We Do
LTIAAS works as a middleman between your application and any LTI® compliant LMS, handling all of the communication and giving you access to a powerful API that allows you to take advantage of the various LTI® functionalities, like grade manipulation and roster retrieval.
## Section Summary
This guide will walk you through the steps of setting up your free LTIAAS account and getting your very first LTI® integration up and running withing minutes.
## Next Steps
You can start building your LTI® integration by creating a free LTIAAS account. Please see the [Account Setup and Deploy](/guides/account-setup) guide for more information.
## Account Setup and Deploy
Source: https://ltiaas.com/guides/account-setup
## Setting up Your Account
With LTIAAS, setting up an LTI® API for your company is easy. LTIAAS hosts a private API service that you can use to connect LTI® tools with LMSes. Getting started is free and easy.
This guide will walk you through creating an account and activating your API in the [LTIAAS Portal](https://portal.ltiaas.com).
### Step 1. Log in to the LTIAAS Portal
All LTIAAS services are managed via the customer portal. To access the portal, please log in using either your email address or Google account. If an account doesn't exist, it will be created for you.
### Step 2. Create New Account
We have two account types that we support: An LTI® Tool (a web application that is accessed from an LMS) or an LTI® Platform (the LMS itself). The LTIAAS portal is only automated to set up LTI® Tools.
On the 'Create New Account' screen, you are asked to provide a name. This name is only use by you. Some customers have several accounts, so it is helpful to have easily identifiable names for each.
**Tip:**
If you already have at least one account, you can add another account by clicking on the `+ Add Account` button on the Portal home page.
### Step 3. Create Your Plan
On the next screen, you get to choose the options for your plan. Options can be selected by clicking the `Select` button below each plan option.
Fore more information about our plans and pricing, please visit our [pricing page](https://ltiaas.com/pricing).
**Tip:**
Each LTIAAS account has a unique subdomain of ltiaas.com (i.e. your.ltiaas.com) that is used to access the API and register with LMSes. LTIAAS give you the option to create what we call a "vanity URL". If you want to have your own domain point to LTIAAS servers (i.e. lti.yourdomain.com), please choose a plan with the [Custom Domain](../portal/custom-domain-setup.mdx) option.
Once you have selected your plan options, we ask that you enter in your credit card to activate the subscription. If a credit card is not an available option for payment, please [reach out](https://ltiaas.com/contact-us) to us. We can accommodate most payment methods with manual intervention. If you are choosing our free tier, no credit card is required.
When the form is complete. Click the `Subscribe` button.
**Tip:**
After subscribing, you can change your subscription and billing information at any time without an interruption in service. Just click on the `Billing` link in the portal for this account.
### Step 4. Set Your API Settings
Once you have created your account, the next step is to set up your API. You should be guided to the `API Settigns` page. If not, please click on the `API Settings` link on the sidebar.
You will be asked to fill in the API settings.
**Caution:**
All the API settings can be changed at any time. However, the zone, once set, cannot be changed without contacting support.
Here's a description of the API settings that can be set:
| Setting | Description |
|--------------------------------------|-------------|
| Data Region | The region in which you want your data hosted in. **After this is provisioned, it cannot be changed.** If you want to change your data region after provisioning, please contact support@ltiaas.com. If GDPR compliance is a concern, our EU servers are specifically designed to meet that compliance and fully reside within the EU. |
| Subdomain | This is the URL that we provision for your tool in our system. All of your API traffic will go through this URL. It is a subdomain of ltiaas.com. i.e. mytool.ltiaas.com. If you chose a plan with the _Custom Domain_ option, you will be able to enter in a fully-qualified domain that you have DNS control over. |
| Tool Name | This is the title that is displayed in the LMS after the tool is linked. |
| Tool Description | This is the short description that is displayed in the LMS after the tool is linked. Just a short sentence is best. Each LMS displays this differently. |
| Launch URL | In order for basic LTI® launch to work, you need to set this setting. This is the URL, hosted by you, that users will be directed to when they click the link to your activity in the LMS. |
| Deep Linking URL | In order for Deep Linking to work (if you choose it as an option when creating the account), you need to provide this URL. This URL, hosted by you, is the location that that users (probably teachers) will be directed to when they click the option to "*choose an activity*" when adding your tool into a course. |
| Logo URL | The URL to your tool's logo that will be shown in the LMS (through _Dynamic Registration_). If _Dynamic Registration_ is not enabled or used, this logo is still shown to the user in the unlikely event of an error during the LTI® launch process. Recommended size: 128x128px. This should be a URL to an image hosted by you. |
| Dynamic Registration Auto-Activation | Enabling this will activate all registrations done through the Dynamic Registration API, If Dynamic Registration is enabled. If you want to approve every activation request first (i.e. after the customer pays you), then leave this disabled. |
After filling in the *API settings* form (as shown below), click the submit button at the bottom of the form.
**Info:**
Note: The last two sections displayed in this guide: `Deep Linking Settings` and `Dynamic Registration Settings` will only appear if you selected those features in step 3.
### Step 5. Deploy!
Any time you make a change to the *API settings*, you need to deploy those changes to the LTIAAS cloud. The LTIAAS Portal will detect when these settings change and will display a message (shown below) asking you to deploy the settings change.
Clicking on the `Deploy Now` button will trigger the settings to propagate to the LTIAAS cloud service. Typically, changes will be available in about 10 seconds. But in rare cases, it may take up to 5 minutes for the new settings to be deployed. In the process of deploying the changes, your private API may become unavailable for a few seconds.
### Step 6. Get Your API Key
After your first deploy, you need to get an API key. Click the `Generate New API Key` button.
Once the API key is generated, your private API key will become visible.
Click on the button to copy this key to your clipboard. This key should be used when [making API requests](https://ltiaas.com/docs/api_documentation/#authentication) to LTIAAS.
**Caution:**
After generating your API key, LTIAAS will never show it again. If you loose you key, you will have to generate a new one.
### Step 7. Test the API
LTIAAS serves a list of public keys for all the platforms registered to your service. You can use the `/lti/keys` endpoint to get this list. It is also a good first test to ensure the LTIAAS service is accessible (from behind a firewalled server, for example). You can access this endpoint with any web browser by navigating to [https://your.ltiaas.com/lti/keys](https://your.ltiaas.com/lti/keys), or use the cURL tool:
```bash
curl https://your.ltiaas.com/lti/keys
```
**Info:**
Where `your` is the subdomain you chose in step 5.
A new API with no registrations should return an empty array called 'keys':
```bash
{"keys":[]}
```
## Next Steps
Now you are ready to setup your LTI® tool in any LMS of your choice. Please see the [LMS Setup](/guides/lms/introduction) guide for more information.
## Receiving Your First Launch
Source: https://ltiaas.com/guides/first-launch
# Receiving Your First Launch
If you are reading this, you have successfully [registered your tool with an LMS](/guides/lms/introduction). Now you are ready to receive to your first LTI® Launch.
## The LTIAAS Launch Redirection
When a user clicks on a link to your tool in the LMS, LTIAAS will receive a launch request and perform the LTI® protocol handshake on your behalf.
After both parties' identities are verified, LTIAAS will redirect the user to your application's registered target launch URL.
You can also add any query parameters you want to the LTIAAS launch URL, as they will be passed along to your application's registered target launch URL during subsequent LTI® launches.
**Content item URL:**
> https://your.ltiaas.com/lti/launch?resource=123
**Final target URL after LTI® launch:**
> https://your.application.com/your-launch-endpoint?resource=123
**Tip:**
These parameters can be used to identify which of your application's resources is linked to a certain activity. You can check our [Deep Linking guide](/guides/api/deeplinking) to know more about this.
## Dealing with Errors During the Launch
If you run into any errors during your launch process, please check our [troubleshooting section](/guides/troubleshooting/introduction).
## Next Steps
Now that you have successfully received your first LTI® launch, here are a few possibilities to expand your LTI® integration even further:
### Display a Simple Tool
This is our most basic use case, you just want to display a simple tool to the user, without any authentication or context based customization.
No additional work is needed to achieve this. After LTIAAS redirects the user to your application, all you need to do is display the content you want users to see.
### Retrieve the ID Token and Perform SSO
The ID Token is the main piece of data in the LTI® protocol, it's sent by the LMS to the LTI® tool and contains information regarding the current LTI® launch, this includes user information, LMS information, launch context information and everything you need to use the many LTI® services.
You can use the information contained in the ID Token to perform SSO and authenticate users into your application, automatically logging them into a preexisting account or provisioning a new one on the spot. To learn more about this, please check our [Retrieving ID Token guide](/guides/api/idtoken) and its [Performing SSO section](/guides/api/idtoken#performing-sso).
**Tip:**
Besides performing SSO, you can also use the ID Token information to customize your tool's UI or content based on the LMS, course, and/or user role.
### Retrieve Roster Information
The LTI® protocol allows you to retrieve roster information for an LMS context through the *Names and Roles Provisioning* service. LTIAAS gives you access to this service through the Memberships API endpoint. To learn more about this functionality, please check our [Retrieving course roster guide](/guides/api/roster).
### Manipulate User Grades
The LTI® protocol allows you to manipulate user's grades and LMS grade lines through the *Assignment and Grades* service. LTIAAS gives you access to this service through the Line Items API endpoint. To learn more about this functionality, please check our [Manipulating grade lines guide](/guides/api/manipulating-grade-lines) and [Manipulating grades guide](/guides/api/manipulating-grades).
### Serving Multiple Resources Through Your Tool
If your application is a hub of multiple resources and/or activities, the LTI® protocol makes this easy to manage through the *Deep Linking* service. You can use this service to allow teachers and administrators to select one specific resource from your application when creating an activity in the LMS. LTIAAS gives you access to this service through the Deep Linking flow and API endpoint. To learn more about this functionality, please check our [Performing Deep Linking guide](/guides/api/deeplinking).
# Connecting an LMS
## LMS Setup
Source: https://ltiaas.com/guides/lms/introduction
# LMS Setup
## Introduction
The LTI® protocol requires each LTI® Tool and LMS that want to communicate to know about one another. When using LTIAAS, this is achieved through the following registration process:
1. The LTI® tool is registered into the LMS using LMS-specific instructions.
2. The LMS provides a unique `Client ID` for that registration.
3. The `Client ID` is used to perform a platform registration within LTIAAS.
### Dynamic Registration - the Easy Way Out
The easiest way to register a tool with an LMS is with Dynamic Registration.
Currently, Moodle (_3.10 or higher_), and BrightSpace support Dynamic Registration.
Dynamic Registration automates the registration process by exposing a single _Dynamic Registration Endpoint_ that is provided to the LMS. To use this feature, the `Dynamic Registration` option must be chosen when creating the account.
Each LMS has a different place where Dynamic Registration is initiated. But the same Dynamic Registration URL can be used for all LMSes that have the feature implemented.
### Manual Registration
For LMSes that do not support dynamic registration (i.e. Canvas, Blackboard, and older Moodle instances), or LTIAAS accounts without the Dynamic Registration feature, the registration process must be done manually.
## Section Summary
This section breaks down the setup process for many of the most popular LMSes.
## Next Steps
If you already have your LTI® tool set up in an LMS, you can start receiving LTI® launches. Please see the [Receiving your first LTI® launch](/guides/first-launch) guide for more information.
## Blackboard
Source: https://ltiaas.com/guides/lms/blackboard
## Manual Registration
The Blackboard LMS only supports manual tool registration, not Dynamic Registration. This guide explains how to manually register an LTIAAS tool with Blackboard.
### Summary
To set up LTIAAS with Blackboard you need to:
1. [Install the LTIAAS tool into Blackboard for all tenants](#step-1-install-the-ltiaas-tool-into-blackboard-for-all-tenants)
2. [Register the Blackboard provided details into LTIAAS](#step-2-register-the-blackboard-provided-details-into-ltiaas)
3. [Install the LTIAAS tool into Blackboard](#step-4-install-ltiaas-tool-into-blackboard)
4. [Set up a placement for the LTIAAS tool](#step-5-setup-a-placement-for-the-tool)
5. [Do a test launch](#step-6-do-a-test-launch)
### Step 1. Install the LTIAAS Tool into Blackboard for All Tenants
Blackboard uses a concept called 'multi-tenancy'. This allows an LTI® tool to be registered once within Blackboard.com and is then made available to all Blackboard "tenants". So, a tool registration is not done for each LMS instance, but only once at [https://developer.blackboard.com](https://developer.blackboard.com), then made available by Blackboard to all instances.
1. Navigate to to [https://developer.blackboard.com](https://developer.blackboard.com) and sign up and/or sign in.
2. Click on “My Applications”, then click “Register a REST or LTI® application”.
3. Fill in the form with the details below:
- **Application Name:** `Your Tool Name`
- **Description:** `Your Tool Description`
- **Domain(s):** `your.ltiaas.com`
- **Group:** (your choice) <- _This is only for organizing apps within your Blackboard Developer account._
- **My Integration supports LTI® 1.3:** `CHECKED`
- **Login Initiation URL:** `https://your.ltiaas.com/login`
- **Tool Redirect URL(s):** `https://your.ltiaas.com/lti/launch`
- **Tool JWKS URL:** `https://your.ltiaas.com/keys`
- **Signing Algorithm:** `RS256`
4. Click the '**Register Application**' button.
5. After the Blackboard registration process is complete, the blackboard endpoints for this tool are made available. Copy the following values that will be used in the LTIAAS registration:
* Application ID
* Issuer
* Public keyset URL
* Auth token endpoint
* OIDC auth request endpoint
### Step 2. Register the Blackboard Provided Details into LTIAAS
A registration can be done within the [LTIAAS Portal](https://portal.ltiaas.com). Start by logging in to your account and navigating to the `Registrations` page. Then click on the `+ Add Registration` button.
Enter the information that the LMS provides for its LTI® endpoints using the table below as a guide.
LTIAAS Registration API Name | Blackboard Provided Name
----------- | ---------------
**Name** | _Any name you would like that helps you identify the LMS_
**Client ID** | _Application ID_
**URL** | _Issuer_
**Authentication Method** | `JWK_SET`
**Authentication Key** | _Public keyset URL_
**Access Token Endpoint** | _Auth token endpoint_
**Authentication Endpoint** | _OIDC auth request endpoint_
Click submit when you have made the necessary changes. Also, you can edit these values at any time.
### Step 3. (Optional) Edit the Registration
If at any time you want to edit or delete a registration, simply click on the blue pencil icon on the leftmost column of the table for the registration item you want to change.
By default, all new registrations are activated upon creation. There may be an instance where a platform was registered, but you don't want the registration to be active. You can activate/deactivate a registration by clicking on the `true/false` button on the *Active* column of the table.
### Step 4. Install LTIAAS Tool into Blackboard
1. Login to the Blackboard LMS as an administrator and click on the 'System Admin' menu button
2. Navigate to 'Integrations' -> 'LTI® Tool Providers'
3. In the resulting 'LTI® Tool Providers' page, click 'Register LTI® 1.3/Advantage Tool'
4. In the resulting page you will be prompted to enter a client ID.
enter the `Client ID` that was created in step 1, then click 'Submit'. 5. This will bring you to a pre-filled 'Accept LTI® 1.3 Tool' form. Please make sure the following settings are changed:
* **Tool Status:** Approved
* **User Fields To Send:** Role in Course, Name, Email Address
* **Allow grade service access:** Yes _(If you are using the LTIAAS Assignments and Grades service)_
* **Allow Membership Service Access:** Yes _(If you are using the LTIAAS Names and Roles service)_
6. Finally click 'Submit' at the bottom of the form.
### Step 5. Setup a Placement for the Tool
1. Now that the tool is added to Blackboard, find it in the table on the 'LTI® Tool Providers' table. Hover over it's name and click on the small 'down arrow' to the right of the name. Then select 'Manage Placements'
2. Click the 'Create Placement' button on the resulting Manage Placements page.
3. Fill in the Placement form with the following information:
- **Label:** `Your Tool Name`
- **Description:** `Your Tool Description`
- **Handle:** `YourToolName`
- **Availability:** `Yes`
- **Type:**`Deep Linking content tool (+ Allow student access)` <- _If you are using the LTIAAS deep linking service._
- **Type:**`Course content tool (+ Allows grading)` <- _If you are using the LTIAAS assignments and grades service._
- **Type:**`Course tool (+ Allow student access)` <- _If you are using a basic LTIAAS launch only._
- **Target Link URI:** `https://your.ltiaas.com/lti/launch` Then click 'Submit'
### Step 6. Do a Test Launch
1. In Blackboard, go to a course and then select 'Content' in the left hand menu.
2. In the Content Page, select 'Build Content' -> 'Your Tool Name', where 'Your Tool Name' is the name of the tool name given inside Blackboard.
3. You should then see the app running inside blackboard.
4. If you get an error similar to 'The Tool Provider has been disabled by the System Administrator', there are a couple things you should check:
1. Navigate to 'System Admin' -> 'Integrations' -> 'LTI® Tool Providers' and validate that the tool's State is 'Approved'. If it is not, click on the small down arrow next to the tool name and click 'Approve' 2. Navigate to 'System Admin' -> 'Tools and Utilities' -> 'Tools' and search for a section labeled 'LTI®'. Make sure LTI® is enabled. ## Next Steps
Now that you have your LTI® tool set up in an LMS, you can start receiving LTI® launches. Please see the [Receiving your first LTI® launch](/guides/first-launch) guide for more information.
## Brightspace
Source: https://ltiaas.com/guides/lms/brightspace
The Brightspace LMS supports two different registration methods: **[dynamic registration](#dynamic-registration)** and **[manual registration](#manual-registration)**. This guide covers both methods.
## Dynamic Registration
The easiest way to register an LTIAAS tool with Brightspace is with Dynamic Registration.
**Caution:**
Make sure that Dynamic Registration for your LTIAAS API is enabled during the LTIAAS onboarding process.
### Summary
To set up LTIAAS with Brightspace using Dynamic Registration, you need to:
1. [Register a new tool in the Brightspace installation](#step-1-register-a-new-tool-in-your-brightspace-installation)
3. [Do a test launch](#step-2-do-a-test-launch)
### Step 1. Register a New Tool in Your Brightspace Installation
1. Login to the Brightspace installation as an administrator.
2. Navigate to 'Admin Tools (Gear Icon)' -> 'Manage Extensibility'
3. Click the 'LTI® Advantage' tab, then the 'Register Tool' button
4. Choose `Dynamic` registration, click Register.
5. In the resulting form, configure the tool with the following information:
* **Dynamic**: `CHECKED`
* **Tool initiation registration endpoint**: `https://your.ltiaas.com/lti/register`
6. It is recommended to review the Extensions, Parameters, and Security Settings (in the deployment if applicable) that the tool has asked for to ensure they meet your organization's use cases.
7. Submit the form
### Step 2. Do a Test Launch
1. In the Brightspace installation, on the page we left off with when registering the tool, click the 'View Deployments' link near the bottom of the page.
2. Click the 'New Deployment' button.
3. In the resulting form, enter the following:
* **Enabled**: `CHECKED`
* **Tool**: _Select your tool from the dropdown list_
* **Name**: `Your Tool Name`
* **Description**: `Your Tool Description`
* **Extensions**: `CHECKED` <- _For each service you plan to use._
* **Security Settings**: `CHECKED` <- _Select as little as possible to avoid passing sensitive user information to your tool._
4. Click the 'Add Org Units' button and in the popup, select which organizations/users should have access to the tool.
5. Click the 'Save and Close' button to save the deployment.
6. Go back into the deployment form and click the 'View Links' link at the bottom of the form.
7. In the _Links_ page, click the 'New Link' button
8. Fill in the resulting form as follows:
* **Enabled**: `CHECKED`
* **Name**: `Your Tool Name`
* **URL**: `https://your.ltiaas.com/lti/launch` <- Note: if using deep linking but with no content selection screen, you can pass a manually generated [deep linking URL](../api/deeplinking.mdx#building-the-content-item-url) here instead.
* **Description**: `Your Tool Description`
* **Type**: _Select the way you want the tool to show to the user._
9. Click the 'Save and Close' button.
10. Click on the grid icon at the top of the page and navigate to a course, then click the 'Content' tab, and click the 'Add a Module' input box. In the box, give the module a name you prefer.
11. Once the module is created, click the 'Existing Activities' button and select 'External Learning Tools' option.
12. In the resulting dialog, find your tool name on the list and click it.
13. Finally, a link should have been created in the course module page. Click it to launch the tool.
## Manual Registration
### Summary
To set up LTIAAS with Brightspace using Manual Registration, you need to:
1. [Create a new tool in the Brightspace installation](#step-1-create-a-new-tool-in-your-brightspace-installation)
2. [Register the Brightspace installation inside LTIAAS](#step-2-register-the-brightspace-installation-inside-ltiaas)
3. [Do a test launch](#step-4-do-a-test-launch)
### Step 1. Create a New Tool in Your Brightspace Installation
1. Login to your Brightspace installation as an administrator.
2. Navigate to 'Admin Tools (Gear Icon)' -> 'Manage Extensibility'
3. Click the 'LTI® Advantage' tab, then the 'Register Tool' button
4. Choose `Standard` registration, then click Register.
5. In the resulting form, configure the tool with the following information:
- **Enabled**: `CHECKED`
- **Name:** `Your Tool Name`
- **Description:** `Your Tool Description`
- **Domain:** `https://your.ltiaas.com/lti/launch`
- **Redirection URLs:** `https://your.ltiaas.com/lti/launch`
- **OpenID Connect Login URL:** `https://your.ltiaas.com/login`
- **Keyset URL:** `https://your.ltiaas.com/keys`
- **Extensions:** `CHECKED` <- _For each service you plan to use._
- **Roles:** `CHECKED` <- _If you plan to use this information in you tool._
6. Click the 'Register' button at the bottom when the form is filled. You can always go back and change this form later if you need to.
7. After saving, scroll down to the bottom of the registration page to find the registration details that need to be entered into LTIAAS.
### Step 2. Register the Brightspace Installation Inside LTIAAS
A registration can be done from within the [LTIAAS Portal](https://portal.ltiaas.com). Start by logging in to your account and navigating to the `Registrations` page. Then click on the `+ Add Registration` button.
Enter the information that the LMS provides for its LTI® endpoints using the table below as a guide.
LTIAAS Registration API Name | Brightspace Provided Name
----------- | ---------------
**Name** | _Any name you would like that helps you identify your LMS_
**Client ID** | _Client ID_
**URL** | _Issuer_
**Authentication Method** | `JWK_SET`
**Authentication Key** | _Brightspace Keyset URL_
**Access Token Endpoint** | _Brightspace OAuth2 Access Token URL_
**Authentication Endpoint** | _OpenID Connect Authentication Endpoint_
**Authorization Server** | _Brightspace OAuth Audience_
Click submit when you have made the necessary changes. Also, you can edit these values at any time.
### Step 3. (Optional) Edit the Registration
If at any time you want to edit or delete a registration, simply click on the blue pencil icon on the leftmost column of the table for the registration item you want to change.
By default, all new registrations are activated upon creation. There may be an instance where a platform was registered, but you don't want the registration to be active. You can activate/deactivate a registration by clicking on the `true/false` button on the *Active* column of the table.
### Step 4. Do a Test Launch
1. In the Brightspace installation, on the page we left off with when registering the tool, click the 'View Deployments' link near the bottom of the page.
2. Click the 'New Deployment' button.
3. In the resulting form, enter the following:
* **Enabled**: `CHECKED`
* **Tool**: _Select your tool from the dropdown list_
* **Name**: `Your Tool Name`
* **Description**: `Your Tool Description`
* **Extensions**: `CHECKED` <- _For each service you plan to use._
* **Security Settings**: `CHECKED` <- _Select as little as possible to avoid passing sensitive user information to your tool._
4. Click the 'Add Org Units' button and in the popup, select which organizations/users should have access to the tool.
5. Click the 'Save and Close' button to save the deployment.
6. Go back into the deployment form and click the 'View Links' link at the bottom of the form.
7. In the _Links_ page, click the 'New Link' button
8. Fill in the resulting form as follows:
* **Enabled**: `CHECKED`
* **Name**: `Your Tool Name`
* **URL**: `https://your.ltiaas.com/lti/launch` <- Note: if using deep linking but with no content selection screen, you can pass a manually generated [deep linking URL](../api/deeplinking.mdx#building-the-content-item-url) here instead.
* **Description**: `Your Tool Description`
* **Type**: _Select the way you want the tool to show to the user._
9. Click the 'Save and Close' button.
10. Click on the grid icon at the top of the page and navigate to a course, then click the 'Content' tab, and click the 'Add a Module' input box. In the box, give the module a name you prefer.
11. Once the module is created, click the 'Existing Activities' button and select 'External Learning Tools' option.
12. In the resulting dialog, find your tool name on the list and click it.
13. Finally, a link should have been created in the course module page. Click it to launch the tool.
## Next Steps
Now that you have your LTI® tool set up in an LMS, you can start receiving LTI® launches. Please see the [Receiving your first LTI® launch](/guides/first-launch) guide for more information.
## Canvas
Source: https://ltiaas.com/guides/lms/canvas
**Info:**
Starting August 2023, All **Instructure-Hosted** Canvas URLs (except the base `URL` field, i.e. the issuer) should be changed from https://`canvas.instructure.com`/... to https://`sso.canvaslms.com`/...
Any previous URLs that are currently working do not need immediate change, but you should consider updating these as they may not continue to work in the future (date to be determined).
The Instructure team has provided a [detailed article on the URL change for Instructure-Hosted Canvas URLs](https://community.canvaslms.com/t5/The-Product-Blog/Minor-LTI®-1-3-Changes-New-OIDC-Auth-Endpoint-Support-for/ba-p/551677).
The instructions below have incorporated this change.
As of August 2024, Canvas supports dynamic registration. It also supports a semi-automated registration using JSON. This guide explains how to register an LTIAAS tool into Canvas with **[Dynamic Registration](#dynamic-registration)**, the **[Automatic JSON method](#automatic-json-registration)** and **[manually](#manual-registration)**.
## Dynamic Registration
### Summary
**Caution:**
Canvas dynamic registration is only available for customers that purchase the `dynamic registration` feature.
To set up LTIAAS with Canvas using dynamic registration you need to:
1. [Add a developer LTI® registration in Canvas](#step-1-add-an-lti-registration-in-canvas)
2. [Install the tool into Canvas](#step-2-install-the-tool-into-canvas)
2. [Do a test launch](#step-3-do-a-test-launch)
### Step 1. Add an LTI® Registration in Canvas
1. Login to your Canvas LMS as an administrator.
2. Navigate to 'Admin' -> [your account name] -> 'Developer Keys'
3. Click the '+ Developer Key' button followed by the '+ LTI® Registration' dropdown
4. In the resulting form, enter your dynamic registration URL: `https://your.ltiaas.com/lti/register`, where `your` is the custom subdomain set up in the LTIAAS portal; Then click 'Continue'.
**Tip:** Sending less user data (anonymous mode)
To limit the personal data Canvas sends on launch, append a `privacyLevel` query parameter to the registration URL — for example `https://your.ltiaas.com/lti/register?privacyLevel=anonymous` (or `name_only` / `email_only`). Declaring it in the registration URL is the reliable way to control this. See [Controlling how much user data the LMS sends](../api/dynamic-registration.mdx#controlling-how-much-user-data-the-lms-sends-privacy-level) for details and caveats — in `anonymous` mode Canvas also stops sending custom fields such as `custom_canvas_user_id`.
5. (optional) If you have the [Dynamic Registration Intermediate Target](../api/dynamic-registration.mdx#the-intermediate-target-flow) feature enabled, an iframe will launch with your intermediate target URL.
6. Canvas will open a final settings dialog that the user can configure additionally if needed; Click the 'Enable & Close' button.
6. After closing the dialog, find the newly registered tool's key on the list of keys and make sure the State is switched to 'ON'
7. Note the number in the details column. It will be used as the `Client ID` in the next steps
### Step 2. Install the Tool into Canvas
1. In the Canvas Admin menu, navigate to 'Settings' -> 'Apps'
2. Click the '+ App' button
3. In the resulting form choose:
* **Configuration Type:** `By Client ID`
* **Client ID:** `[The Client ID number you found in the previous series of steps]`
4. Click the 'Submit' button
5. In the resulting dialog, click 'Install'
6. The tool will now show up in the 'External Apps' table.
### Step 3. Do a Test Launch
1. Follow the instructions on the Canvas website to add an external tool assignment to a course: [How do I add an assignment using an external app?](https://community.canvaslms.com/t5/Instructor-Guide/How-do-I-add-an-assignment-using-an-external-app/ta-p/656)
2. When choosing an external tool, choose the newly registered tool from step 1.
## Automatic JSON Registration
### Summary
**Caution:**
Canvas automatic JSON registration is only available for customers that purchase the `dynamic registration` feature.
To set up LTIAAS with Canvas JSON you need to:
1. [Add a developer LTI® Key in Canvas](#step-1-add-a-developer-key-in-canvas)
2. [Install the tool into Canvas](#step-2-install-the-tool-into-canvas-1)
3. [Register the Canvas provided details into LTIAAS](#step-3-register-the-canvas-provided-details-into-ltiaas)
4. [Do a test launch](#step-5-do-a-test-launch)
### Step 1. Add a Developer Key in Canvas
1. Login to your Canvas LMS as an administrator.
2. Navigate to 'Admin' -> [your account name] -> 'Developer Keys'
3. Click the '+ Developer Key' button followed by the '+ LTI® Key' dropdown
4. In the resulting form, enter the following information:
- **Key name:** `Your Tool Name`
- **Method:** `Enter URL`
- **Redirection URI(s):** `https://your.ltiaas.com/lti/launch`
- **JSON URL:** `https://your.ltiaas.com/lti/register/canvas`
5. Click the 'Save' button at the bottom when the form is filled.
6. After saving, find the newly registered tool's key on the list of keys and make sure the State is switched to 'ON'
7. Note the number in the details column. It will be used as the `Client ID` in the next steps
### Step 2. Install the Tool into Canvas
1. In the Canvas Admin menu, navigate to 'Settings' -> 'Apps'
2. Click the '+ App' button
3. In the resulting form choose:
* **Configuration Type:** `By Client ID`
* **Client ID:** `[The Client ID number you found in the previous series of steps]`
4. Click the 'Submit' button
5. In the resulting dialog, click 'Install'
6. The tool will now show up in the 'External Apps' table.
### Step 3. Register the Canvas Provided Details into LTIAAS
A registration can be done within the [LTIAAS Portal](https://portal.ltiaas.com). Start by logging in to your account and navigating to the `Registrations` page. Then click on the `+ Add Registration` button.
Enter the information that the LMS provides for its LTI® endpoints using the table below as a guide.
LTIAAS Registration API Name
Canvas Provided Name (Instructure Cloud)
Canvas Provided Name (Self Hosted)
Name
Any name you would like that helps you identify your LMS
Client ID
The client ID found in the first sequence of steps
URL
https://canvas.instructure.com
https://[YOUR_CANVAS_URL] (or, at times) https://canvas.instructure.com
**Info:**
Replace **[YOUR_CANVAS_URL]** with the base URL of your canvas instance. For example: _canvas.my-university.edu_ or _canvas.instructure.com_. More information about the Canvas LTI® 1.3 endpoints can be found on **[Instructure's website](https://canvas.instructure.com/doc/api/file.lti_dev_key_config.html)**.
**Caution:**
If a registration is not working, one of the likely causes is an incorrect `URL`. Please try both `https://canvas.instructure.com` and `https://[YOUR_CANVAS_URL]`.
Click submit when you have made the necessary changes. Also, you can edit these values at any time.
### Step 4. (Optional) Edit the Registration
If at any time you want to edit or delete a registration, simply click on the blue pencil icon on the leftmost column of the table for the registration item you want to change.
By default, all new registrations are activated upon creation. There may be an instance where a platform was registered, but you don't want the registration to be active. You can activate/deactivate a registration by clicking on the `true/false` button on the *Active* column of the table.
### Step 5. Do a Test Launch
**Tip:**
You may need to **[add placements](https://community.canvaslms.com/t5/Instructor-Guide/How-do-I-configure-a-manual-entry-external-app-for-a-course/ta-p/1137#:~:text=To%20edit%20the%20app%2C%20click%20the%20Edit%20link%20%5B2%5D.%20To%20manage%20where%20the%20app%20displays%20in%20Canvas%2C%20click%20the%20Placements%20link%20%5B3%5D.%20To%20delete%20the%20app%2C%20click%20the%20Delete%20link%20%5B4%5D)** for the newly installed tool before adding it to certain places within the LMS.
1. Follow the instructions on the Canvas website to add an external tool assignment to a course: [How do I add an assignment using an external app?](https://community.canvaslms.com/t5/Instructor-Guide/How-do-I-add-an-assignment-using-an-external-app/ta-p/656)
2. When choosing an external tool, choose the newly registered tool from step 1.
## Manual Registration
### Summary
To set up LTIAAS with Canvas using Manual Registration, you need to:
1. [Add a developer LTI® key in Canvas](#step-1-add-a-developer-key-in-canvas-1)
2. [Install the LTIAAS tool into Canvas](#step-2-install-the-ltiaas-tool-into-canvas-1)
3. [Register the Canvas provided details into LTIAAS](#step-3-register-the-canvas-provided-details-into-ltiaas-1)
4. [Do a test launch](#step-5-do-a-test-launch-1)
### Step 1. Add a Developer Key in Canvas
1. Login to your Canvas LMS as an administrator.
2. Navigate to 'Admin' -> [your account name] -> 'Developer Keys'
3. Click the '+ Developer Key' button followed by the '+ LTI® Key' dropdown
4. In the resulting form, enter the following information:
- **Key name:** `Your Tool Name`
- **Method:** `Manual Entry`
- **Redirection URI(s):** `https://your.ltiaas.com/lti/launch`
- **Title:** `Your Title`
- **Description:** `Your Tool Description`
- **Target Link URI:** `https://your.ltiaas.com/lti/launch`
- **OpenID Connect Initiation Url:** `https://your.ltiaas.com/lti/login`
- **JWK Method:** `Public JWK URL`
- **Public JWK URL:** `https://your.ltiaas.com/lti/keys`
5. Further down in the form, select the services your tool needs. The screenshot below shows an example of a tool that uses assignments, grades, and the launch context (i.e. the course ID).
6. Toward the bottom of the form, add placements for your tool. These are the locations in the LMS where your tool can be added. If you are using Deep Linking, select `LtiDeepLinkingRequest`. Otherwise, select `LtiResourceLinkRequest` for a basic LTI® launch.
7. Click the 'Save' button at the bottom when the form is filled.
8. After saving, find the newly registered tool's key on the list of keys and make sure the State is switched to 'ON'.
9. Note the number in the details column. It will be used as the `Client ID` in the next steps.
### Step 2. Install the LTIAAS Tool into Canvas
1. In the Canvas Admin menu, navigate to 'Settings' -> 'Apps'
2. Click the '+ App' button
3. In the resulting form choose:
* **Configuration Type:** `By Client ID`
* **Client ID:** `[The Client ID number you found in the previous series of steps]`
4. Click the 'Submit' button
5. In the resulting dialog, click 'Install'
6. The tool will now show up in the 'External Apps' table.
### Step 3. Register the Canvas Provided Details into LTIAAS
A registration can be done within the [LTIAAS Portal](https://portal.ltiaas.com). Start by logging in to your account and navigating to the `Registrations` page. Then click on the `+ Add Registration` button.
Enter the information that the LMS provides for its LTI® endpoints using the table below as a guide.
LTIAAS Registration API Name
Canvas Provided Name (Instructure Cloud)
Canvas Provided Name (Self Hosted)
Name
Any name you would like that helps you identify your LMS
Client ID
The client ID found in the first sequence of steps
URL
https://canvas.instructure.com
https://[YOUR_CANVAS_URL] (or, at times) https://canvas.instructure.com
**Info:**
Replace **[YOUR_CANVAS_URL]** with the base URL of your canvas instance. For example: _canvas.my-university.edu_ or _canvas.instructure.com_. More information about the Canvas LTI® 1.3 endpoints can be found on **[Instructure's website](https://canvas.instructure.com/doc/api/file.lti_dev_key_config.html)**.
**Caution:**
If a registration is not working, one of the likely causes is an incorrect `URL`. Please try both `https://canvas.instructure.com` and `https://[YOUR_CANVAS_URL]`.
Click submit when you have made the necessary changes. Also, you can edit these values at any time.
### Step 4. (Optional) Edit the Registration
If at any time you want to edit or delete a registration, simply click on the blue pencil icon on the leftmost column of the table for the registration item you want to change.
By default, all new registrations are activated upon creation. There may be an instance where a platform was registered, but you don't want the registration to be active. You can activate/deactivate a registration by clicking on the `true/false` button on the *Active* column of the table.
### Step 5. Do a Test Launch
**Tip:**
You may need to **[add placements](https://community.canvaslms.com/t5/Instructor-Guide/How-do-I-configure-a-manual-entry-external-app-for-a-course/ta-p/1137#:~:text=To%20edit%20the%20app%2C%20click%20the%20Edit%20link%20%5B2%5D.%20To%20manage%20where%20the%20app%20displays%20in%20Canvas%2C%20click%20the%20Placements%20link%20%5B3%5D.%20To%20delete%20the%20app%2C%20click%20the%20Delete%20link%20%5B4%5D)** for the newly installed tool before adding it to certain places within the LMS.
1. Follow the instructions on the Canvas website to add an external tool assignment to a course: [How do I add an assignment using an external app?](https://community.canvaslms.com/t5/Instructor-Guide/How-do-I-add-an-assignment-using-an-external-app/ta-p/656)
2. When choosing an external tool, choose the newly registered tool from step 1.
## Canvas Placements
Canvas supports many different LTI placements. A full list is available [in their developer documentation](https://canvas.instructure.com/doc/api/file.placements_overview.html).
When registering your tool, you need to decide where you want your tool to appear within Canvas. Here are some common placements and when they might be used:
### Assignment Selection
This is the most common placement. It is used when you want to use an LTI tool to generate a link or links that will be placed as an activity within a course. This placement supports a simple launch and deep linking.
### Global Navigation
If your tool does not need to send grades and is simply used as a launch-point from with the LMS, you can use this placement to add a link to your tool in the main Canvas sidebar. This placement supports a simple launch only, not deep linking.
### Course Navigation
If your tool does not need to send grades and is simply used as a launch-point from with the LMS, you can use this placement to add a link to your tool in a course's sidebar. This placement supports a simple launch only, not deep linking.
### Course Assignments Menu
The _Course Assignments Menu_ is used when your LTI tool is designed to use deep linking and the deep-linking process returns an array of LTI items. This placement supports a simple launch and deep linking, but is typically only used with deep linking.
## Next Steps
Now that you have your LTI® tool set up in an LMS, you can start receiving LTI® launches. Please see the [Receiving your first LTI® launch](/guides/first-launch) guide for more information.
## Moodle
Source: https://ltiaas.com/guides/lms/moodle
**Tip:**
This page describes how to install an LTI® tool into Moodle.
If you are looking for instructions on how to use Moodle itself as an LTI® tool, please follow [this guide on using the LTIAAS Moodle plugin](../resources/moodle-as-an-lti-cms).
The Moodle LMS supports two different registration methods: **[dynamic registration](#dynamic-registration)** and **[manual registration](#manual-registration)**. This guide covers both methods.
## Dynamic Registration
**Info:**
Dynamic Registration is available in version 3.10 or higher of Moodle.
**Caution:**
Make sure that Dynamic Registration for your LTIAAS API is enabled during the LTIAAS onboarding process.
### Summary
To set up LTIAAS with Moodle using dynamic registration, you need to:
1. [Add a new 'LTI® Advantage' Tool in your Moodle installation](#step-1-add-a-new-lti-advantage-tool-in-your-moodle-installation)
2. [Do a test launch](#step-2-do-a-test-launch)
### Step 1. Add a New 'LTI Advantage' Tool in Your Moodle Installation
1. Login to your Moodle installation as an administrator.
2. Navigate to 'Site administration' -> 'Plugins' -> 'Activity modules' -> 'External tool' -> 'Manage tools'
3. In the `Tool Url` field enter: `https://your.ltiaas.com/lti/register`, where `your` is the custom subdomain set up in the LTIAAS portal.
4. Click the `Add LTI® Advantage` button
Once the tool is registered, you can view and activate/deactivate the registration in the [LTIAAS portal](https://portal.ltiaas.com).
### Step 2. Do a Test Launch
Adding your tool to a course in Moodle is slightly different depending on which version of Moodle that you are using.
- [Launching in Moodle 3.x](#launching-in-moodle-3x)
- [Launching in Moodle 4.x](#launching-in-moodle-4x)
## Manual Registration
### Summary
To set up LTIAAS with Moodle using manual registration, you need to:
1. [Create a new tool in the Moodle installation](#step-1-create-a-new-external-tool-in-your-moodle-installation)
2. [Register the Moodle installation inside LTIAAS](#step-2-register-the-moodle-installation-inside-ltiaas)
3. [Do a test launch](#step-4-do-a-test-launch)
### Step 1. Create a New 'External Tool' in Your Moodle Installation
1. Login to your Moodle installation as an administrator.
2. Navigate to 'Site administration' -> 'Plugins' -> 'Activity modules' -> 'External tool' -> 'Manage tools'
3. Click the 'configure a tool manually' link
4. In the resulting form. configure the tool with the following information:
- **Tool name:** `Your Tool Name`
- **Tool URL:** `https://your.ltiaas.com/lti/launch`
- **Tool description:** `Your Tool Description`
- **LTI® version:** `LTI® 1.3`
- **Public key type:** `Keyset URL`
- **Public keyset:** `https://your.ltiaas.com/keys`
- **Initiate login URL:** `https://your.ltiaas.com/login`
- **Redirection URI(s):** `https://your.ltiaas.com/lti/launch`
- **Tool configuration usage:** `Show in activity chooser as a preconfigured tool`
- **Default launch container:** _(your choice)_
- **Supports Deep Linking (Content-Item Message):** `CHECKED` \<\- _This is needed if the LTIAAS deep linking service is going to be used. Otherwise leave it unchecked._
- **Content Selection URL:** _(left blank)_
- **Icon URL:** `https://your-webiste.com/`
- **Secure icon URL:** `https://your-website.com/`
- **IMS LTI® Assignment and Grade Services:**`Use this service for grade sync and column management` <- _This is needed if the LTIAAS assignments and grades service is going to be used._
- **IMS LTI® Names and Role Provisioning:** `Use this service to retrieve members' information as per privacy settings` <- _This is needed if the LTIAAS names and roles service is going to be used._
- **Tool Settings:** Use this service
- **Share launcher's name with tool:** `Delegate to teacher (recommended)`
- **Share launcher's email with tool:** `Delegate to teacher (recommended)`
- **Accept grades from the tool:** `Delegate to teacher (recommended)`
- **Force SSL:** `CHECKED`
**Note:**
NOTE: When using LTIAAS, the Content Selection URL should be left blank. This URL is configured within the LTIAAS setup.
5. Click the 'Save changes' button at the bottom when the form is filled. You can always go back and change this form later if you need to.
6. After saving, find the tool on the list of tools and click the button that has 4 horizontal lines.
7. A dialog should pop up that shows the information that you need to provide to LTIAAS. Save this for the next step.
### Step 2. Register the Moodle Installation Inside LTIAAS
A registration can be done from within the [LTIAAS Portal](https://portal.ltiaas.com). Start by logging in to your account and navigating to the `Registrations` page. Then click on the `+ Add Registration` button.
Enter the information that the LMS provides for its LTI® endpoints using the table below as a guide.
LTIAAS Registration API Name | Moodle Provided Name
----------- | ---------------
**Name** | _Any name you would like that helps you identify your LMS_
**Client ID** | _Client ID_
**URL** | _Platform ID_
**Authentication Method** | `JWK_SET`
**Authentication Key** | _Public Keyset URL_
**Access Token Endpoint** | _Access Token URL_
**Authentication Endpoint** | _Authentication Request URL_
Click submit when you have made the necessary changes. Also, you can edit these values at any time.
### Step 3. (Optional) Edit the Registration
If at any time you want to edit or delete a registration, simply click on the blue pencil icon on the leftmost column of the table for the registration item you want to change.
By default, all new registrations are activated upon creation. There may be an instance where a platform was registered, but you don't want the registration to be active. You can activate/deactivate a registration by clicking on the `true/false` button on the *Active* column of the table.
### Step 4. Do a Test Launch
Adding your tool to a course in Moodle is slightly different depending on which version of Moodle that you are using.
- [Launching in Moodle 3.x](#launching-in-moodle-3x)
- [Launching in Moodle 4.x](#launching-in-moodle-4x)
## Launching in Moodle 3.X
If you set **Tool configuration usage** = `Show in activity chooser as a preconfigured tool` when the tool was installed, you can select the tool easily when adding an activity:
1. In the Moodle installation, go to a course and turn editing on.
2. Press the "Add and activity or resource" link in one of the section of the course.
3. Select the newly created tool.
4. Give the Activity a name, set any other options to your liking, and click 'Save and display'
5. If the setup was successful, you should see the new tool launch successfully.
If you (or the Moodle administrator) set **Tool configuration usage** to a different value, you will have ot go through an extra step when adding the tool to the course:
1. In the Moodle installation, go to a course and turn editing on.
2. Press the "Add and activity or resource" link in one of the section of the course.
3. _Select 'External Tool'_
4. _Select your tool from the 'Preconfigured Tool' list_
5. Give the Activity a name, set any other options to your liking, and click 'Save and display'
6. If the setup was successful, you should see the new tool launch successfully.
**Note:**
If your tool has deeplinking configured, you can launch the deeplinking view by clicking the `Select Content` button before saving/displaying the tool. Otherwise, after you save, the tool will only launch to the default launch URL.
## Launching in Moodle 4.X
Adding a tool to a course in Moodle 4.x is a little bit more complicated than Moodle 3.x, as you have to first allow the tool to be used globally within the course before adding it as an activity. This is done by:
1. Navigate to the course home page and click 'More' -> 'LTI® External Tools'
2. In the resulting screen, toggle the appropriate 'Show activity in chooser' to the enabled position for the tool you want this course to have access to.
Once the above steps are done, you can proceed in adding the tool as an activity in the course.
1. In the Moodle installation, go to a course and turn editing on.
2. Press the "Add and activity or resource" link in one of the section of the course.
3. Select the newly created tool.
4. Give the Activity a name, set any other options to your liking, and click 'Save and display'
5. If the setup was successful, you should see the new tool launch successfully.
**Caution:**
Unlike Moodle 3.x, in Moodle 4.x if your tool has deeplinking configured, you _are required to_ execute deeplinking by clicking the `Select Content` button during step 4 above. There is no way to launch the default launch URL if deeplinking is enabled.
## Next Steps
Now that you have your LTI® tool set up in an LMS, you can start receiving LTI® launches. Please see the [Receiving your first LTI® launch](/guides/first-launch) guide for more information.
## Others
Source: https://ltiaas.com/guides/lms/others
## Registering LMSes with LTIAAS
To register an LMS with an LTIAAS tool, the registration must first be completed within the LMS. After this, the LMS creates a `Client ID` that can be registered in LTIAAS.
**Tip:**
Most of the popular LMSes have [dedicated guides](/guides/lms/introduction#section-summary) that explain how to initiate the registration.
### Step 1. Register the LTIAAS Tool into the LMS
The following values need to be registered into the LMS:
- **Tool URL:** `https://your.ltiaas.com/lti/launch`
- **Public keyset URL:** `https://your.ltiaas.com/keys`
- **Initiate login URL:** `https://your.ltiaas.com/login`
- **Redirection URL:** `https://your.ltiaas.com/lti/launch`
#### Step 2. Register the LMS into LTIAAS
When step 1 was completed inside the LMS, a `Client ID` should have been provided as a result. The LMS will also provide the necessary endpoints required to register the LMS into the tool.
A registration can be done within the [LTIAAS Portal](https://portal.ltiaas.com). Start by logging in to your account and navigating to the `Registrations` page. Then click on the `+ Add Registration` button.
Enter the information that the LMS provides for its LTI® endpoints. The example below shows what a production Canvas setup might look like.
Note that the `clientID` field in this form should be filled with the client ID provided by the LMS in step 1.
Click submit when you have made the necessary changes. Also, you can edit these values at any time.
### Step 3. (Optional) Edit the Registration
If at any time you want to edit or delete a registration, simply click on the blue pencil icon on the leftmost column of the table for the registration item you want to change.
By default, all new registrations are activated upon creation. There may be an instance where a platform was registered, but you don't want the registration to be active. You can activate/deactivate a registration by clicking on the `true/false` button on the *Active* column of the table.
### Step 4. Do a Test Launch
Once the platform is registered (and activated) you can now go to the LMS and do a trial launch of the tool. The LMS will have a way to create a new activity and place the tool in the LMS. Once this is done, you should be able to click the link that the LMS created that links to your tool. If everything went well, you should see the `launch URL` that you configured in the LTIAAS portal displayed within the LMS.
## Next Steps
Now that you have your LTI® tool set up in an LMS, you can start receiving LTI® launches. Please see the [Receiving your first LTI® launch](/guides/first-launch) guide for more information.
## Schoology
Source: https://ltiaas.com/guides/lms/schoology
## Manual Registration
The Scoology LMS only supports manual tool registration. This guide explains how to manually register an LTIAAS tool with Schoology.
### Summary
To set up LTIAAS with Schoology you need to:
1. [Create a new tool in the Schoology installation](#step-1-create-a-new-tool-in-the-schoology-installation)
2. [Register the Schoology installation inside LTIAAS](#step-2-register-the-schoology-installation-inside-ltiaas)
3. [Do a test launch](#step-4-do-a-test-launch)
### Step 1. Create a New Tool in the Schoology Installation
1. Login to your Schoology installation as an administrator.
2. Navigate to 'Grid Icon' -> 'App Center'
3. Then choose 'Organization Apps' or 'My developer Apps' depending on your intention.
4. On the next page, click 'Add App'
4. In the resulting form, fill out the data, such as name and description, as you see fit. At the bottom of the form, choose _Type of App_ = 'LTI® 1.3 App' and configure the tool with the following information:
* **Privacy:** `Choose the user information your tool requires the LMS to send`
* **Deep Linking:** `CHECKED` <- If you have the LTIAAS deep linking service enabled
* **Assignments and Grade Services:** `CHECKED` <- If you have the LTIAAS assignments and grades service enabled
* **Names and Roles Services:** `CHECKED` <- If you have the LTIAAS names and roles service enabled
* **JWKS URL:** `https://your.ltiaas.com/keys`
* **Domain/URL:** `https://your.ltiaas.com/lti/launch`
* **Public key type:** `Keyset URL`
* **OIDC Login Init URL:** `https://your.ltiaas.com/login`
* **Redirect URLs:** `https://your.ltiaas.com/lti/launch`
5. Accept the terms and click the 'Submit' button at the bottom when the form is filled. You can always go back and change this form later if you need to.
6. Back on the list of apps, find your app and click the 'Install LTI® 1.3 App' Button. In the resulting dialogs:
1. Click 'I Agree'
2. Click 'Continue'
3. Choose where to install the app (i.e. 'Add to Organization' or 'Install for Me')
4. You may be asked to choose where to make the app available. i.e. which courses/users should have access to the app.
7. Back on the App Center list of apps, find your app and click 'Options' -> 'API Info'. In the resulting dialog, record the 'Client ID' for the next steps.
### Step 2. Register the Schoology Installation Inside LTIAAS
A registration can be done within the [LTIAAS Portal](https://portal.ltiaas.com). Start by logging in to your account and navigating to the `Registrations` page. Then click on the `+ Add Registration` button.
Enter the information that the LMS provides for its LTI® endpoints using the table below as a guide.
LTIAAS Registration API Name | Schoology Provided Name
----------- | ---------------
**Name** | _Any name you would like that helps you identify the LMS_
**Client ID** | _Client ID_
**URL** | _https://schoology.schoology.com_
**Authentication Method** | `JWK_SET`
**Authentication Key** | _https://lti-service.svc.schoology.com/lti-service/.well-known/jwks_
**Access Token Endpoint** | _https://lti-service.svc.schoology.com/lti-service/access-token_
**Authentication Endpoint** | _https://lti-service.svc.schoology.com/lti-service/authorize-redirect_
Click submit when you have made the necessary changes. Also, you can edit these values at any time.
### Step 3. (Optional) Edit the Registration
If at any time you want to edit or delete a registration, simply click on the blue pencil icon on the leftmost column of the table for the registration item you want to change.
By default, all new registrations are activated upon creation. There may be an instance where a platform was registered, but you don't want the registration to be active. You can activate/deactivate a registration by clicking on the `true/false` button on the *Active* column of the table.
### Step 4. Do a Test Launch
1. In the Schoology installation, go to a course.
2. Press the "Add Materials" button.
3. Select the newly created tool.
4. If your app uses deep linking, your deep linking content selection screen will appear. If not, give the Activity a name, set any other options to your liking, and click 'Save Changes'
5. If the setup was successful, you should see the new tool launch successfully.
**Danger:**
When using deep linking, Schoology does not correctly set the tool provider when adding the tool. Once the tool link is added to the course, click the gear icon and then 'edit settings'. Change the _Tool Provider_ field back to your tool name and the click 'Save Changes'. :::
## Next Steps
Now that you have your LTI® tool set up in an LMS, you can start receiving LTI® launches. Please see the [Receiving your first LTI® launch](/guides/first-launch) guide for more information.
# Integration flows
## Integration Flows
Source: https://ltiaas.com/guides/flows
# Integration Flows
The guides in this section walk through each part of an LTIAAS integration from end to end, illustrating the flow of data between the LMS, LTIAAS, and your application. Where the LTI® API reference describes individual endpoints in isolation, these flow guides show how those endpoints fit together to accomplish a complete task — registering an LMS, embedding content, signing a user in, or sending a grade back to the gradebook.
Each guide is also explicit about the division of responsibility. LTIAAS handles the LTI® protocol — the OIDC handshakes, JWT signing and verification, and service proxying — while your application is responsible for the pieces that are unique to your product, such as the *Deep Linking* content selection view, the user provisioning that backs single sign-on, or the background jobs that reconcile grades. Every flow calls out which steps belong to which side.
If you're building an integration for the first time, it's recommended to read the flows in order: registration establishes the trust between the LMS and your tool, launch and SSO turn a launch into a logged-in user, and the remaining flows layer on the optional *Deep Linking*, *Assignment and Grades*, and *Names and Roles Provisioning* services.
## Async / Service-Key Grading
Source: https://ltiaas.com/guides/flows/async-grading
# Async / Service-Key Grading
**Tip:**
Make sure you've read about [authenticating API requests](/guides/api/authentication) and [accessing the API asynchronously](/guides/api/async) before proceeding to the guide below.
**Info:**
The AGS and NRPS endpoints accept both the [ltik based](/guides/api/authentication#ltik-based-authentication) and [service key based](/guides/api/authentication#service-key-based-authentication) API authentication methods.
Due to the nature of LTI®, it's recommended to always utilize the LTI® services synchronously, within the context of an LTI® launch. For most use cases it should be enough and a lot easier to trigger periodic tasks when someone launches into your tool. However, some scenarios make synchronous calls impractical: a nightly job that pushes every student's progress to the LMS gradebook, a webhook from an external grading service that fires hours after the original launch, a roster refresh on a schedule, or a backfill of grades after fixing a scoring bug. For these cases, the `ltik` token is not suitable, because it expires 24 hours after the launch in which it was issued.
LTIAAS exposes a long-lived alternative called the `serviceKey`, which is issued during any LTI® launch that has access to the *Assignment and Grades* or *Names and Roles Provisioning* services. The `serviceKey` does not expire, can be persisted in your application's database, and can be used outside of an LTI® launch via the [service key based authentication method](/guides/api/authentication#service-key-based-authentication). This guide walks through the end-to-end pattern for using the `serviceKey` to submit grades asynchronously: capturing it at launch time, scheduling background jobs, swapping the authentication header, batching score submissions, and surfacing operational health.
}
>
When a user launches into your tool and the launch context has access to at least one of the *Assignment and Grades* or *Names and Roles Provisioning* services, the ID Token returned by `/api/idtoken` includes a `services.serviceKey` field. Your application's back-end should capture this value during the launch and persist it in your own database, keyed by the `(platformId, contextId, resourceLinkId)` triple that identifies the launch context.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const idtoken = await requests.get(
'https://your.ltiaas.com/api/idtoken',
{ headers }
)
const serviceKey = idtoken['services']['serviceKey']
// Persist the serviceKey alongside the context that originated it.
await storeServiceKeyInDatabase({
platformId: idtoken['platform']['id'],
contextId: idtoken['launch']['context']['id'],
resourceLinkId: idtoken['launch']['resourceLink']['id'],
serviceKey
})
```
**Caution:**
The `serviceKey` does not expire; however, due to the nature of LTI®, the information stored within a certain `serviceKey` might become inaccurate at some point. For this reason it's recommended to update the `serviceKey` for a certain context on every launch by overwriting the stored value. This guarantees that your application always has the freshest reference to the launch context.
**Tip:**
The `serviceKey` grants API access to its associated context indefinitely, so it should be encrypted at rest. It's also useful to store the `(platformId, contextId, resourceLinkId)` triple alongside activity and grading information in your application's own database, so that background jobs can find the right `serviceKey` for the work they need to do.
You can read more about the structure and lifecycle of the `serviceKey` in the [Accessing the API asynchronously guide](/guides/api/async).
}
>
LTIAAS does not run background jobs on your behalf — that infrastructure lives in your application. The pattern is the same regardless of which job runner your application uses. A typical setup is a cron job that fires once a day and scans for activities with unsynced grades, a job queue (BullMQ, Sidekiq, Celery, SQS, and similar) that picks up "sync grades for activity X" jobs as your tool finishes scoring attempts, or a webhook handler that enqueues sync jobs in response to events from an external grading service.
Each job needs three pieces of information to act:
1. The `(platformId, contextId, resourceLinkId)` triple that identifies the activity being graded.
2. The set of user IDs and scores to submit.
3. The `serviceKey` from Step 1, looked up by that triple.
```javascript
// Inside a background worker
const job = await queue.next()
const { platformId, contextId, resourceLinkId, grades } = job
const serviceKey = await getServiceKeyFromDatabase({
platformId, contextId, resourceLinkId
})
await submitScoresForActivity({ serviceKey, resourceLinkId, grades })
await markJobComplete(job.id)
```
**Tip:**
The queue, the worker, the idempotency keys for individual jobs, the dead-letter handling, and any retry policy live entirely inside your application. LTIAAS only provides the API endpoints; it does not impose a particular execution model.
For more guidance on the asynchronous access pattern, see the [Accessing the API asynchronously guide](/guides/api/async).
}
>
The only difference between synchronous and asynchronous calls to the AGS and NRPS endpoints is the `Authorization` header. Synchronous calls during a launch use the `LTIK-AUTH-V2` header carrying the `ltik`; asynchronous calls outside of a launch use the `SERVICE-AUTH-V1` header carrying the `serviceKey`. Every other request shape — URL, body, response — is identical.
```
# Synchronous (during a launch):
Authorization: LTIK-AUTH-V2 :
# Asynchronous (background job, no launch in flight):
Authorization: SERVICE-AUTH-V1 :
```
It's recommended to design your application's HTTP client to accept either kind of credential, so that the same code paths can be reused in synchronous and asynchronous contexts.
```javascript
function buildAuthHeader({ ltik, serviceKey }) {
if (ltik) return `LTIK-AUTH-V2 ${API_KEY}:${ltik}`
if (serviceKey) return `SERVICE-AUTH-V1 ${API_KEY}:${serviceKey}`
throw new Error('Either ltik or serviceKey is required.')
}
```
**Caution:**
Only the AGS and NRPS endpoints accept the `SERVICE-AUTH-V1` scheme. The `/api/idtoken` and `/api/deeplinking` endpoints are tied to the live launch and only accept `LTIK-AUTH-V2`. Attempting to call them asynchronously will result in an authentication error.
}
>
With the `serviceKey` in hand, the asynchronous score submission looks identical to the synchronous version described in the [Grade Passback guide](/guides/flows/grade-passback). The only changes are the `Authorization` header (already covered in Step 3) and the absence of a live launch context. There is no bulk score endpoint, so each user requires an individual POST to `/api/lineitems/:lineItemID/scores`. For a typical class size of around 100 students, sequential submission is acceptable; for larger cohorts, it's recommended to parallelize with a small concurrency cap of four to eight requests in flight to avoid the LMS's rate limits.
```javascript
// Building service key based API authentication header
const authorizationHeader = `SERVICE-AUTH-V1 ${API_KEY}:${serviceKey}`
const headers = { Authorization: authorizationHeader }
// Look up the line item by your application's resourceId
const lineItemsResponse = await requests.get(
`https://your.ltiaas.com/api/lineitems?resourceId=${resourceId}`,
{ headers }
)
const lineItemId = lineItemsResponse['lineItems'][0]['id']
const urlSafeLineItemId = encodeURIComponent(lineItemId)
// Submit each user's score
for (const grade of activity.grades) {
const score = {
userId: grade.userId,
scoreGiven: grade.score,
scoreMaximum: 100,
activityProgress: 'Completed',
gradingProgress: 'FullyGraded'
}
await requests.post(
`https://your.ltiaas.com/api/lineitems/${urlSafeLineItemId}/scores`,
score,
{ headers }
)
}
```
**Tip:**
It's recommended to re-fetch the `lineItemId` once per job, rather than caching it across many days. The underlying line item could have been recreated, renamed, or deleted by the teacher between runs, and using a stale `lineItemId` will result in a `404` response from the LMS.
For a description of the full score envelope and its allowed values, see [Submitting grades](/guides/api/manipulating-grades#submitting-grades) in the Manipulating Grades guide.
}
>
The LMS is the source of truth for grades, and it can be temporarily unavailable. It's recommended to build a retry policy that distinguishes between transient failures, which should be retried with exponential backoff, and permanent failures, which should be logged and escalated rather than retried. The table below lists the common response classes and the recommended action for each.
| Response | Cause | Recommended action |
|---|---|---|
| `2xx` | Score successfully submitted. | Mark the score sent; move on. |
| `5xx` / network error | LMS unavailable, gateway hiccup. | Retry with exponential backoff for up to five attempts over about ten minutes. |
| `401` / `403` | `serviceKey` was invalidated, scope revoked, or the line item was deleted. | Stop retrying. Alert ops and prompt the user for a fresh launch to refresh the `serviceKey`. |
| `400` / `422` | Invalid payload — for example, an unknown `userId` or an out-of-range score. | Skip the user, log loudly, and continue with the rest of the batch. |
```javascript
async function submitWithRetry(score, attempt = 0) {
const res = await submitScore(lineItemId, score, { serviceKey })
if (res.ok) return
if (res.status >= 500 && attempt < 5) {
const delay = Math.min(2 ** attempt * 1000, 60_000)
await sleep(delay)
return submitWithRetry(score, attempt + 1)
}
if (res.status === 401 || res.status === 403) {
await markServiceKeyStale({ platformId, contextId, resourceLinkId })
throw new PermanentScoreError(res.status, score.userId)
}
if (res.status >= 400 && res.status < 500) {
await logSkippedScore(score, res.status)
return
}
throw new PermanentScoreError(res.status, score.userId)
}
```
**Tip:**
Score submissions to a line item are idempotent — re-submitting the same `userId` with the same `scoreGiven` is safe, because the LMS treats subsequent submissions as updates rather than additions. If your worker crashes mid-batch, simply re-running the job will not result in duplicate grades.
When a `serviceKey` becomes stale (typically because the user changed their LMS password, the teacher deleted the assignment, or the LMS admin revoked the OAuth scopes), it's recommended to surface a banner in your application's UI telling the relevant teacher to re-open the assignment. Re-opening the assignment generates a fresh launch, which produces a new `serviceKey` that overwrites the stale one in your database.
}
>
When grades silently fail to reach the LMS, the teacher is usually the first to notice — typically as a support ticket complaining that the gradebook is missing data. It's recommended to build observability into your asynchronous grading pipeline so that your team can detect problems before customers do. The shape of this observability depends on your stack, but the core ingredients are common.
It's useful to expose a dashboard showing recent job runs together with their started, sent, retried, and skipped counts. A per-activity drill-down that lists which scores have and have not made it to the LMS lets a support engineer quickly answer a teacher's question about a specific student. Alerting should fire when the overall skip rate exceeds a threshold or when a single platform's success rate drops, since those are the early warning signs of a broken `serviceKey` or a misconfigured registration.
A periodic reconciliation job is also worth building. It calls the LMS's view of the scores for each line item via `GET /api/lineitems/:lineItemID/scores` and compares the result against your application's local view of what was submitted. Drift between the two is a sign that something went wrong, either in your worker or on the LMS side.
```javascript
// Reconciliation sketch (run weekly per line item)
const remote = await getScoresFromLMS(lineItemId, { serviceKey })
const local = await db.sentScores.findMany({ where: { lineItemId } })
const drift = local.filter((s) =>
!remote['scores'].find((r) =>
r['userId'] === s.userId && r['resultScore'] === s.scoreGiven
)
)
if (drift.length > 0) await alertOps(lineItemId, drift)
```
**Tip:**
The dashboard, alerts, and reconciliation job all live in your application. LTIAAS provides the API endpoints for retrieving the remote view; the operational tooling that surrounds those endpoints is your application's responsibility.
For details on the response format of the score retrieval endpoint, see [Retrieving grades](/guides/api/manipulating-grades#retrieving-grades) in the Manipulating Grades guide.
---
## What's Next
- [Grade Passback flow](/guides/flows/grade-passback) — the synchronous version of this guide. Read it first to understand the per-user score envelope, then return here for the asynchronous pattern.
- [Names & Roles flow](/guides/flows/names-and-roles) — the same `serviceKey` technique works for refreshing class rosters from a cron job, outside of a live launch.
- [Accessing the API asynchronously](/guides/api/async) — the underlying authentication method this flow is built on.
## Deep Linking Flow
Source: https://ltiaas.com/guides/flows/deep-linking
# Deep Linking
**Tip:**
Make sure you've read about [authenticating API requests](/guides/api/authentication) before proceeding to the guide below.
**Info:**
The Deep Linking endpoint only accepts the [ltik based authentication method](/guides/api/authentication#ltik-based-authentication).
If your application is a hub of multiple resources or activities, the LTI® protocol makes this easy to manage through the *Deep Linking* service. *Deep Linking* lets a privileged user within the LMS, usually a teacher or administrator, pick a specific resource from your application and embed it into the LMS as an assignment, a module item, or a generic resource link. From that point forward, students can launch directly into that resource without your tool having to display a content selection view every time.
LTIAAS gives you access to the *Deep Linking* service through the Deep Linking launch and the `/api/deeplinking/form` API endpoint. LTIAAS handles every piece of the protocol — verifying the LMS's signed deep-linking request, signing your tool's content selection response, and proxying the JWT back to the LMS. Your application is responsible for displaying the content selection view to the teacher, since the look and feel of that picker is unique to each tool.
}
>
The teacher opens the LMS's assignment editor, module builder, or external-tool insertion UI, and chooses your tool from the list of registered LTI® tools. Different LMSes label this action differently — Canvas calls it "External Tool", Schoology calls it "External Content", and Moodle calls it "External tool" — but the underlying mechanism is the same: the LMS sends an LTI® Deep Linking launch request to your tool's deep linking URL.
Whether the LMS even presents this option to the teacher depends on how your tool was registered with the LMS. The `LtiDeepLinkingRequest` message type must have been included in the registration, with an appropriate placement (such as `ContentArea` or `link_selection` in Canvas terminology). See the [Dynamic LMS Registration guide](/guides/flows/lms-registration) for details on enabling deep-linking placements.
}
>
A deep-linking launch works in the same way as a regular LTI® launch, with the exception that LTIAAS will redirect the user to your application's registered **deep-linking target launch URL** rather than the normal launch URL. The deep-linking launch's ID Token also contains a `services.deepLinking` section with availability information, which your application's back-end can check before attempting to render a content picker.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const idtoken = await requests.get(
'https://your.ltiaas.com/api/idtoken',
{ headers }
)
const isServiceAvailable = idtoken['services']['deepLinking']['available']
if (!isServiceAvailable) {
// This launch isn't a deep-linking request; render a normal launch view.
return renderNormalLaunch(idtoken)
}
```
After receiving the Deep Linking launch, your application should display a content selection view. The user will be able to choose which resource provided by your application should be linked to the activity being created in the LMS.
**Tip:**
Your application can display a personalized content selection view for the user that initiated the LTI® launch based on their access level and role. The `idtoken.user.roles` field tells you whether the user is an Instructor, Administrator, TeachingAssistant, or other role, and `idtoken.platform.id` identifies the LMS the user came from. See the [Retrieving the ID Token guide](/guides/api/idtoken) for the full structure of the ID Token.
}
>
After the user selects the desired resource, your application's front-end should make a request to your back-end identifying the selection. Your back-end then calls the LTIAAS Deep Linking API to build a signed deep-linking response that the LMS will accept.
The `/api/deeplinking/form` endpoint accepts a POST request whose body contains a `contentItems` array. Each entry in the array describes one resource that should be added to the LMS. The most common content item type is `ltiResourceLink`, which represents an LTI® link to a resource in your application, usually in the form of an activity. The `type` and `url` fields are required for this content item type; it's also common to provide a `title` field that most LMSes will use as the activity title.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/deeplinking/form POST request
const body = {
contentItems: [{
type: 'ltiResourceLink',
url: 'https://your.ltiaas.com/lti/launch?resource=lesson-3',
title: 'Lesson 3 — Interactive Reading'
}]
}
const response = await requests.post(
'https://your.ltiaas.com/api/deeplinking/form',
body,
{ headers }
)
const form = response['form']
```
**Caution:**
For the LTI® launch to work from the newly created activity, the `url` field of the content item **must** point to your LTIAAS subdomain launch URL `https://your.ltiaas.com/lti/launch`, not directly to your application. You can add any query parameters you want to this URL, and they will be passed along to your application's registered launch URL during subsequent LTI® launches.
**Tip:**
The LTI® protocol allows you to pass multiple content items in a deep linking request, but it's common for LMSes to accept only one, since this flow is most often tied to the creation of a single activity.
For a description of all accepted fields, as well as possible response bodies and statuses, check the [Deep Linking endpoint API reference](/api/post-api-deeplinking-form).
}
>
The `/api/deeplinking/form` endpoint response contains a `form` field whose value is an HTML snippet — a hidden form pointed at the LMS's deep-linking return URL, together with a one-line script that submits it immediately. Your application's front-end appends this snippet to the document body, the script executes, and the browser POSTs the signed selection back to the LMS.
```javascript
// Back-end returns the form to the front-end
const form = response['form']
return form
// Front-end appends the self-submitting form to the document body
$('body').append(form)
```
The LMS verifies the JWT contained in the form against your tool's public key (which it learned about during registration), persists each content item as a new `resource_link_id` associated with the activity being created, and closes the deep-linking dialog. The teacher then sees the embedded resource card in the LMS with whatever `title` your application provided in Step 3.
**Tip:**
If you want full control over the submission HTML — for example, in a CSP-strict environment that disallows inline scripts — call the `/api/deeplinking` endpoint (without `/form`) instead. This endpoint returns a JSON object containing a `message` field (the signed JWT) and a `target` field (the LMS's deep-linking return URL), which you can use to build your own submission form. See [Creating your own submission form](/guides/api/deeplinking#creating-your-own-submission-form) in the Deep Linking guide.
}
>
The deep-linked content now lives in the LMS as a normal resource link associated with the activity that was created. When a student opens that activity and clicks through, the LMS issues a *resource link launch* — a different LTI® message type than the deep-linking launch from Step 2, but verified by LTIAAS along the same code path. Your application receives the launch at its registered target launch URL, with the `ltik` query parameter and the query parameters that were encoded into the content item's `url` field in Step 3.
```
https://your.application.com/your-launch-endpoint?ltik=...&resource=lesson-3
```
Your application's back-end reads the `resource` query parameter (or whatever encoding you chose), looks up the corresponding resource in your database, and renders the right view for the student. The user identity comes from `/api/idtoken` exactly as it does on any other launch — see the [Launch + idtoken SSO guide](/guides/flows/launch-and-sso) for the SSO pattern.
**Tip:**
If the activity is graded, this is also where your application would typically create the corresponding LMS line item, so that grades submitted later land on the right gradebook column. See the [Grade Passback guide](/guides/flows/grade-passback) for the line-item lifecycle.
---
## What's Next
- [Launch + idtoken SSO flow](/guides/flows/launch-and-sso) — log the student into your tool from the resource link launch.
- [Grade Passback flow](/guides/flows/grade-passback) — submit the student's score to the LMS gradebook after they complete the activity.
- [Names & Roles flow](/guides/flows/names-and-roles) — retrieve the full class roster so that you know which students have access to the deep-linked activity.
## Grade Passback Flow
Source: https://ltiaas.com/guides/flows/grade-passback
# Grade Passback
**Tip:**
Make sure you've read about [authenticating API requests](/guides/api/authentication) before proceeding to the guide below.
**Info:**
The Line Items and Scores endpoints accept both the [ltik based](/guides/api/authentication#ltik-based-authentication) and [service key based](/guides/api/authentication#service-key-based-authentication) API authentication methods.
The LTI® protocol allows you to manipulate grades in the LMS grade book through the *Assignment and Grades* service (AGS). A "grade line" — that is, a column in the LMS grade book — is called a **line item** in LTI® terminology, and a per-user score posted to a line item is called a **score**. To submit a grade to the LMS, your application needs the `id` of the relevant line item plus a score object describing the user, their result, and the progress of the activity.
LTIAAS gives you access to the *Assignment and Grades* service through the `/api/lineitems` and `/api/lineitems/:lineItemID/scores` endpoints. The typical pattern is: when a student finishes an activity in your tool, check whether the AGS service is available, locate or create the line item that represents your activity, and submit the score to that line item.
This guide walks through that synchronous pattern. For tools that need to submit grades asynchronously — from cron jobs, webhook handlers, or batch jobs that run long after the original launch — see the [Async / Service-Key Grading guide](/guides/flows/async-grading).
}
>
The grade-passback flow is initiated by your application, not by the LMS. Your application decides what constitutes "finishing" — submitting a quiz, completing every question, hitting a passing threshold, the teacher manually approving a draft, or a periodic job summarizing nightly progress. LTIAAS does not impose any particular definition of completion; it simply provides the API endpoints to push the resulting grade into the LMS gradebook.
```javascript
// Inside your "submit attempt" handler
const result = computeScore(attempt) // your application's scoring logic
await sendToLMS(result, ltik, idtoken)
```
**Tip:**
Tracking attempts, retake policies, partial credit, late penalties, and the rules that ultimately produce a final score live entirely inside your application. It's recommended to also design for idempotency from the start, so that re-running the grade-passback step for the same user and activity is safe — the LMS treats subsequent score submissions as updates to the previous one.
}
>
The *Assignment and Grades* service might not be available for every LTI® launch context. Before attempting to manipulate grade lines or submit scores, your application should call the ID Token endpoint and check the `services` section to confirm that the service is available in the current launch context.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const idtoken = await requests.get(
'https://your.ltiaas.com/api/idtoken',
{ headers }
)
const ags = idtoken['services']['assignmentAndGrades']
if (!ags['available']) {
// The AGS service is not available in this launch context.
// Gracefully degrade — surface the score in your tool but don't push it.
return
}
// If there's only one line item associated with this context, its ID
// will already be present in the ID Token.
let lineItemId = ags['lineItemId']
```
If `ags.lineItemId` is present, the LMS launched this activity bound to a specific gradebook column and your application can skip the lookup. If the field is missing — for instance, when multiple line items are associated with the context, or when no line item exists yet — your application needs to either look up an existing line item by some attribute it controls (Step 3) or create one (Step 4).
You can find more guidance on the `services` section in the [ID Token guide](/guides/api/idtoken#services-section) and in the [Checking service availability section](/guides/api/manipulating-grade-lines#checking-service-availability) of the Manipulating Grade Lines guide.
}
>
If your application doesn't already have a `lineItemId` for the activity being graded, the next step is to query the LMS context for existing line items. The recommended approach is to filter by the `resourceLinkId`, which is the LMS-side identifier for the activity the user launched from. The resource link ID can be retrieved from the `idtoken.launch.resourceLink.id` field of the ID Token, so it's always available on a launch from an activity — and because many LMSes automatically create a line item bound to the resource link when the activity is added, filtering this way is the most likely to find an existing grade line.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// The resource link ID identifies the launched activity in the LMS.
const resourceLinkId = idtoken['launch']['resourceLink']['id']
// Making /api/lineitems GET request, filtered by resourceLinkId
const response = await requests.get(
`https://your.ltiaas.com/api/lineitems?resourceLinkId=${resourceLinkId}`,
{ headers }
)
const lineItems = response['lineItems']
let lineItemId = lineItems.length > 0 ? lineItems[0]['id'] : null
```
You can also filter by a `resourceId` that your application controls — an internal identifier you stored on the line item when it was created — by passing `?resourceId=` instead. This is useful when a single resource in your tool can be linked from several different LMS activities, but it only matches line items that your application created with that `resourceId` set in the first place.
**Tip:**
It's common to cache the `lineItemId` against `(platformId, resourceLinkId)` in your application's database after the first lookup, so that future grade submissions skip the GET request entirely.
For details on every available query parameter and the structure of the response, see [Filtering grade lines by the associated activity](/guides/api/manipulating-grade-lines#filtering-grade-lines-by-the-associated-activity).
}
>
The first time your application grades a particular resource inside a particular course, no line item will exist for that combination — the lookup in Step 3 will return an empty array. Your application then creates the line item by making a POST request to the `/api/lineitems` endpoint. When creating a new line item, the `label` and `scoreMaximum` fields are required, and it's recommended to also populate `resourceLinkId` and `resourceId` so that the line item is bound both to the LMS activity and to your application's internal resource.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/lineitems POST request
const body = {
label: 'Lesson 3',
scoreMaximum: 100,
resourceLinkId: idtoken['launch']['resourceLink']['id'],
resourceId: 'lesson-3',
tag: 'weekly-reading'
}
const created = await requests.post(
'https://your.ltiaas.com/api/lineitems',
body,
{ headers }
)
const lineItemId = created['id']
```
Binding the line item correctly is important. Setting `resourceLinkId` tells the LMS that the new gradebook column belongs to the activity that was launched, so the LMS will display the score in the correct location. Setting `resourceId` lets your application find this line item again later without scanning every line item in the context.
**Caution:**
The PUT endpoint for updating a line item replaces all fields with the new body. Fields present in the existing line item that are missing from the new body will be deleted. If you want to perform a partial update, GET the current line item first, merge in your changes, and PUT the merged object.
For a description of all accepted fields, as well as possible response bodies and statuses, check the [Creating a grade line](/guides/api/manipulating-grade-lines#creating-a-grade-line) section of the Manipulating Grade Lines guide.
}
>
With the `lineItemId` in hand, your application's back-end submits the score by making a POST request to the `/api/lineitems/:lineItemID/scores` endpoint. The score object describes the user being graded, the progress of the activity, and the optional numeric grade. The `userId`, `activityProgress`, and `gradingProgress` fields are required; the `scoreGiven` and `scoreMaximum` fields are optional, but when one is present the other is required as well.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/lineitems/:lineItemID/scores POST request
const score = {
userId: idtoken['user']['id'],
activityProgress: 'Completed',
gradingProgress: 'FullyGraded',
scoreGiven: 95,
scoreMaximum: 100,
comment: 'Nice work on the comprehension questions.'
}
const urlSafeLineItemId = encodeURIComponent(lineItemId)
await requests.post(
`https://your.ltiaas.com/api/lineitems/${urlSafeLineItemId}/scores`,
score,
{ headers }
)
// A successful response has a 204 status.
```
There is no bulk-score endpoint — submitting grades for an entire class means looping over each user and POSTing a score per user. Each request is independent, so your application can parallelize the loop. It's recommended to throttle the parallelism to a few requests per second to avoid exceeding the LMS's rate limits.
**Caution:**
Some LMSes might restrict grade submissions based on the user's role. It's recommended to only submit grades for users who have a Learner role in the launch context. If you test grade submission while logged in as an Instructor or Administrator, the API may return success but the LMS gradebook may not display the resulting grade.
**Tip:**
For asynchronous grading scenarios — cron jobs, webhook handlers, or any background work that runs outside the original launch — capture the `serviceKey` from `idtoken.services.serviceKey` during the launch and persist it. The same scoring endpoint accepts the `SERVICE-AUTH-V1` authorization header. See the [Async / Service-Key Grading guide](/guides/flows/async-grading) for the full pattern.
For a description of every score field and its allowed values, see the [Submitting grades section](/guides/api/manipulating-grades#submitting-grades) of the Manipulating Grades guide.
}
>
The score lands in the LMS gradebook against the line item created in Step 4. There is no second confirmation step — the next time the teacher opens the gradebook (or the LMS auto-refreshes the page), the score is visible in the right column. The student sees the updated grade in their own gradebook view as well.
```
Lesson 3
─────────
John Doe 95 / 100
Aisha Khan 97 / 100
Maria Chen 82 / 100
Theo Park 88 / 100
```
**Tip:**
The *Assignment and Grades* service does not include webhooks confirming that the LMS persisted a score. It's recommended to treat grade submission as an eventually-consistent operation: retry on `5xx` responses, and periodically reconcile your application's view of "what we sent" against the LMS's actual results via the [Retrieving grades](/guides/api/manipulating-grades#retrieving-grades) endpoint.
It's also useful to expose a "last sent score" view to support staff inside your application. When a teacher reports that the LMS shows an unexpected grade, having a clear audit trail of what your tool actually submitted, and when, dramatically speeds up troubleshooting.
---
## What's Next
- [Launch + idtoken SSO flow](/guides/flows/launch-and-sso) — review how a launch identifies the user whose grade is being submitted.
- [Deep Linking flow](/guides/flows/deep-linking) — pre-create line items at deep-link time so that they exist before the student's first launch.
- [Async / Service-Key Grading flow](/guides/flows/async-grading) — submit grades from cron jobs and background workers using the `serviceKey`.
## Launch + idtoken-based SSO
Source: https://ltiaas.com/guides/flows/launch-and-sso
# Launch + idtoken-based SSO
**Tip:**
Make sure you've read about [authenticating API requests](/guides/api/authentication) before proceeding to the guide below.
**Info:**
The ID Token endpoint only accepts the [ltik based authentication method](/guides/api/authentication#ltik-based-authentication).
When a user clicks your LTI® tool inside an LMS, their browser will eventually arrive at your application. By the time they land on your page, you need them logged in to your own application's authentication system — LTIAAS does not run that authentication for you. What LTIAAS does is verify the LMS's signed claims about the user and hand them to you in a developer-friendly format called the ID Token. Your application then uses the trusted identity contained in the ID Token to either log the user into an existing account in your tool or provision a new one on the spot.
This guide walks through the full end-to-end flow, from the moment a user clicks the tool link inside the LMS to the moment they're logged into your application. It also shows concrete patterns for the most common authentication setups your application might already be using: a server-side session cookie, a short-lived JWT for a single-page application, and identity providers like Auth0, Cognito, Firebase Auth, and NextAuth.js.
}
>
A student or teacher opens an assignment, module item, or course page in the LMS and clicks the link that points at your tool. Behind the scenes, the LMS POSTs an OIDC login initiation request to your LTIAAS subdomain — not directly to your application.
```
LMS ──POST──▶ https://your.ltiaas.com/lti/login
```
The user doesn't see this happen. It takes place inside the LMS's iframe, and your application is not yet involved in any way. At this stage, the LMS is simply telling LTIAAS "a user from my system wants to launch your tool, here is who they are and which activity they're launching from."
}
>
LTIAAS handles the protocol-level work of the launch. It verifies the login initiation against the registered platform, issues an `id_token` request back to the LMS, receives a signed ID Token JWT from the LMS, verifies that JWT against the LMS's public key as discovered via the platform's JWKS, and stores the verified launch context on LTIAAS's side. Finally, LTIAAS issues an `ltik` token representing this specific launch and redirects the user's browser to your application's registered target launch URL, with the `ltik` appended as a query parameter.
Your application writes zero code for any of this work — handling the LTI® protocol handshake is the entire reason LTIAAS exists.
**Info:**
You can read more about the structure of the `ltik` token and how to use it to authenticate API requests in the [Authenticating API requests guide](/guides/api/authentication#ltik-based-authentication).
}
>
After LTIAAS finishes the handshake, the user's browser arrives at your application's registered target launch URL, with the `ltik` appended as a query parameter:
```
GET https://your.application.com/launch?ltik=eyJhbGciOiJIUzI1NiIs...
```
This is the first request your application sees in the entire launch flow. Your application's back-end should capture the `ltik` value immediately on this request and use it to call the LTIAAS API. It's recommended to read the `ltik` server-side from this initial request rather than relying on the front-end to forward it on subsequent navigation.
```javascript
// Express example
app.get('/launch', async (req, res) => {
const ltik = req.query.ltik
if (!ltik) return res.status(400).send('Missing ltik')
// ... continue to step 4
})
```
**Caution:**
The `ltik` token represents a single LTI® launch and grants API access in the context of that launch. Treat it like a session token: never log it, never include it in URLs on subsequent pages, and never expose it to the front-end. Each `ltik` expires 24 hours after the launch in which it was issued.
}
>
With the `ltik` in hand, your application's back-end calls the `/api/idtoken` endpoint to retrieve the launch's ID Token. LTIAAS returns a developer-friendly object containing the verified identity of the user, information about the LMS that originated the launch, the launch context, and the LTI® services available within this context. Your application can fully trust the contents of this response because LTIAAS has already verified the LMS's signature on the underlying JWT.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const idtoken = await requests.get(
'https://your.ltiaas.com/api/idtoken',
{ headers }
)
// Trusted identity claims:
const lmsUserId = idtoken['user']['id']
const lmsEmail = idtoken['user']['email']
const lmsName = idtoken['user']['name']
const platformId = idtoken['platform']['id']
const roles = idtoken['user']['roles']
```
**Caution:**
Fields containing personal information like `name` and `email` might not be present in the ID Token. The LMS can choose to omit this information based on its privacy rules. The only fields guaranteed to be present in the `user` section are `id` and `roles`. For this reason, it's not recommended to use email alone as the primary key for your user records.
For a description of the full structure of the ID Token, as well as guidance on how to identify a user globally across multiple LMSes, check the [Retrieving the ID Token guide](/guides/api/idtoken).
}
>
**Info:**
This step is only necessary if your tool maintains its own user accounts or sessions. A tool that simply displays content to whoever launches it — without per-user persistence, saved progress, or a login concept of its own — can skip straight to rendering the launch using the identity already verified in Step 4. Reach for this step when you need the launched user to map onto a durable account in your application.
This step is implemented entirely in your application's back-end. The specific code depends on which authentication system your application uses, but the principle is always the same: identify the user by combining `platform.id` and `user.id` into a globally unique external identifier, look up that identifier in your database, either update the matching user record or create a new one, and then issue whatever credential your application uses to represent a logged-in session.
The most common patterns are illustrated below.
**Server-side session cookie (Express, Rails, Django, Phoenix, and similar):**
```javascript
const externalId = `${idtoken['platform']['id']}:${idtoken['user']['id']}`
const user = await db.users.upsert({
where: { externalId },
update: { lastSeen: new Date() },
create: {
externalId,
email: idtoken['user']['email'],
name: idtoken['user']['name'],
source: 'lti'
}
})
req.session.userId = user.id // session cookie set on the response
res.redirect('/app')
```
**Single-page application using a short-lived JWT:**
```javascript
const token = jwt.sign(
{ sub: user.id, roles: mapLtiRoles(idtoken['user']['roles']) },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
)
// Render an HTML page that stores `token` and bootstraps the SPA.
```
**Identity providers — Auth0, Cognito, Firebase Auth:**
```javascript
// Firebase Admin SDK example
const customToken = await admin.auth().createCustomToken(externalId, {
ltiPlatform: idtoken['platform']['id'],
ltiRoles: idtoken['user']['roles']
})
// Return an HTML page that calls signInWithCustomToken(customToken).
```
**NextAuth.js with a credentials provider:**
Pass the verified ID Token claims into the credentials provider's `authorize()` function via a server-side fetch, and let NextAuth.js issue the normal NextAuth session cookie on the response.
**Tip:**
It's common for an application to also store the ID Token's `ltiPlatform` and `ltiRoles` claims alongside the user record so that role-based permissions inside your tool can be evaluated without needing to call the ID Token endpoint again on every request.
You can find more guidance on identifying users across different LMSes in the [Performing SSO section of the ID Token guide](/guides/api/idtoken#performing-sso).
}
>
The browser is now inside your application, and your tool has the verified identity of the user and the context of their launch — who they are, which LMS and course they came from, their role, and which LTI® services are available. If you completed the optional Step 5, the user also has a session in your tool's own authentication system; if you didn't, you can still render the right view for this launch using the trusted claims from the ID Token alone. Either way, the user was never prompted for a password or a consent dialog.
There are a few additional things worth doing inside the launched session, depending on which LTI® services your tool consumes.
**Tip:**
If your tool will call other LTIAAS API endpoints within the same launch — for example, to fetch the class roster or submit a grade — persist the `ltik` in the user's server-side session so that subsequent requests can reuse it. Remember that the `ltik` expires 24 hours after the launch in which it was issued.
**Tip:**
If your tool needs to access LTI® services asynchronously, outside the context of a live launch (for example, from a cron job or a webhook handler), retrieve the `serviceKey` from `idtoken.services.serviceKey` and persist it in your database keyed by `(platformId, contextId, resourceLinkId)`. The `serviceKey` does not expire, and can be used with the [service key based authentication method](/guides/api/authentication#service-key-based-authentication) to call AGS and NRPS endpoints from outside a launch. See the [Async / Service-Key Grading flow](/guides/flows/async-grading) for the full pattern.
---
## What's Next
- [Deep Linking flow](/guides/flows/deep-linking) — allow teachers to pick specific content from your tool and embed it into an LMS assignment.
- [Grade Passback flow](/guides/flows/grade-passback) — send a user's score back to the LMS gradebook after they complete an activity.
- [Names & Roles flow](/guides/flows/names-and-roles) — retrieve the full class roster for the LMS context the user just launched from.
## Dynamic LMS Registration
Source: https://ltiaas.com/guides/flows/lms-registration
# Dynamic LMS Registration
**Tip:**
Make sure you've read about [authenticating API requests](/guides/api/authentication) before proceeding to the guide below.
**Info:**
The Registration API endpoints only accept the [bearer api key based authentication method](/guides/api/authentication#bearer-api-key-based-authentication).
Before an LMS can launch your LTI® tool, the two systems must complete a one-time **platform registration**. During registration, the LMS and your tool exchange OIDC client metadata, public keys, redirect URIs, and the list of LTI® services and message types your tool offers. From that point on, both systems can trust each other to sign and verify the JWTs that drive every subsequent launch.
LTIAAS gives you access to Dynamic Registration through the `https://your.ltiaas.com/lti/register` endpoint. The LMS administrator only ever needs to know that one URL; LTIAAS performs the registration handshake on your behalf, and you can optionally interpose your own page partway through to gate the registration on a sign-in, a payment, or a terms-of-service acceptance.
This guide walks through the full Dynamic Registration flow, including the optional pre-approval branch. If your LMS does not support Dynamic Registration, see the [Manual LMS Registration guide](/guides/flows/manual-registration) instead — the end state is identical: a persistent `platformId` in LTIAAS pointing at one LMS.
}
>
The LMS administrator opens whichever page their LMS provides for adding a new LTI® 1.3 tool (Canvas calls it "LTI Registration", Moodle calls it "Register a tool", Brightspace exposes it under External Learning Tools), and enters your tool's single dynamic registration URL:
```
https://your.ltiaas.com/lti/register
```
This is the only piece of information about your tool that the LMS administrator needs. Each LMS that supports Dynamic Registration accepts this same URL — there is no separate URL per LMS or per registration. Once the administrator submits the form, the LMS begins the registration handshake by making a request to that URL.
**Tip:**
It's common to publish a short "How to install our tool" page on your marketing site or documentation that lists this URL together with a brief screenshot of where to paste it in each LMS family you support. The per-LMS guides under [LMS Setup](/guides/lms/introduction) are good starting points for the LMS-specific instructions.
}
>
After the LMS administrator initiates the registration, LTIAAS automatically exchanges OIDC client metadata with the LMS, including registration endpoint discovery, the JWKS URL pointing to your tool's public keys, the redirect URIs the LMS should use for launches, the list of requested LTI® services, and the message types your tool supports (resource link launch, deep linking, and so on). None of this traffic reaches your tool's code; LTIAAS owns the protocol-level work.
If `Dynamic Registration Auto-Activation` is enabled in the LTIAAS portal under the *API Settings* page, the registration completes immediately and the LMS administrator is shown a "registered" confirmation. The new platform is fully active and ready to launch your tool, and you don't need to do anything else.
If auto-activation is disabled, the registration is created in a pending state and won't be allowed to launch until you flip it on manually in the LTIAAS portal. This is the simplest form of approval gate, and may be all you need if your only requirement is that *somebody on your team* reviews each new registration before it goes live.
**Tip:**
For most tools, auto-activation paired with a free or trial tier is enough. Continue to the next step only if you need to authenticate the LMS administrator, collect payment, or otherwise interpose your own UI before each registration becomes active.
}
>
If you have enabled **Enable Pre-Approval** in the LTIAAS portal and configured a **Pre-Approval URL**, LTIAAS pauses the registration handshake partway through and redirects the user's browser to your URL with a query parameter identifying the in-progress registration:
```
https://your-tool.com/lti/approve?registrationId=abc123
```
Your landing page is rendered inside the same iframe in which the LMS started the registration. It's important to remain inside that iframe — LTIAAS uses it to communicate the final result back to the LMS at the end of the flow, and breaking out of it with a `target="_top"` redirect or a new tab will cause the registration to be abandoned.
**Caution:**
The `registrationId` parameter is not persistent. It can only be used to read information about the in-progress registration and to complete it. Once the registration is either completed or abandoned, the `registrationId` will no longer work, and any attempt to use it will return an error.
Your application is responsible for whatever pre-approval workflow makes sense for your business: sign in with an existing account, sign up for a new one, choose a subscription plan, accept your terms of service, or any combination of these. LTIAAS does not impose a particular workflow — it simply hands control of the iframe to you and waits for your back-end to call the next API.
}
>
Once the user has completed whatever pre-approval steps you required of them, your application's back-end can call the LTIAAS API to retrieve information about the in-progress registration. This is useful when you want to know which LTI® services and message types the originating LMS supports, so that you can tailor the placements and capabilities you request on a per-LMS basis.
```javascript
// Building Bearer API authentication header
const authorizationHeader = `Bearer ${API_KEY}`
const headers = { Authorization: authorizationHeader }
// Making /api/registrations GET request
const response = await requests.get(
`https://your.ltiaas.com/api/registrations/${registrationId}`,
{ headers }
)
const familyCode = response['familyCode']
const supportedScopes = response['supportedScopes']
const supportedMessages = response['supportedMessages']
```
A successful response will contain the LMS's family code (`moodle`, `canvas`, `BlackboardLearn`, and so on), the LMS version, the list of supported scopes, and the list of supported message types and their allowed placements.
**Tip:**
You can use `familyCode` to refuse registration for unsupported LMS families, or to silently strip placements you know the LMS won't accept. You can find an example of every supported field in the [Get Registration endpoint API reference](/api/get-api-registrations).
}
>
When the pre-approval workflow is complete, your application's back-end finalizes the registration by making a POST request to the `/api/registrations/:registrationId/complete` endpoint. The request body is optional; any fields you omit will fall back to the defaults you have set in the LTIAAS portal. The fields you can override per registration include the displayed platform name, whether the registration should activate immediately, and which message types and placements to enable.
```javascript
// Building Bearer API authentication header
const authorizationHeader = `Bearer ${API_KEY}`
const headers = { Authorization: authorizationHeader }
// Making /api/registrations/:registrationId/complete POST request
const body = {
platformName: 'Springfield Elementary Moodle',
autoActivate: true,
messages: [
{ type: 'LtiResourceLinkRequest' },
{ type: 'LtiDeepLinkingRequest', placements: ['ContentArea'] }
]
}
const response = await requests.post(
`https://your.ltiaas.com/api/registrations/${registrationId}/complete`,
body,
{ headers }
)
const platformId = response['platformId']
const htmlToInject = response['html']
```
The response contains two fields. The first is a `platformId`, which is the persistent identifier for this new platform registration; you'll want to store this against your internal tenant or organization record. The second is an `html` snippet that your application's front-end must append to the document body of the active registration iframe.
```html
```
That `postMessage` call is the protocol-defined signal that tells the LMS the registration dialog can be closed. It must be injected into the iframe's document by your application — LTIAAS doesn't have access to that document, so it cannot inject the snippet itself.
For a description of all accepted fields, as well as possible response bodies and statuses, check the [Complete Registration endpoint API reference](/api/post-api-registrations-complete).
}
>
Once the registration completes, the LMS marks your tool as registered and visible to instructors. Every subsequent launch from this LMS will arrive at your tool's launch URL with a `ltik` token, and the resulting [ID Token](/guides/api/idtoken) will include this registration's `platformId` in the `idtoken.platform.id` field. You can use this ID to recognize which LMS a given launch originated from and to look up the corresponding tenant in your own database.
```javascript
// res.platformId is persistent — store it against your internal tenant record.
await db.organizations.update({
where: { id: currentOrgId },
data: {
lmsPlatformId: res.platformId,
lmsFamily: reg.familyCode
}
})
```
**Tip:**
It also makes sense to track subscription state, seat counts, and feature flags keyed by `platformId`, so that you can later deactivate or reactivate registrations as customers churn or upgrade.
To revoke access for an LMS later — for example, when a subscription lapses — it's recommended to **deactivate** the platform via the `/admin/platforms/:id/deactivate` endpoint rather than deleting the registration outright. Deactivation returns a clear error to the LMS on the next launch attempt and can be reversed with a single call, while deletion is permanent and forces the LMS administrator to re-register your tool from scratch. See the [Platforms API reference](/api/ltiaas) for the full set of admin endpoints.
---
## What's Next
Once a registration exists, every launch from that LMS will arrive at your registered launch URL. To continue building out your integration, see the following guides:
- [Manual LMS Registration flow](/guides/flows/manual-registration) — the alternative path when Dynamic Registration isn't an option, or when you want to register programmatically via `POST /admin/platforms`.
- [Launch + idtoken SSO flow](/guides/flows/launch-and-sso) — turn a launch into a logged-in session in your tool's auth system.
- [Deep Linking flow](/guides/flows/deep-linking) — allow teachers to pick specific content from your tool and embed it into an LMS assignment.
- [Grade Passback flow](/guides/flows/grade-passback) — send scores back to the LMS gradebook from inside your tool.
## Manual LMS Registration
Source: https://ltiaas.com/guides/flows/manual-registration
# Manual LMS Registration
**Tip:**
Make sure you've read about [authenticating API requests](/guides/api/authentication) before proceeding to the guide below.
**Info:**
The Platforms API endpoints only accept the [bearer api key based authentication method](/guides/api/authentication#bearer-api-key-based-authentication).
Not every LMS supports [Dynamic Registration](/guides/flows/lms-registration). For LMSes that don't — including older versions of Canvas, Blackboard Learn, Moodle below 3.10, Schoology, and most custom or in-house platforms — the registration must be performed manually. The LMS and your tool exchange the same set of OIDC metadata as in a Dynamic Registration, but instead of LTIAAS doing it over the wire, the LMS administrator and your team copy values between the two systems by hand or via API.
Manual registration is also the right choice when you want full programmatic control over the process, regardless of whether the LMS supports Dynamic Registration. Common reasons to choose this path include a self-service signup workflow where your customers paste their LMS details into your tool and your back-end calls LTIAAS on their behalf, a bulk import from an older system, or a "rebuild from config" disaster-recovery script.
LTIAAS gives you access to manual platform management through the `/admin/platforms` API endpoints, which are also surfaced through the **Registrations** page in the LTIAAS Portal. Both routes produce the same result: a persistent `platformId` that uniquely identifies the LMS within your LTIAAS account.
The shape of the manual registration is always the same: the LMS and LTIAAS each generate half of the required metadata, and you stitch the two halves together. LTIAAS exposes its half automatically under your subdomain. The LMS gives you its half through its admin UI (or, in some cases, through its own API). You then feed the LMS's half into LTIAAS via the Portal or the `/admin/platforms` API.
}
>
LTIAAS hosts a fixed set of LTI® endpoints under your account's subdomain. These don't change per registration, and you don't need to configure them anywhere — they're derived from your subdomain.
```
https://your.ltiaas.com/lti/login ← OIDC login initiation URL
https://your.ltiaas.com/lti/launch ← target launch / redirect URI
https://your.ltiaas.com/lti/deeplink ← deep-linking launch URL (if offered)
https://your.ltiaas.com/lti/keys ← your tool's public JWKS
```
You can verify that your LTIAAS account is live and accessible by making a request to the `/lti/keys` endpoint. A new account with no registrations will return an empty `keys` array.
```bash
curl https://your.ltiaas.com/lti/keys
# { "keys": [] }
```
**Tip:**
It's common to display these URLs in your application's onboarding UI so that customers self-serving a registration can simply copy them into their LMS without contacting your support team.
}
>
The LMS administrator opens whichever "register a new LTI® 1.3 tool" form their LMS provides and pastes in the URLs from the previous step. While the form's exact layout varies between LMSes, every LMS will ask for roughly the same four URLs under slightly different names.
| Concept | Canvas calls it | Moodle calls it | Blackboard calls it |
|---|---|---|---|
| Login initiation URL | OIDC Initiation URL | Initiate login URL | Initiate login URL |
| Launch URL | Target Link URI | Tool URL | Tool launch URL |
| JWKS URL | Public JWK URL | Public keyset URL | Tool's JWKS URL |
| Deep linking URL | Placements → Content Selection URL | Content selection URL | Deep linking URL |
Once the administrator saves the form, the LMS provisions the tool and assigns it a `Client ID`.
**Tip:**
You can find the LMS-specific instructions for several major LMSes in the [LMS Setup](/guides/lms/introduction) section of the documentation. It's common to publish a short companion page for each LMS you support, explaining where in the LMS administrator UI to find this form.
}
>
After the registration is saved, the LMS exposes a small set of values that LTIAAS needs to talk to it during launches and service calls. The LMS administrator can either copy these values out of the admin UI by hand, or read them from the LMS's own API if you're automating bulk onboarding.
| Field | What it is | Example value |
|---|---|---|
| Client ID | The identifier the LMS issued for your tool inside this LMS. | `10000000000123` |
| Issuer / Platform URL | The LMS's base URL, also referred to as the `iss` claim in the LTI® protocol. | `https://lms.school.edu` |
| OIDC authentication endpoint | The endpoint LTIAAS calls during the OIDC handshake. | `…/api/lti/authorize_redirect` |
| Access token endpoint | The OAuth2 token endpoint LTIAAS uses for AGS and NRPS calls. | `…/login/oauth2/token` |
| Public JWKS URL | The LMS's public keys, which LTIAAS uses to verify the signature on launch JWTs. | `…/api/lti/security/jwks` |
**Info:**
The `Client ID` is the only value here that is specific to your tool's registration. The other four are properties of the LMS itself and will be identical for any tool registered in the same LMS instance.
}
>
You now feed the values from the previous step into LTIAAS. There are two ways to do this, and they produce the same result.
The simpler option is to use the LTIAAS Portal. Navigate to [portal.ltiaas.com](https://portal.ltiaas.com), open your account, go to the **Registrations** page, click **Add registration**, paste the five values into the form, and save. After deploying, the registration is live.
The programmatic option is to call the `/admin/platforms` endpoint directly. This is the right choice when you want to automate the registration step inside your own onboarding flow or bulk import script.
```javascript
// Building Bearer API authentication header
const authorizationHeader = `Bearer ${API_KEY}`
const headers = { Authorization: authorizationHeader }
// Making /admin/platforms POST request
const body = {
name: 'School Canvas',
url: 'https://lms.school.edu',
clientId: '10000000000123',
authenticationEndpoint: 'https://lms.school.edu/api/lti/authorize_redirect',
accesstokenEndpoint: 'https://lms.school.edu/login/oauth2/token',
authConfig: {
method: 'JWK_SET',
key: 'https://lms.school.edu/api/lti/security/jwks'
}
}
const response = await requests.post(
'https://your.ltiaas.com/admin/platforms',
body,
{ headers }
)
const platformId = response['id']
```
A successful response will contain the newly created platform object, including its `id` field — the persistent `platformId` you'll see in every future launch from this LMS.
The `authConfig.method` field accepts one of three values, each describing a different way the LMS exposes its public key material:
- `JWK_SET` — the most common case. `key` is the URL of the LMS's JWKS document.
- `JWK_KEY` — `key` contains a single JWK JSON object inline. Rare.
- `RSA_KEY` — `key` contains a raw RSA public key in PEM format. Used by some legacy LMSes.
**Tip:**
The `POST /admin/platforms` endpoint is particularly useful for self-service signup workflows. Your application's front-end can collect the five LMS-side values from the customer, your back-end can submit them through this endpoint, and the customer is registered without anyone on your team ever opening the LTIAAS Portal. Store the returned `id` against your tenant record so that you can recognize future launches from this LMS.
For a description of all accepted fields, as well as possible response bodies and statuses, check the [Register Platform endpoint API reference](/api/post-admin-platforms).
}
>
After registering the platform, it's worth verifying that the registration is in place and active before asking the LMS administrator to attempt a launch. You can list all registered platforms by making a GET request to the `/admin/platforms` endpoint, or retrieve a single platform by its `id`.
```javascript
// Building Bearer API authentication header
const authorizationHeader = `Bearer ${API_KEY}`
const headers = { Authorization: authorizationHeader }
// Making /admin/platforms GET request
const platforms = await requests.get(
'https://your.ltiaas.com/admin/platforms',
{ headers }
)
```
Once the registration is confirmed, have the LMS administrator perform a test launch from the LMS — typically by adding your tool to a course as an external resource and clicking through. If the registration is correct, the launch will arrive at your tool's launch URL with a `ltik` query parameter, exactly as described in the [Launch + idtoken SSO guide](/guides/flows/launch-and-sso).
If the test launch fails, the most common causes are:
- **Mismatched URLs** — a trailing slash, a wrong protocol (`http://` vs `https://`), or a typo in the OIDC initiation URL given to the LMS.
- **Wrong `authConfig.method`** — for example, `JWK_SET` configured when the LMS only exposes a single inline JWK.
- **Inactive registration** — the platform exists in LTIAAS but its `active` field is `false`. Re-check by GETting the platform.
- **Browser cookie restrictions** — LMS launches happen inside iframes, and some browser/cookie configurations interfere with the flow. See [Cookies and LTI](/guides/resources/cookies-and-lti) for the common workarounds.
You can find further help in the [troubleshooting guide](/guides/troubleshooting/introduction).
}
>
The `/admin/platforms` API lets you script the entire registration lifecycle, which is useful both for ongoing maintenance and for tying registration state to your billing or subscription system.
```javascript
// Update an existing registration — e.g. the LMS has rotated its JWKS URL.
// Note: the PUT body replaces the existing registration; include all required fields.
await requests.put(
`https://your.ltiaas.com/admin/platforms/${platformId}`,
body,
{ headers }
)
// Deactivate a registration — keeps the config in place but refuses launches.
await requests.post(
`https://your.ltiaas.com/admin/platforms/${platformId}/deactivate`,
{},
{ headers }
)
// Reactivate a deactivated registration.
await requests.post(
`https://your.ltiaas.com/admin/platforms/${platformId}/activate`,
{},
{ headers }
)
// Delete a registration permanently. The LMS will need to re-register to use your tool.
await requests.delete(
`https://your.ltiaas.com/admin/platforms/${platformId}`,
{ headers }
)
```
**Tip:**
It's recommended to prefer **deactivation** over deletion when you want to temporarily disable a registration. A deactivated registration returns a clear error to the LMS on the next launch attempt and can be reactivated with a single API call, while a deleted registration cannot be restored — the LMS administrator must complete the entire manual registration process again.
**Info:**
It also makes sense to wire your billing or subscription state to platform activation. When a customer cancels, deactivate the platform; when they reactivate the subscription, re-activate the platform. This guarantees that your tool's availability tracks the customer's billing state without requiring an LMS administrator to take action.
For a description of all admin endpoints and their accepted fields, check the [Platforms API reference](/api/ltiaas).
---
## What's Next
- [Dynamic LMS Registration flow](/guides/flows/lms-registration) — the automated alternative for LMSes that support it. The resulting `platformId` is identical in shape to the one you get from the manual flow.
- [Launch + idtoken SSO flow](/guides/flows/launch-and-sso) — what happens on every launch after a successful registration.
- [Per-LMS setup guides](/guides/lms/introduction) — concrete LMS-specific instructions for the form fields in Step 2.
## Names & Roles (Roster Sync)
Source: https://ltiaas.com/guides/flows/names-and-roles
# Names & Roles (Roster Sync)
**Tip:**
Make sure you've read about [authenticating API requests](/guides/api/authentication) before proceeding to the guide below.
**Info:**
The Memberships endpoint accepts both the [ltik based](/guides/api/authentication#ltik-based-authentication) and [service key based](/guides/api/authentication#service-key-based-authentication) API authentication methods.
The LTI® protocol allows you to retrieve roster information for an LMS context through the *Names and Roles Provisioning* service (NRPS). Users that are part of an LMS context are called *members*, and the *Names and Roles Provisioning* service exposes each member's identifier, optional name and email, list of LTI® roles within the context, and active/inactive status. LTIAAS gives you access to this service through the `/api/memberships` API endpoint.
This flow shows the end-to-end pattern for keeping a roster in sync between an LMS and your tool: deciding when to trigger the sync, checking that the service is available in the current launch context, paginating over the membership list, mapping LTI® roles to your tool's own role model, and upserting each member into your application's database.
}
>
The *Names and Roles Provisioning* service is pull-based — the LMS will not push notifications to your tool when students enroll or drop out of a course. Your application decides when to refresh the roster, and the right trigger depends on how fresh the roster needs to be and how big your courses are. Common triggers include a "Sync class roster" button on the teacher's dashboard, a sync on every launch, a nightly cron job that uses the stored `serviceKey`, or a just-in-time sync when a previously unknown student first launches into your tool.
```javascript
// Inside your "sync roster" handler
const ltik = req.session.ltik // captured during the most recent launch
await syncRoster(ltik)
```
**Tip:**
Stale rosters are a common source of support tickets. It's recommended to surface the "last synced" timestamp prominently in the teacher's view and make the refresh action easy to discover, so that a teacher noticing a missing student can fix it themselves rather than escalating to support.
}
>
The *Names and Roles Provisioning* service is not always available — its availability depends on the LMS family, the scopes that were approved during registration, and the specific context of the launch. Before attempting to retrieve memberships, your application should call the ID Token endpoint and check the `services` section.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const idtoken = await requests.get(
'https://your.ltiaas.com/api/idtoken',
{ headers }
)
const isServiceAvailable = idtoken['services']['namesAndRoles']['available']
if (!isServiceAvailable) {
// The NRPS service is not available in this context.
// Gracefully degrade — show only the user who just launched.
return
}
```
There are three common reasons for `available` to be `false`. The NRPS scope may not have been approved when the tool was registered, in which case asking the LMS administrator to re-approve the registration with the scope enabled will resolve it. The launch may have come from a context that does not have enrollments, such as a sandbox course or a global admin preview. Finally, some LMS families do not implement NRPS at all — this is rare among modern LMSes, but still happens with older or custom platforms.
For more details on how to interpret the `services` section of the ID Token, see [Checking service availability](/guides/api/roster#checking-service-availability) in the Retrieving Course Roster guide.
}
>
Once the service is confirmed to be available, your application calls the `/api/memberships` endpoint to retrieve the membership list for the LMS context that originated the launch. The response is an object containing a `members` array, plus a `context` object describing the LMS context (identical in shape to the `context` field in the ID Token).
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/memberships GET request
const response = await requests.get(
'https://your.ltiaas.com/api/memberships',
{ headers }
)
const members = response['members']
const context = response['context']
const next = response['next'] // present only if the LMS paginated the response
```
Each entry in `members` contains a `userId` and a list of `roles`, plus an optional `email`, `name`, and `status` (`Active` or `Inactive`).
**Caution:**
Fields containing personal information like `name` and `email` might not be present in the memberships information. The LMS can choose to omit this information based on its privacy rules. The only fields guaranteed to be present in each membership are `userId` and `roles`. For this reason, it's not recommended to key your application's user records on email alone — instead, combine `idtoken.platform.id` with each member's `userId` to form a globally unique identifier, the same way you do during the [SSO flow](/guides/flows/launch-and-sso).
You can also filter the membership list to a specific role by passing `?role=`, or to users with access to a specific activity by passing `?resourceLinkId=`. See the [Filtering results by user role](/guides/api/roster#filtering-results-by-user-role) and [Filtering results by activity access](/guides/api/roster#filtering-results-by-activity-access) sections of the Retrieving Course Roster guide for details.
}
>
Some LMSes return the entire membership list in a single response, while others paginate large rosters across multiple pages. The `next` field will only be present if there are more memberships to be retrieved from the context. Your application should follow the cursor until it disappears, accumulating members along the way.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
const allMembers = []
let url = 'https://your.ltiaas.com/api/memberships'
while (url) {
const page = await requests.get(url, { headers })
allMembers.push(...page['members'])
url = page['next']
}
```
LTIAAS forwards the LMS's pagination tokens verbatim, so this loop works uniformly regardless of which LMS issued the cursor.
**Tip:**
For very large courses with thousands of members, it's recommended to perform the sync asynchronously and surface progress to the teacher rather than blocking the UI on a spinner. The [Async / Service-Key Grading guide](/guides/flows/async-grading) covers the pattern for running this kind of work outside a live launch.
You can read more about the pagination model in the [Handling pagination guide](/guides/api/pagination).
}
>
LTI® roles are not short strings like `"teacher"` or `"student"` — they are full URIs from the IMS role vocabulary, such as `http://purl.imsglobal.org/vocab/lis/v2/membership#Learner` or `http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor`. A single user can hold multiple roles in the same context, for example a teaching assistant who is also a Learner in another course.
Your application is responsible for translating these URIs into the role model used by its own authorization layer. A common pattern is to define a mapping table and pick the most privileged role that matches.
```javascript
const LTI_ROLE_MAP = [
{ uri: 'http://purl.imsglobal.org/vocab/lis/v2/membership#Administrator', role: 'admin', weight: 3 },
{ uri: 'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor', role: 'teacher', weight: 2 },
{ uri: 'http://purl.imsglobal.org/vocab/lis/v2/membership#TeachingAssistant', role: 'teacher', weight: 2 },
{ uri: 'http://purl.imsglobal.org/vocab/lis/v2/membership#Mentor', role: 'teacher', weight: 2 },
{ uri: 'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner', role: 'student', weight: 1 }
]
function pickRole(ltiRoles) {
const matches = LTI_ROLE_MAP.filter((m) => ltiRoles.includes(m.uri))
if (matches.length === 0) return 'student' // safe default
return matches.sort((a, b) => b.weight - a.weight)[0].role
}
```
**Tip:**
The mapping is entirely your application's choice — LTIAAS does not impose one because every tool's permission model differs. It's worth deciding up front how your application should react when a user's role changes between syncs. Demoting a user usually takes effect immediately, but for teachers who lose access mid-grading you may want to apply a short grace period rather than revoking access instantly.
A list of all possible roles can be found in the [IMS Role Vocabulary](https://www.imsglobal.org/spec/lti/v1p3/#role-vocabularies).
}
>
The final step combines everything: for each member returned by the LMS, derive the globally unique identifier, map the LTI® roles to your application's role model, and upsert the resulting record into your enrollment table. Any enrollment that exists in your database but is missing from the LMS response is treated as a dropped student and marked inactive.
```javascript
const platformId = idtoken['platform']['id']
const contextId = idtoken['launch']['context']['id']
const classId = await classFromContext(platformId, contextId)
await db.transaction(async (tx) => {
for (const member of allMembers) {
const externalId = `${platformId}:${member['userId']}`
await tx.classEnrollments.upsert({
where: { classId, externalId },
update: {
role: pickRole(member['roles']),
status: member['status']
},
create: {
classId,
externalId,
email: member['email'],
name: member['name'],
role: pickRole(member['roles']),
status: member['status']
}
})
}
// Mark enrollments not present in the new roster as inactive.
await tx.classEnrollments.markInactiveOutsideOf(classId, allMembers)
})
```
**Tip:**
The schema for classes, enrollments, and users — together with any indexes, soft-delete logic, and "link existing account" UX — lives entirely in your application. It's recommended to also plan for email collisions, since a user may already exist in your tool from a non-LTI signup with the same email address that the LMS reports.
Your application can now render the synced roster anywhere it's useful — a class roster page for the teacher, a per-student progress dashboard, or simply as the source of truth for which users belong to which class when displaying tool-specific data.
---
## What's Next
- [Launch + idtoken SSO flow](/guides/flows/launch-and-sso) — the same identity model (`platform.id` + `user.id`) you use here is established at launch time.
- [Grade Passback flow](/guides/flows/grade-passback) — now that you know which students belong to the class, submit their scores to the gradebook.
- [Async / Service-Key Grading flow](/guides/flows/async-grading) — use the stored `serviceKey` to refresh rosters from a cron job, outside of a live launch.
## OAuth2 with the LMS API
Source: https://ltiaas.com/guides/flows/oauth2
# OAuth2 with the LMS API
**Tip:**
Make sure you've read about [authenticating API requests](/guides/api/authentication) before proceeding to the guide below.
**Info:**
The OAuth2 API endpoints only accept the [bearer api key based authentication method](/guides/api/authentication#bearer-api-key-based-authentication).
The LTI® protocol covers launches, deep linking, grades, and rosters, but not every operation a tool might need. Some LMSes — most notably Canvas and Brightspace (D2L) — expose a native REST API that goes well beyond the LTI® surface, with endpoints for listing course files, manipulating enrollments, reading non-graded content, posting announcements, and many other operations. These APIs are protected by standard OAuth2 and act on behalf of an individual LMS user, with the user's explicit consent. If your tool needs functionality beyond what the LTI® services provide, complementing LTI® with OAuth2 is the way to go.
LTIAAS gives you access to the OAuth2 handshake through the `/oauth/start/:platformId` and `/api/oauth/access-token/:platformId` endpoints. LTIAAS handles redirecting the user to the LMS, accepting the LMS's callback, and exchanging the LMS's authorization code for an access token, so that your application never has to mint its own OAuth client, manage state cookies across the redirect chain, or store the LMS's signing keys. The resulting access token is a Bearer token that your application uses directly against the LMS's REST API.
**Warning:**
OAuth2 is a paid add-on. Before you start wiring it up, make sure your LTIAAS plan includes OAuth2 support, or upgrade your existing plan.
}
>
OAuth2 needs to be registered with the LMS just like an LTI® tool does. The LMS administrator opens whichever page their LMS provides for creating an API key — Canvas calls it *Admin → Developer Keys → API Key*; Brightspace exposes it under *Manage Extensibility → OAuth 2.0* — and configures a new key for your tool. The most important field is the redirect URI, which must point at your LTIAAS subdomain:
```
https://your.ltiaas.com/oauth/complete
```
The LMS administrator also chooses the scopes your tool will be allowed to request. Some LMS administrators enforce a strict allowlist of scopes, so it's important to communicate clearly which endpoints your tool plans to call, so they can enable the matching scopes. Once the key is saved, the LMS surfaces a **Client ID** and **Client Secret** that the administrator passes back to you.
**Tip:**
It's recommended to publish, alongside your tool's documentation, a short page listing every scope your tool may request. Mismatched scopes are a common source of mystifying 403 responses, and giving LMS administrators a clear allowlist up front prevents most of those tickets.
For the LMS-specific instructions for Canvas, see the [Full OAuth2 setup walkthrough](/guides/api/oauth#oauth2-setup) in the OAuth2 guide.
}
>
The Client ID and Client Secret returned by the LMS, together with the LMS's OAuth login, token, and refresh endpoint URLs, all need to be entered into the LTIAAS Portal. In the [LTIAAS Portal](https://portal.ltiaas.com), navigate to the `Registrations` page in the left sidebar, and either add a new registration or edit an existing one. Fill in the following fields:
| Field | Description | Canvas example |
|---|---|---|
| Client ID | The client ID provided by the LMS. | `10000000000123` |
| Client Secret | The client secret provided by the LMS. | (the secret value) |
| Login URL | The login endpoint URL for the LMS. | `https://canvas.school.edu/login/oauth2/auth` |
| Access Token URL | The token endpoint URL for the LMS. | `https://canvas.school.edu/login/oauth2/token` |
| Refresh Token URL | The refresh token endpoint URL. | `https://canvas.school.edu/login/oauth2/token` |
| Scopes | The scopes that your application requires. | `url:GET|/api/v1/courses/:course_id` |
Save the form and deploy the changes. The OAuth configuration is one-time per LMS — every user who connects from that LMS will reuse the same OAuth client.
**Tip:**
If you want to use OAuth2 without an LTI® context — that is, without any LTI® launches — this is supported. Enter dummy values into the LTI®-specific fields of the registration form. The OAuth2 endpoints will continue to work normally.
}
>
OAuth2 is per-user — every individual who needs LMS API access must grant it explicitly. Your application surfaces this through a "Connect with Canvas" (or similar) button, somewhere natural in the user experience — typically on a settings page or as part of an onboarding step. The button is a plain link pointing at the LTIAAS-hosted OAuth start URL, including the `platformId` of the LMS to connect to.
```html
Connect With Canvas
```
The `/oauth/start/:platformId` endpoint also accepts an optional list of scopes via a `scope` query parameter. If omitted, the default scopes configured in the Portal will be used.
```html
Connect With Canvas (file access)
```
**Tip:**
The `platformId` is the same persistent identifier you stored when the LMS registered with LTIAAS. If your tool is multi-tenant, look up the correct `platformId` per user before constructing the link, so that each user is sent to their own LMS's OAuth flow.
}
>
After the user clicks the connect button, LTIAAS takes over the OAuth2 dance. It redirects the user's browser to the LMS's OAuth login endpoint with the appropriate client ID and scopes; the LMS prompts the user to approve the requested permissions; the LMS then redirects the user's browser to LTIAAS at `https://your.ltiaas.com/oauth/complete` with an authorization code; and LTIAAS captures that code and exchanges it for a one-time `authorizationCode` of its own, which is the token your application will use to request the actual LMS access token.
LTIAAS finally redirects the user's browser to the OAuth Redirect URL you configured in the LTIAAS Portal, with the `authorizationCode` and the `platformId` appended as query parameters. Your application writes zero code for any of these steps — the entire flow happens between LTIAAS, the LMS, and the user's browser.
}
>
Configure the OAuth Redirect URL in the LTIAAS Portal under the *API Settings* page. This is the URL of an endpoint hosted by your application; it must be reachable by the user's browser. When LTIAAS finishes the OAuth2 handshake, it redirects the user there with two query parameters.
```
GET https://your.application.com/oauth/callback
?authorizationCode=abc123
&platformId=Y9LENU9n911EMxp9QSrE
```
Your application's back-end captures both values immediately. The `authorizationCode` is one-time use and short-lived — exchange it for an access token without delay. The `platformId` identifies which LMS the user just connected from; use it to map this OAuth connection to the correct tenant or organization in your application's database.
**Caution:**
Always make authenticated LTIAAS API requests from your application's back-end, never from client-side code. The Bearer token used to call `/api/oauth/access-token/:platformId` is your LTIAAS API key, and exposing it from the front-end will leak your credentials.
}
>
The next step is to exchange the `authorizationCode` for the LMS access token by making a POST request to the `/api/oauth/access-token/:platformId` endpoint. This endpoint uses the Bearer authentication scheme — no `ltik`, no `serviceKey`, since no LTI® launch is happening.
```javascript
// Building Bearer API authentication header
const authorizationHeader = `Bearer ${API_KEY}`
const headers = { Authorization: authorizationHeader }
// Making /api/oauth/access-token/:platformId POST request
const body = { authorizationCode: receivedAuthorizationCode }
const response = await requests.post(
`https://your.ltiaas.com/api/oauth/access-token/${platformId}`,
body,
{ headers }
)
const accessToken = response['accessToken']
const refreshToken = response['refreshToken']
const expiresIn = response['expiresIn'] // seconds until accessToken expiry
```
Your application stores both tokens, keyed by `(toolUserId, platformId)`, and encrypts them at rest. These tokens grant API access to the LMS user's account, so they must be protected at the same level as any other long-lived credential.
When the access token expires — which can happen in as little as one hour, depending on the LMS — your application refreshes it by making a POST request to `/api/oauth/access-token/:platformId/refresh` with the stored `refreshToken`. The refresh response mirrors the original, except that it does not include a new `refreshToken`; your application continues to use the one it already stored.
For a description of all accepted fields and refresh-token management, see [Managing Access Token Expiration](/guides/api/oauth#managing-access-token-expiration) in the OAuth2 guide.
}
>
Your application now has a Bearer access token that the LMS itself accepts. From here, your application calls the LMS's REST API directly — not through LTIAAS. LTIAAS does not proxy the LMS-native API calls; it is only involved in issuing and refreshing the access token.
```javascript
const accessToken = await getValidLmsToken(userId, platformId)
// (refresh the token via LTIAAS before returning if it has expired)
const response = await requests.get(
'https://canvas.school.edu/api/v1/courses/123/files',
{ headers: { Authorization: `Bearer ${accessToken}` } }
)
```
Because the calls happen directly between your application and the LMS, your application is responsible for several pieces of operational logic.
**Tip:**
Build a `getValidLmsToken()` helper that checks the stored `expiresAt` timestamp on every call and refreshes the token through LTIAAS when it's about to expire. A buffer of 60 seconds before the actual expiration is usually safe.
It's also important to handle the case where a stored token is unexpectedly invalidated mid-call — for example, when the LMS administrator revokes the developer key or the user changes their LMS password. A `401` response from the LMS API on a token your application thought was valid should be treated as a permanent failure and surfaced to the user as a prompt to reconnect.
LMS APIs typically throttle aggressively, so it's recommended to cache responses where possible and batch reads. LMS APIs also evolve independently of LTI®, so pin your application to a specific API version where the LMS allows (Canvas, for example, accepts `Accept: application/vnd.canvas.api+json; version=1.0`).
You can read more about the OAuth2 flow, including LMS-specific setup, in the [OAuth2 guide](/guides/api/oauth).
---
## What's Next
- [Launch + idtoken SSO flow](/guides/flows/launch-and-sso) — the typical entry point that establishes a user in your tool before they connect via OAuth.
- [Names & Roles flow](/guides/flows/names-and-roles) — for most roster needs, NRPS is simpler than OAuth2. Reach for OAuth2 only when you need functionality that LTI® does not expose.
- [Grade Passback flow](/guides/flows/grade-passback) — for standard grading, the AGS service is enough; OAuth2 is only needed for the LMS's advanced gradebook features.
# Using the API
## Using the LTIAAS API
Source: https://ltiaas.com/guides/api/introduction
# Using the LTIAAS API
## Section Summary
This section will provide you with useful guides on how to use the LTIAAS API to interact with the many LTI® services we support.
## Next Steps
You can start by learning how to authenticate LTIAAS API requests. Please see the [Authenticating API requests](/guides/api/authentication) guide for more information.
## Accessing the API Asynchronously
Source: https://ltiaas.com/guides/api/async
# Accessing the API Asynchronously
**Caution:**
Due to the nature of LTI®, it's recommended to always utilize the services synchoronously, within the context of an LTI® launch.
For most use cases it should be enough and a lot easier to trigger periodic tasks when someone launches to your tool.
Some of the LTI® services might be useful to access outside of the context of an LTI® launch. For example, you might want to run a cron job daily to submit students grades.
Since the `ltik` token used in the [ltik based API authentication method](/guides/api/authentication#ltik-based-authentication) expires after **24 hours**, it's not ideal for this kind of use case. Instead, you can use the `serviceKey` token detailed in the [service key based API authentication method](/guides/api/authentication#service-key-based-authentication) to call the LTIAAS service API asynchronously.
## Retrieving the Service Key
After receiving an LTI® launch you can use the [ltik based authentication](/guides/api/authentication#ltik-based-authentication) to [retrieve an ID Token](/guides/api/idtoken). If the current LTI® launch context has access to at least one of the *Assignment and Grades* or *Names and Roles Provisioning* LTI® services, the ID Token you retrieved will have an `idtoken.services.serviceKey` field.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const response = requests.get('https://your.ltiaas.com/api/idtoken', { headers })
// Retrieving service key
const serviceKey = response['services']['serviceKey']
```
## Storing the Service Key
Each `serviceKey` represents a single LTI® launch context, which usually translates to an activity in the LMS. So you need to be mindful of that when storing and using these tokens. Calling the API using a certain `serviceKey` will yield results from the launch context that it's associated with.
Ideally you want to store the `serviceKey` in your database along with the, **resource link ID**, **context ID** and **platform (LMS) ID** that originated it.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const response = requests.get('https://your.ltiaas.com/api/idtoken', { headers })
// Retrieving service key and context and platform IDs
const serviceKey = response['services']['serviceKey']
const resourceLinkId = response['launch']['resourceLink']['id']
const contextId = response['launch']['context']['id']
const platformId = response['platform']['id']
storeServiceKeyInDatabase(resourceLinkId, contextId, platformId, serviceKey)
```
**Caution:**
The `serviceKey` does not expire, however, due to the nature of LTI®, the information stored within a certain `serviceKey` might become inaccurate at some point and this will likely cause API requests to fail. For this reason it's recommended to always update the `serviceKey` for a certain context **on every launch**.
**Tip:**
It also makes sense to store the context IDs along side activity and grading information on your application, so you can know which `serviceKey` to use when calling the API to perform an action.
## Using the Service Key
Once you have a `serviceKey` stored, you can use it to perform the LTIAAS service API calls by using the [service key based API authentication method](/guides/api/authentication#service-key-based-authentication).
*Synchronizing grades example:*
```javascript
const getLineItem = (headers, resourceLinkId) => {
// Making /api/lineitems GET request to get line item for resource link
const url = `https://your.ltiaas.com/api/lineitems?resourceLinkId=${resourceLinkId}`
const lineitems = requests.get(url, { headers })
return encodeURIComponent(lineitems[0]['id'])
}
const sendGrade = (headers, lineItemId, grade) => {
// Making /api/lineitems//scores POST request to sync grades
const score = {
userId: grade.userId,
scoreGiven: grade.score,
activityProgress: 'Completed',
gradingProgress: 'FullyGraded'
}
requests.post(`https://your.ltiaas.com/api/lineitems/${lineItemId}/scores`, score, { headers })
}
const syncGrades = (activity) => {
// Get service key for activity
const serviceKey = getServiceKeyFromDatabase(activity)
// Building service key based API authentication header
const authorizationHeader = `SERVICE-AUTH-V1 ${API_KEY}:${serviceKey}`
const headers = { Authorization: authorizationHeader }
// Get line item ID for activity
const lineItemId = getLineItem(headers, activity.resourceLinkId)
// Send grades
for (const grade of activity.grades) {
sendGrade(headers, lineItemId, grade)
}
}
// This task runs once a day
const activities = getAcvivitiesFromDatabase()
for (const activity of activities) {
syncGrades(activity)
}
```
## Authenticating API Requests
Source: https://ltiaas.com/guides/api/authentication
# Authenticating API Requests
**Caution:**
LTIAAS API endpoints **must** only be called from your application's back-end. Authenticated API requests will include your API key, so making them from the front-end might result in your credentials getting leaked.
## Ltik Based Authentication
During an LTI® launch, LTIAAS will perform the LTI® protocol authentication flow and then redirect the user to your application, this redirection will also append a query parameter called `ltik` to your account's registered target launch URL:
> https://your.tool.com/launch?ltik=eyJhbGciOiJIUzI1NiIs...
This token represents a single LTI® launch and allows LTIAAS to store and retrieve information specific to that launch context.
You can then combine your account's `API Key` and the `ltik` token received to build the following authorization header:
```
Authorization: LTIK-AUTH-V2 :```
Example:
```
Authorization: LTIK-AUTH-V2 df06d55e-3b0f-4121-b60f-c39469b5b550:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0
```
**Caution:**
The `ltik` token represents a single LTI® launch, therefore each token must only be used in requests made within the same launch context in which it was generated. Using an `ltik` token outside of its originating context can cause unexpected behavior and provide outdated information.
**Caution:**
Due to the nature of LTI®, the ID Token related to a launch should not be trusted after some time, for this reason each `ltik` token **expires after 24 hours.**
The ltik based authentication schema is accepted by all of the LTIAAS service API endpoints.
## Service Key Based Authentication
If you need to make API requests outside of the context of an LTI® launch, you can use the service key based authentication schema.
**Caution:**
Due to the nature of LTI®, it's recommended to always utilize the services synchronously, within the context of an LTI® launch.
For most use cases it should be enough and a lot easier to trigger periodic tasks when someone launches to your tool.
After receiving an LTI® launch you can use the [ltik based authentication](#ltik-based-authentication) to [retrieve an ID Token](/guides/api/idtoken). If the current LTI® launch context has access to at least one of the *Assignment and Grades* or *Names and Roles Provisioning* LTI® services, the ID Token you retrieved will have an `idtoken.services.serviceKey` field.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const response = requests.get('https://your.ltiaas.com/api/idtoken', { headers })
// Retrieving service key
const serviceKey = response['services']['serviceKey']
```
You can then combine your account's `API Key` with the `serviceKey` retrieved to build the following authorization header.
```
Authorization: SERVICE-AUTH-V1 :```
Example:
```
Authorization: SERVICE-AUTH-V1 df06d55e-3b0f-4121-b60f-c39469b5b550:eyJhbGcieyJzdWIiOiIxMjM0.OiJIUzI1NiIsInR5cCI6IkpXVCJ9
```
**Info:**
Unlike the `ltik` token, the `serviceKey` will not expire after 24 hours and can be used outside of an LTI® launch. This allows you to, for example, run a cron job to synchronize grades once a day.
It's important to note that each `serviceKey` represents a single LTI® launch context, which usually translates to an activity in the LMS. So you need to be mindful of that when storing and using these tokens. Calling the API using a certain `serviceKey` will yield results from the launch context that it's associated with.
**Tip:**
You can learn more about using the service key based API authentication method in the [Accessing the API asynchronously guide](/guides/api/async).
## Bearer API Key Based Authentication
A select few API endpoints are not related to an LTI launch context, such as during dynamic registration. Therefore, an LTIK or service key is not available. Only the API key needs to be sent in the form of a Bearer token.
```
Authorization: Bearer ```
Example:
```
Authorization: Bearer df06d55e-3b0f-4121-b60f-c39469b5b550
```
## Performing Deep Linking
Source: https://ltiaas.com/guides/api/deeplinking
# Performing Deep Linking
**Tip:**
Make sure you've read about [authenticating API requests](/guides/api/authentication) before proceeding to the guide below.
**Info:**
The Deep Linking endpoint only accepts the [ltik based authentication method](/guides/api/authentication#ltik-based-authentication).
If your application is a hub of multiple resources and/or activities, the LTI® protocol makes this easy to manage through the *Deep Linking* service. You can use this service to allow teachers and administrators to select one specific resource from your application when creating a resource link (activity) in the LMS. LTIAAS gives you access to this service through the Deep Linking flow and `/api/deeplinking/form` API endpoint.
## The Deep Linking Flow
The LTI® Deep Linking flow consists of 4 steps:
- [Deep Linking launch](#deep-linking-launch);
- [User content selection](#user-content-selection);
- [Creation of deep linking form](#creation-of-deep-linking-form);
- [Submission of selected content](#submission-of-selected-content).
### Deep Linking Launch
An LTI® Deep Linking launch happens when a privileged user within the LMS, usually administrator or teacher, launches to your LTI® tool from an activity or tool link creation page. This launch works in the same way as the regular LTI® launch, with the exception that, at the end of the flow, LTIAAS will redirect the user to your application's registered deep linking target launch URL.
After receiving the Deep Linking launch, your application should display a content selection view.
**Tip:**
Your application can display a personalized view for the user that initiated the LTI® launch based on their access level and role. To know more about this, check our [Retrieving ID Token guide ](/guides/api/idtoken) and its [Performing SSO section](/guides/api/idtoken#performing-sso).
### User Content Selection
In the content selection view, the user will be able to choose which resource provided by your application should be linked to the activity being created.
After the user selects the desired resource, your application's front-end should make a request to your back-end identifying the selection.
### Creation of Deep Linking Form
**Info:**
This guide focuses on the most common and/or relevant fields of each section in the deep linking request body, for a full list of available fields, as well as possible API response bodies and statuses, check the [Deep Linking endpoint API reference](/api/post-api-deeplinking-form).
#### The Content Item Object
A content item represents a resource that should be added to the LMS. Only the `type` field is always required to be present in this object, the other fields might be required or optional depending on the type of resource being added.
**Tip:**
You can find a full list of available types and their respective required fields in the IMS Deep Linking Content Item Types Documentation:
Learning Tools Interoperability (LTI®) Deep Linking Specification, IMS Final Release Spec Version 2.0, 1EdTech Consortium, April 2019, Section 3, https://www.imsglobal.org/node/162911#content-item-types.
The most common type of content item is the `ltiResourceLink`, which represents an LTI® link to a resource in your application, usually in the form of an activity. For this type of content item, the `type` and `url` fields are **required**.
```json
{
"type": "ltiResourceLink",
"url": "https://your.ltiaas.com/lti/launch?resource=123",
"title": "Resource"
}
```
**Tip:**
It's common to provide a `title` field that most LMSes will use as the activity title.
#### Building the Content Item URL
The `ltiResourceLink` content item `url` field is the target the LMS should redirect the user to after a successful LTI® launch from the newly created activity.
**Caution:**
For the LTI® launch to work from the new activity, the `url` field of the content item **MUST** point to your LTIAAS subdomain launch URL `https://your.ltiaas.com/lti/launch`.
You can add any query parameters you want to the content item URL, as they will be passed along to your application's registered launch URL during subsequent LTI® launches. These parameters can be used to identify the resource linked to the activity.
**Content item URL:**
> https://your.ltiaas.com/lti/launch?resource=123
**Final target URL after LTI® launch:**
> https://your.application.com/your-launch-endpoint?resource=123
#### Calling the Deep Linking Endpoint
After your back-end receives the request containing the user's selection, you can call the `/api/deeplinking/form` endpoint to create a self-submitting form that will be used to submit the selection and finalize the flow.
The Deep Linking endpoint accepts a POST request with a JSON body containing the `contentItems` field. This field is an array of content item objects that represent a resource that should added to the LMS, potentially being created as an activity.
**Tip:**
The LTI® protocol allows you to pass multiple content items in a deep liking request, but it's common for LMSes to accept only one since this flow is often connected to the creation of an activity.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/deeplinking/form POST request
const body = {
contentItems: [{
type: 'ltiResourceLink',
url: 'https://your.ltiaas.com/lti/launch?resource=123',
title: 'Resource'
}]
}
const response = requests.post(`https://your.ltiaas.com/api/deeplinking/form`, body, { headers })
const form = response['form']
return form
```
The `/api/deeplinking/form` endpoint response is an object containing a `form` field that is an HTML snippet that should be added to your application's HTML body to finalize the deep linking process.
```json
{
"form": "
"
}
```
#### Creating Your Own Submission Form
If you want to create your own form instead of using the one provided by the Deep Linking form endpoint, you can do so by making a POST request to the `/api/deeplinking` endpoint. This endpoint accepts the same request body as the `/api/deeplinking/form` endpoint, but instead of returning a self-submitting form, it returns a JSON object containing the `message` and `target` fields.
```json
{
"message": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwib...",
"target": "https://lms.example.com/lti/deeplinking"
}
```
The `message` field is a JWT containing the selected resource information and the `target` field is the submission endpoint for the form. You can use this information to create your own submission form:
```javascript
const form = `
`
return form
```
### Submission of Selected Content
After your application's back-end makes the API request to generate the self-submitting form, your front-end should append the resulting HTML snippet to the body:
```javascript
// Back-end
// ...
const form = response['form']
return form
// Front-end
// Append self-submitting form to HTML body
$('body').append(form)
```
The form will then submit itself, finalizing the deep linking process and often closing the dialogue window.
## Checking Service Availability
The *Deep Linking* service will only be available in the context of deep linking LTI® launches. Before attempting to submit resources you can [make a call to the ID Token endpoint](/guides/api/idtoken) and check the [services section](/guides/api/idtoken#deeplinking) to know if the service is available.
You can know if the service is available based on the value of the `idtoken.services.deepLinking.available` boolean field:
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const idtoken = requests.get('https://your.ltiaas.com/api/idtoken', { headers })
const isServiceAvailable = idtoken['services']['deepLinking']['available']
if (isServiceAvailable) {
// Making /api/deeplinking/form POST request
const body = {
contentItems: [{
type: 'ltiResourceLink',
url: 'https://your.ltiaas.com/lti/launch?resource=123',
title: 'Resource'
}]
}
const response = requests.post(`https://your.ltiaas.com/api/deeplinking/form`, body, { headers })
const form = response['form']
return form
}
```
## Performing Dynamic Registration
Source: https://ltiaas.com/guides/api/dynamic-registration
# Performing Dynamic Registration
**Tip:**
Make sure you've read about [authenticating API requests](/guides/api/authentication) before proceeding to the guide below.
**Info:**
The Dynamic Registrations API endpoints only accept the [bearer api key based authentication method](/guides/api/authentication#bearer-api-key-based-authentication).
## How Dynamic Registration Works
LTI® Dynamic Registration is used to automate the LMS/tool registration process. Several LMSes support it, including [Canvas](guides/lms/canvas#dynamic-registration), [Moodle](/guides/lms/moodle#dynamic-registration), and [Brightspace](/guides/lms/brightspace#dynamic-registration). The default Dynamic Registration process is as follows:
1. The LMS administrator enters your dynamic registration URL into their LMS: `https://your.ltiaas.com/lti/register`
2. LTIAAS communicates with the LMS and completes the registration process automatically.
3. (optional) The LMS administrator can open the registered tool and modify any pre-defined settings as needed.
### Controlling How Much User Data the LMS Sends (Privacy Level)
By default, registrations request the **`public`** privacy level, meaning the LMS sends full user information (name and email) on each launch. You can request that the LMS send less personal data by choosing a different privacy level:
| Privacy level | User data sent on launch |
| --- | --- |
| `public` (default) | Full name and email |
| `name_only` | Name only |
| `email_only` | Email only |
| `anonymous` | No name or email — only the stable, opaque user id (`sub`) |
There are three ways to set it, in order of precedence (highest first):
1. **Registration completion body (Pre-Approval)** — if you use the [Pre-Approval flow](#the-pre-approval-flow), pass `privacyLevel` in the body of the [Complete Registration API](/api/post-api-registrations-complete) call (see [below](#submission-of-registration-approval-and-settings-override)). This overrides everything else.
2. **Registration URL** — append a `privacyLevel` query parameter to the Dynamic Registration URL the LMS administrator enters:
`https://your.ltiaas.com/lti/register?privacyLevel=anonymous`
This works in **both** the standard and Pre-Approval flows. In the Pre-Approval flow the value is carried through as the default and can be overridden by the completion body above — you can read it back from the [Get Registrations API](/api/get-api-registrations) (`privacyLevel` field).
3. **Account-wide default** — the default privacy level applied when no per-registration value is given, configured for your account in the LTIAAS Portal.
**Caution:**
The privacy level is declared **at registration time** — changing it afterwards means re-registering the tool. On **Canvas**, any level other than `public` also stops Canvas custom fields (such as `custom_canvas_user_id`) from being sent. If you need the Canvas numeric user id, use `public`; otherwise the stable LTI `sub` is available at every privacy level.
In some cases, it is not desirable to allow specific users or LMSes to register your tool. There are two general scenarios that might require approval/intervention while doing dynamic registration:
1. **Simple Approval**: If you want to allow any user to register your tool, but want to manually approve each registration before it can be used, you can simply enable the `Dynamic Registration Auto-Activation` feature in the LTIAAS Portal within the _API Settings_ page.
2. **Account Authentication or Payment**: You might want to require the user to login to your service or submit payment before allowing the registration to complete. LTIAAS has APIs that enable this flow that we call _Dynamic Registration Pre-Approval_.
**Info:**
If all you need is _simple approval_, you can stop reading here.
## The Pre-Approval Flow
**Tip:**
The _Dynamic Registration Pre-Approval_ flow is disabled by default. It can be enabled in the LTIAAS Portal under the _API Settings_ page by enabling the `Enable Pre-Approval` option.
The LTIAAS _Dynamic Registration Pre-Approval_ flow consists of 4 steps:
- [Initiate Dynamic Registration](#initiate-dynamic-registration)
- [Pre-Approval Redirection](#intermediate-target-redirection)
- [Get Registration Data](#get-registration-data)
- [Submission of registration approval and/or settings override](#submission-of-registration-approval-and-settings-override)
### Initiate Dynamic Registration
Just like in the _'vanilla'_ dynamic registration described above, the LMS administrator initiates Dynamic Registration by entering the Dynamic Registration URL into their LMS: `https://your.ltiaas.com/lti/register`.
### Pre-Approval Redirection
Because the `Enable Pre-Approval` option is true, the user is redirected to the URL you entered into the `Pre-Approval URL` field of the _API Settings_ page. This page will receive a query parameter called `registrationId`. For example:
`https://yoursite.com/intermediateTarget?registrationId=123`
You will use the `registrationId` to get information about the LMS and complete/approve the registration process.
### Get Registration Data
Once LTIAAS has redirected to your `Pre-Approval URL`, you can use the `registrationId` query parameter to get information about the LMS using the [Get Registrations API](/api/get-api-registrations).
```javascript
// Building Bearer API authentication header
const authorizationHeader = `Bearer ${API_KEY}`
const headers = { Authorization: authorizationHeader }
// Get the registrationId (from the front-end)
const registrationId = req.query.registrationId
// Making /api/registrations GET request (called in your back-end)
const response = requests.get(`https://your.ltiaas.com/api/registrations/${registrationId}`, { headers })
const url = response['url']
const lmsFamily = response['familyCode']
//... and other data
```
A successful response will contain details about the LMS and the options it supports.
```json
{
"url": "https://moodle.ltiaas.com",
"familyCode": "moodle",
"version": "4.4.1+ (Build: 20240705)",
"supportedScopes": [
"https://purl.imsglobal.org/spec/lti-bo/scope/basicoutcome",
"https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly",
"https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly",
"https://purl.imsglobal.org/spec/lti-ags/scope/score",
"https://purl.imsglobal.org/spec/lti-ags/scope/lineitem",
"https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly",
"https://purl.imsglobal.org/spec/lti-ts/scope/toolsetting",
"openid"
],
"supportedMessages": [
{
"type": "LtiResourceLinkRequest"
},
{
"type": "LtiDeepLinkingRequest",
"placements": [
"ContentArea"
]
}
]
}
```
**Caution:**
The `registrationId` parameter is not persistent. It can only be used to get information about a potential registration and to complete a registration. Once the registration is completed or abandoned, the registrationId will no longer work.
### Submission of Registration Approval and Settings Override
During the dynamic registration process, you are free to redirect to other pages on your site, for example to allow the user to log in. It is important to not leave the context of the dynamic registration iframe, because the iframe is used by LTIAAS to communicate with the LMS.
When you are ready to approve the registration, you can use the [Complete Registration API](/api/post-api-registrations-complete).
```js
// Building Bearer API authentication header
const authorizationHeader = `Bearer ${API_KEY}`
const headers = { Authorization: authorizationHeader }
// Get the registrationId
const registrationId = req.query.registrationId
// build the registration completion message
const body = {
// These are all optional, defaulting to the stored values set in the LTIAAS Portal
platformName: 'Platform name',
autoActivate: true,
privacyLevel: 'public', // 'public' (default), 'name_only', 'email_only', or 'anonymous'
messages: [
// Canvas placements are fully qualified URLs; Brightspace uses the standard bare names
{ type: 'LtiResourceLinkRequest', placements: ['https://canvas.instructure.com/lti/assignment_selection'] },
{ type: 'LtiDeepLinkingRequest', placements: ['ContentArea'] }
// LTIAAS will validate each of these placements, only sending the ones allowed by the LMS
]
}
// Making /api/registrations POST request
const response = requests.post(`https://your.ltiaas.com/api/registrations/${registrationId}/complete`, body, { headers })
const htmlToInject = response['html']
return htmlToInject
```
#### Choosing Placements
Placements tell the LMS *where* your tool should appear. The identifiers are **LMS specific**, and they must be sent exactly as the LMS advertised them. LTIAAS validates each placement against the list the LMS provided when it started the registration and silently drops anything that doesn't match — so a misspelled or wrongly formatted placement is discarded rather than raising an error.
The safest approach is to read `supportedMessages[].placements` from the [Get Registrations API](#get-registration-data) response and pick from those exact strings. Use `familyCode` to branch when you need to request different placements per LMS.
| LMS | `familyCode` | Format | Official list |
| --- | --- | --- | --- |
| Canvas | `canvas` | Fully qualified URLs prefixed with `https://canvas.instructure.com/lti/`, e.g. `https://canvas.instructure.com/lti/course_navigation` | [Canvas placements overview](https://canvas.instructure.com/doc/api/file.placements_overview.html) and the [Canvas LTI registration docs](https://developerdocs.instructure.com/services/canvas/external-tools/lti/file.registration) |
| Brightspace | `desire2learn` | Bare, standard names only: `ContentArea` and `RichTextEditor` | [Brightspace tool registration, deployment, and links](https://community.d2l.com/brightspace/kb/articles/23662-tool-registration-deployment-and-links) and [Deep linking extension with LTI 1.3](https://community.d2l.com/brightspace/kb/articles/23755-deep-linking-extension-with-lti-1-3) |
**Info:**
`ContentArea` and `RichTextEditor` are the only placements defined by the [LTI® Dynamic Registration specification](https://www.imsglobal.org/spec/lti-dr/v1p0) itself, and every other value is a vendor extension. Canvas accepts both — it maps `ContentArea` onto its `link_selection` placement and `RichTextEditor` onto `editor_button`. If `placements` is omitted entirely, deep linking defaults to `ContentArea`.
The full per-placement breakdown, including which message types each Canvas placement supports, is in the [Complete Registration API reference](/api/post-api-registrations-complete).
This will return an html snippet that needs to be appended to the document that is in the active iframe for this registration.
```js
{
platformId: "A2F48n9Ss8Hjpw4gjo0",
html: ""
}
```
**Tip:**
The `platformId` that is returned in this step is persistent. It can be used later on to update, activate, or deactivate the platform registration.
Here's an example of completing the process by appending the html snippet to the active iframe's document body.
```js
// Front-end
// Append returned script to HTML body
$('body').append(htmlToInject)
```
## Platform Registration Management
Once dynamic registration (or manual registration) is complete, you can use the platformId to manipulate the registration via the `/admin/platforms` API. See our [Platforms API reference](/api/ltiaas) for more information.
## Handling API Errors
Source: https://ltiaas.com/guides/api/error-handling
# Handling API Errors
The LTIAAS API works a middleman between your application and the LMS, this means that errors can happen in both sides of the communication. This guide will help you understand the different types of errors that can happen and how to handle them.
## Structure of the LTIAAS Error Object
## Retrieving the ID Token
Source: https://ltiaas.com/guides/api/idtoken
# Retrieving the ID Token
**Tip:**
Make sure you've read about [authenticating API requests](/guides/api/authentication) before proceeding to the guide below.
**Info:**
The ID Token endpoint only accepts the [ltik based API authentication method](/guides/api/authentication#ltik-based-authentication).
The ID Token is the main piece of data in the LTI® protocol, it's sent by the LMS to the LTI® tool and contains information regarding the current LTI® launch, this includes user information, LMS information, launch context information and everything you need to use the many LTI® services.
LTIAAS gives you access to a launch specific ID Token, formatted for easy of use, through the `/api/idtoken` API endpoint.
## Calling the ID Token Endpoint
To retrieve the ID Token related to an LTI® launch, you need to make a GET request to the `/api/idtoken` endpoint of your LTIAAS' account subdomain.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const response = requests.get('https://your.ltiaas.com/api/idtoken', { headers })
const userInfo = response['user']
const lmsInfo = response['platform']
const launchInfo = response['launch']
const servicesInfo = response['services']
```
You now have everything you need to [perform SSO and let users into your application](#performing-sso).
## Structure of the LTIAAS ID Token
**Info:**
This guide focuses on the most common and/or relevant fields of each section in the ID Token object, for a full list of fields currently supported, as well as possible API response bodies and statuses, check the [[GET] ID Token endpoint API reference](/api/get-idtoken).
LTIAAS formats the raw ID Token sent by the LMS into a developer-friendly format, separating the claims into helpful sections and giving them more human-readable names:
```json
{
"ltiVersion": "1.3.0",
"user": {...},
"platform": {...},
"launch": {...},
"services": {...}
}
```
### User Section
The `user` section in the LTIAAS ID Token is an object containing information provided by the LMS regarding the user that initiated a specific LTI® launch:
```json
{
"ltiVersion": "1.3.0",
"user": {
"id": "2",
"email": "john@lms.com",
"name": "John Doe",
"givenName": "John",
"familyName": "Doe",
"roles": [
"http://purl.imsglobal.org/vocab/lis/v2/system/person#Administrator"
],
...
},
...
}
```
**Info:**
Fields containing personal information like `name` and `email` might not be present in the ID Token. The LMS can choose to omit this information based on their privacy rules. The only fields guaranteed to be present in this section are `id` and `roles`.
#### id
The `id` field contains the user's identifier inside of the LMS. This ID will probably not be unique between LMSes, so it's not recommended to be used alone as a primary identifier. We go into more detail on this topic in the [performing SSO section](#performing-sso).
#### email
The `email` field contains the user's registered email inside of the LMS.
#### name, givenName and familyName
The `name`, `givenName` and `familyName` fields contain the user's registered full name, first last and last name respectively.
#### roles
The `roles` field contains a list of the user's roles within this specific launch context.
**Tip:**
A list of all possible roles can be found in the IMS Role Vocabulary:
Learning Tools Interoperability (LTI®) Core Specification, IMS Final Release Spec Version 1.3, 1EdTech Consortium, April 2019, A.2 Role vocabularies, https://www.imsglobal.org/node/162741#role-vocabularies.
You can choose to customize your tool's view based on the roles present in this list, for example, showing additional options for user's with variations of the `Instructor` role.
### Platform Section
The `platform` section in the LTIAAS ID Token is an object containing information regarding the LMS that originated a specific LTI® launch:
```json
{
"ltiVersion": "1.3.0",
"platform": {
"id": "Y9LENU9n911EMxp9QSrE",
"url": "https://lms.example.com",
"clientId": "KzJtrQxEUYGWXjx",
"deploymentId": "1",
"name": "LMS",
"description": "A great LMS",
"productFamilyCode": "lms",
"version": "2021051712",
...
},
...
}
```
#### id
The `id` field contains the LMS's registration ID inside LTIAAS, generated when you perform a platform registration. This ID is globally unique.
#### url
The `url` field contains the LMS's issuer URL. This will match the URL used in the platform registration.
#### clientId
The `clientId` field contains the identifier generated by LMS for your LTI® tool. This will match the client ID used in the platform registration.
#### deploymentId
The `deploymentId` field contains the identifier for one specific instance of the LMS. This is most relevant when dealing with an LMS that uses multi-tenancy (Blackboard, for example), since this means that every instance of this LMS will share a single platform registration. The deployment ID will allow you to track and/or control access between the various instances of such LMSes.
#### name and description
The `name` and `description` fields contain the LMS's name and description as used in the platform registration.
#### productFamilyCode
The `productFamilyCode` field contains an identifier for that family of LMSes. This allows you to identify the type of LMS and maybe customize your tool accordingly. Common examples are: `moodle`, `canvas` and `BlackboardLearn`.
#### version
The `version` field contains the LMS version inside its own versioning system.
### Launch Section
The `launch` section in the LTIAAS ID Token is an object containing information specific to the actual LTI® Launch:
```json
{
"ltiVersion": "1.3.0",
"launch": {
"context": {...},
"resourceLink": {...},
"presentation": {...},
"custom": {...}
},
...
}
```
#### context
The `context` object contains information regarding the LMS context where the LTI® launch was originated. A context is most commonly the representation of a course in the LMS, but it can be any section from where an LTI® tool can be launched.
```json
{
"launch": {
"context": {
"id": "2",
"label": "course",
"title": "Course",
"type": [
"CourseSection"
]
},
...
},
...
}
```
- `id` - Context ID. Commonly the ID of a course in the LMS.
- `label` and `title` - Context name. These two fields usually contain a short and full version of the context name respectively.
- `type` - Context type.
**Tip:**
A list of all possible context types can be found in the IMS Context Type Vocabulary:
Learning Tools Interoperability Core Specification, IMS Final Release Spec Version 1.3, 1EdTech Consortium, April 2019, A.1 Context type vocabulary, https://www.imsglobal.org/spec/lti/v1p3/#context-type-vocabulary.
#### resourceLink
The `resourceLink` object contains information regarding the LMS resource link that originated the current LTI® launch. A resource link is most commonly the representation of an activity in the LMS.
```json
{
"launch": {
"resourceLink": {
"id": "4",
"title": "LTI® Activity 1",
"description": "This is a cool activity"
},
...
},
...
}
```
- `id` - Resource link ID. Commonly the ID of an activity in the LMS.
- `title` - Resource link name.
- `description` - Resource link description.
#### presentation
The `presentation` object contains information regarding how the current LTI® launch is being displayed.
```json
{
"launch": {
"presentation": {
"documentTarget": "iframe",
"locale": "en"
},
...
},
...
}
```
- `documentTarget` - Type of LTI® tool visualization. Possible values for this field are `frame`, `iframe` or `window`.
- `locale` - Language, country, and variant as represented using the [IETF Best Practices for Tags for Identifying Languages](https://www.rfc-editor.org/rfc/rfc5646).
#### custom
The `custom` object contains key-value pairs representing the custom parameters defined in the LMS for that tool. Depending on the LMS you can set these custom parameters globally for a tool or specific to individual launches or locations inside the LMS. Some LMSes also support variable substitution.
```json
{
"launch": {
"custom": {
"param": "value",
"location": "workspace"
},
...
},
...
}
```
### Services Section
The `services` section in the LTIAAS ID Token is an object containing information regarding the LTI® services and their availability in the context of a specific LTI® Launch:
```json
{
"ltiVersion": "1.3.0",
"services": {
"deepLinking": {...},
"namesAndRoles": {...},
"assignmentAndGrades": {...},
"serviceKey": "eyJhbGciajsheu..."
},
...
}
```
#### deepLinking
The `deepLinking` object contains availability information for the *Deep Linking* LTI® service.
```json
{
"services": {
"deepLinking": {
"available": false
},
...
},
...
}
```
- `available` - Availability status for the *Deep Linking* service in the current launch context.
#### namesAndRoles
The `namesAndRoles` object contains availability information for the *Names and Roles Provisioning* LTI® service.
```json
{
"services": {
"namesAndRoles": {
"available": true
},
...
},
...
}
```
- `available` - Availability status for the *Names and Roles Provisioning* service in the current launch context.
#### assignmentAndGrades
The `assignmentAndGrades` object contains availability information for the *Assignment and Grades* LTI® service.
```json
{
"services": {
"assignmentAndGrades": {
"available": true,
"lineItemId": "https://lms.example.com/course/1/lineitems/1",
},
...
},
...
}
```
- `available` - Availability status for the *Assignment and Grades* service in the current launch context.
- `lineItemId` - Line item ID for the current launch context. This field will only be present if there's only one line item ID associated with the current context.
#### serviceKey
The `serviceKey` field contains the service key token that can be used to perform the [service key based API authentication](/guides/api/authentication#service-key-based-authentication). This field is only populated if the current launch context has access to at least one of the *Assignment and Grades* or *Names and Roles Provisioning* LTI® services.
## Additional Fields and Raw ID Token
This guide focuses on the most common and/or relevant fields of each section in the ID Token object, for a full list of fields currently supported, check the [ID Token endpoint API reference](/api/get-idtoken).
**Info:**
If the field you need to build your LTI® integration is not one of the fields we take into account when formatting the ID Token, please fell free to [contact us](/contact-us) and we will make sure to include it in a future update.
Alternatively, you can retrieve a raw version of the ID Token generated by the LMS by sending the `?raw=true` query parameter when calling the `/api/idtoken` endpoint:
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const response = requests.get('https://your.ltiaas.com/api/idtoken?raw=true', { headers })
const userID = response['sub']
const userEmail = response['email']
```
The response will be an unformatted version of the ID Token object, including every field as sent by the LMS:
```json
{
"sub": "2",
"iss": "https://lms.example.com",
"aud": "KzJtrQxEUYGWXjx",
"email": "john@lms.com",
"name": "John Doe",
"givenName": "John",
"familyName": "Doe",
"https://purl.imsglobal.org/spec/lti/claim/ext": {...},
"https://purl.imsglobal.org/spec/lti/claim/deployment_id": "1",
"https://purl.imsglobal.org/spec/lti/claim/version": "1.3.0",
"https://purl.imsglobal.org/spec/lti/claim/tool_platform": {...},
"https://purl.imsglobal.org/spec/lti/claim/custom": {...},
"https://purl.imsglobal.org/spec/lti/claim/launch_presentation": {...},
"https://purl.imsglobal.org/spec/lti/claim/roles": [...],
"https://purl.imsglobal.org/spec/lti/claim/context": {...},
"https://purl.imsglobal.org/spec/lti/claim/resource_link": {...},
...
}
```
## Performing SSO
After retrieving the ID Token, you can use the information contained in it to perform SSO and authenticate users into your application, automatically logging them into a preexisting account or provisioning a new one on the spot. There are two main ways of identifying a user:
### Identifying the User by Their Email
One of the quickest ways of identifying a user is through their email:
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const response = requests.get('https://your.ltiaas.com/api/idtoken', { headers })
const userEmail = response['user']['email'] // user@email.com
```
The email contained in the ID Token can be trusted as an identifier for the user that initiated that specific LTI® launch.
**Caution:**
The `user.email` field might not be present in the ID Token. The LMS can choose to omit this information based on their privacy rules.
### Identifying the User by Their ID
The most reliable way of identifying a user is through their ID reported in the ID Token. This ID is only unique within the LMS that originated the LTI® launch, so this value alone is not enough to represent a globally unique user. To get a globally unique identifier, you can combine the value of the user ID with the platform ID:
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const response = requests.get('https://your.ltiaas.com/api/idtoken', { headers })
const platformID = response['platform']['id'] // Y9LENU9n911EMxp9QSrE
const userID = response['user']['id'] // 2
// Build globally unique user ID
const globalUserID = `${platformID}:${userID}`
```
**Info:**
You could technically generate a more precise platform identifier by also combining the platform's deployment ID, however, this is largely unnecessary since user IDs should be unique across different instances of the same platform registration.
## Manipulating Grade Lines
Source: https://ltiaas.com/guides/api/manipulating-grade-lines
# Manipulating Grade Lines
**Tip:**
Make sure you've read about [authenticating API requests](/guides/api/authentication) before proceeding to the guide below.
**Info:**
The Line items endpoint accepts both the [ltik based](/guides/api/authentication#ltik-based-authentication) and [service key based](/guides/api/authentication#service-key-based-authentication) API authentication methods.
The LTI® protocol allows you to manipulate grade lines in the LMS grade book through the *Assignment and Grades - Line Items* service. In the context of LTI®, these grade lines are called *line items*. LTIAAS gives you access to this service through the `/api/lineitems` API endpoint.
## The Line Item Object
**Info:**
This guide focuses on the most common and/or relevant fields of the line item object, a detailed description of available fields can be found in the IMS Line Item Service documentation:
Learning Tools Interoperability (LTI®) Assignment and Grade Services Specification, IMS Final Release Spec Version 2.0, 1EdTech Consortium, April 2019, 3.2 Line item service scope and allowed http methods, https://www.imsglobal.org/spec/lti-ags/v2p0/#line-item-service-scope-and-allowed-http-methods.
The *line item* is the LTI® representation of a grade line in the LMS grade book. Each line item contains information about the grade line it represents.
```json
{
"id": "https://lms.example.com/course/1/lineitems/1",
"scoreMaximum" : 100,
"label" : "Activity 1",
"tag" : "activity",
"resourceLinkId" : "1g3k4dlk49fk",
"resourceId" : "my-tool-activity-1"
}
```
#### id
The `id` field is the identifier for the line item and it's a URL that points to a specific grade line in the LMS grade book. This field is automatically populated by the LMS when the line item is created.
#### scoreMaximum [*required*]
The `scoreMaximum` field is the maximum score that can be assigned to the line item. MUST be a numeric non-null value, strictly greater than 0.
#### label [*required*]
The `label` field is the name of the grade line represented by the line item.
#### tag
The `tag` field is a string that can be used to further identify grade lines and group them together.
#### resourceLinkId
The `resourceLinkId` field is the identifier of the resource link (activity) this grade line is associated with in the LMS.
#### resourceId
The `resourceId` field is a tool provided identifier of the resource that this grade line is associated with inside the LTI® tool.
## Creating a Grade Line
To create grade lines, you need to make a POST request to the `/api/lineitems` endpoint of your LTIAAS' account subdomain. The created line item will be associated with the context that originated the LTI® launch.
**Tip:**
For a description of all accepted fields, as well as possible response bodies and statuses, check the [[POST] Line Items endpoint API Reference](/api/post-api-lineitems).
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/lineitems POST request
const body = {
label: 'Activity 1',
scoreMaximum: 100
}
const response = requests.post('https://your.ltiaas.com/api/lineitems', body, { headers })
```
**Info:**
When creating a new grade line, the `label` and `scoreMaximum` fields are required.
A successful response will contain the newly created line item object with the `id` field populated and any other additional accepted fields you may have provided.
```json
{
"id": "https://lms.example.com/course/1/lineitems/1",
"scoreMaximum" : 100,
"label" : "Activity 1"
}
```
### Binding a Grade Line to an Activity
If you want to bind a new grade line to a specific activity in the LMS, you can populate the `resourceLinkId` field in the request body.
**Tip:**
The resource link ID for an activity can be retrieved from the `idtoken.launch.resourceLink.id` field of the [ID Token](/guides/api/idtoken) generated by launching the LTI® tool from that activity.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const idtoken = requests.get('https://your.ltiaas.com/api/idtoken', { headers })
const resourceLinkId = response['launch']['resourceLink']['id']
// Making /api/lineitems POST request
const body = {
label: 'Activity 1',
scoreMaximum: 100,
resourceLinkId: resourceLinkId
}
const response = requests.post('https://your.ltiaas.com/api/lineitems', body, { headers })
```
**Tip:**
Adding a resourceLinkId allows you to later [search for grade lines associated a specific activity](#filtering-grade-lines-by-the-associated-activity).
### Binding a Grade Line to a Tool Resource
If you want to bind a new grade line to a specific resource inside your LTI® tool, you can populate the `resourceId` field in the request body with an identifier for your internal resource.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/lineitems POST request
const body = {
label: 'Activity 1',
scoreMaximum: 100,
resourceId: 'my-tool-activity-1'
}
const response = requests.post('https://your.ltiaas.com/api/lineitems', body, { headers })
```
**Tip:**
Adding a resourceId allows you to later [search for grade lines associated a specific resource inside your LTI® tool](#filtering-grade-lines-by-the-associated-tool-resource).
## Retrieving Grade Lines for a Context
To retrieve grade lines from an LTI® context you need to make a GET request to the `/api/lineitems` endpoint of your LTIAAS' account subdomain.
**Tip:**
For a description of all accepted query parameters, as well as possible response bodies and statuses, check the [[GET] Line Items endpoint API Reference](/api/post-api-lineitems).
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/lineitems GET request
const response = requests.get('https://your.ltiaas.com/api/lineitems', { headers })
const lineItems = response['lineItems']
```
A successful response will be an object with a `lineItems` field containing an array of line item objects tied to that LTI® context.
```json
{
"lineItems": [
{
"id": "https://lms.example.com/course/1/lineitems/1",
"scoreMaximum" : 100,
"label" : "Activity 1"
},
{
"id": "https://lms.example.com/course/1/lineitems/2",
"scoreMaximum" : 100,
"label" : "Activity 2"
}
]
}
```
#### Response Pagination Fields
**Caution:**
This functionality might not be supported by every LMS.
The `next`, `prev`, `first` and `last` fields will only be present if there are more line items to be retrieved from the context. You can read more about this in the [Handling pagination guide](/guides/api/pagination).
```json
{
"lineItems": [...],
"next": "https://lms.example.com/course/1/lineitems?page=3",
"prev": "https://lms.example.com/course/1/lineitems?page=1",
"first": "https://lms.example.com/course/1/lineitems?page=1",
"last": "https://lms.example.com/course/1/lineitems?page=4"
}
```
### Filtering Grade Lines by the Associated Activity
If you want to retrieve only the grade lines associated to a specific activity, you can populate the `resourceLinkId` query parameter.
**Tip:**
The resource link ID for an activity can be retrieved from the `idtoken.launch.resourceLink.id` field of the [ID Token](/guides/api/idtoken) generated by launching the LTI® tool from that activity.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const idtoken = requests.get('https://your.ltiaas.com/api/idtoken', { headers })
const resourceLinkId = response['launch']['resourceLink']['id']
// Making /api/lineitems GET request
const response = requests.get(`https://your.ltiaas.com/api/lineitems?resourceLinkId=${resourceLinkId}`, { headers })
const lineItems = response['lineItems']
```
### Filtering Grade Lines by the Associated Tool Resource
If you want to retrieve only the grade lines associated to a specific tool resource, you can populate the `resourceId` query parameter with an identifier for your internal resource.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/lineitems GET request
const resourceId = 'my-tool-activity-1'
const response = requests.get(`https://your.ltiaas.com/api/lineitems?resourceId=${resourceId}`, { headers })
const lineItems = response['lineItems']
```
### Retrieving a Grade Line ID from the ID Token
If there is only one grade line associated with a certain LTI® context, its ID might be present in the ID Token generated for that context in the `idtoken.services.assignmentAndGrades.lineItemId` field.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const idtoken = requests.get('https://your.ltiaas.com/api/idtoken', { headers })
const lineItemID = idtoken['services']['assignmentAndGrades']['lineItemId']
```
## Retrieving a Grade Line by Its ID
To retrieve a grade line by its ID you need to make a GET request to the `/api/lineitems/:lineItemID` endpoint of your LTIAAS' account subdomain.
**Tip:**
For a description of possible response bodies and statuses, check the [[GET] Line Item endpoint API Reference](/api/get-lineitem).
**Info:**
Line item IDs are URLs, so their value needs to be URL encoded to be safely sent as a query parameter.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/lineitems/:lineItemID GET request
const lineItemId = 'https://lms.example.com/course/1/lineitems/1'
const urlSafeLineItemId = encodeURIComponent(lineItemId)
const lineItem = requests.get(`https://your.ltiaas.com/api/lineitems/${urlSafeLineItemId}`, { headers })
```
A successful response will contain the requested line item object.
```json
{
"id": "https://lms.example.com/course/1/lineitems/1",
"scoreMaximum" : 95,
"label" : "Activity 1 - New name"
}
```
## Updating a Grade Line by Its ID
To update a grade line you need to make a PUT request to the `/api/lineitems/:lineItemID` endpoint of your LTIAAS' account subdomain.
**Tip:**
For a description of all accepted fields, as well as possible response bodies and statuses, check the [[PUT] Line Item endpoint API Reference](/api/put-api-lineitems-lineitemid).
**Info:**
Line item IDs are URLs, so their value needs to be URL encoded to be safely sent as a query parameter.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/lineitems/:lineItemID PUT request
const body = {
label: 'Activity 1 - New name' // Updating the name
scoreMaximum: 95 // Updating the maximum score
}
const lineItemId = 'https://lms.example.com/course/1/lineitems/1'
const urlSafeLineItemId = encodeURIComponent(lineItemId)
const response = requests.put(`https://your.ltiaas.com/api/lineitems/${urlSafeLineItemId}`, body, { headers })
```
**Info:**
The body you send will replace the information of the existing grade line. For this reason, similar to when creating a new grade line, the `label` and `scoreMaximum` fields are required.
**Caution:**
Fields present in the previous version of the grade line and missing from the new body will be deleted.
A successful response will contain the updated line item object.
```json
{
"id": "https://lms.example.com/course/1/lineitems/1",
"scoreMaximum" : 95,
"label" : "Activity 1 - New name"
}
```
## Deleting a Grade Line by Its ID
To delete a grade line related to the context that originated an LTI® launch, you need to make a DELETE request to the `/api/lineitems/:lineItemID` endpoint of your LTIAAS' account subdomain.
**Tip:**
For a description of possible response statuses, check the [[DELETE] Line Item endpoint API Reference](/api/delete-api-lineitems-lineitemid).
**Info:**
Line item IDs are URLs, so their value needs to be URL encoded to be safely sent as a query parameter.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/lineitems/:lineItemID DELETE request
const lineItemId = 'https://lms.example.com/course/1/lineitems/1'
const urlSafeLineItemId = encodeURIComponent(lineItemId)
requests.delete(`https://your.ltiaas.com/api/lineitems/${urlSafeLineItemId}`, { headers })
```
## Checking Service Availability
The *Assignment and Grades* service might not be available for every LTI® launch context. Before attempting to manipulate grade lines you can [make a call to the ID Token endpoint](/guides/api/idtoken) and check the [services section](/guides/api/idtoken#assignmentandgrades) to know if the service is available.
You can know if the service is available based on the value of the `idtoken.services.assignmentAndGrades.available` boolean field:
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const idtoken = requests.get('https://your.ltiaas.com/api/idtoken', { headers })
const isServiceAvailable = idtoken['services']['assignmentAndGrades']['available']
if (isServiceAvailable) {
// Making /api/lineitems GET request
const response = requests.get(`https://your.ltiaas.com/api/lineitenms`, { headers })
const members = response['lineItems']
}
```
## Manipulating Grades
Source: https://ltiaas.com/guides/api/manipulating-grades
# Manipulating Grades
**Tip:**
Make sure you've read about [authenticating API requests](/guides/api/authentication) before proceeding to the guide below.
**Tip:**
Make sure you've read about [manipulating grade lines](/guides/api/manipulating-grade-lines) before proceeding to the guide below.
**Info:**
The Scores endpoint accepts both the [ltik based](/guides/api/authentication#ltik-based-authentication) and [service key based](/guides/api/authentication#service-key-based-authentication) API authentication methods.
The LTI® protocol allows you to manipulate grades in the LMS grade book through the *Assignment and Grades - Scores and Results* services. LTIAAS gives you access to these services through the `/api/lineitems/:lineItemID/scores` API endpoint.
## The Score Object
**Info:**
This guide focuses on the most common and/or relevant fields of the score object, a detailed description of available fields can be found in the IMS Score Service documentation:
Learning Tools Interoperability (LTI®) Assignment and Grade Services Specification, IMS Final Release Spec Version 2.0, 1EdTech Consortium, April 2019, 3.4 Score publish service, https://www.imsglobal.org/spec/lti-ags/v2p0/#score-publish-service.
The *score* is the object used when we want to update the status of a user in a grade line. This can be used not only to submit grades but also to provide feedback and update the status of the activity.
```json
{
"userId" : "1",
"activityProgress" : "Completed",
"gradingProgress": "FullyGraded",
"scoreGiven" : 95,
"scoreMaximum" : 100
}
```
#### userId [*required*]
The `userId` field is the identifier for the user to whom the score belongs.
**Caution:**
Some LMSes might restrict grade submissions based on the user role. It's recommended to only submit grades to users with a student role.
#### activityProgress [*required*]
The `activityProgress` field is an indicator of the user's status towards the activity's completion. The values allowed in this field are:
- `Initialized` - The user has not started the activity, or the activity has been reset for that student.
- `Started` - The activity associated with the line item has been started by the user to which the result relates.
- `InProgress` - The activity is being drafted and is available for comment.
- `Submitted` - The activity has been submitted at least once by the user but the user is still able make further submissions.
- `Completed` - The user has completed the activity associated with the line item.
#### gradingProgress [*required*]
The `gradingProgress` field is an indicator of the user's current grading process in the activity. The values allowed in this field are:
- `FullyGraded` - The grading process is completed; the score value, if any, represents the current Final Grade; the gradebook may display the grade to the learner.
- `Pending` - Final Grade is pending, but does not require manual intervention; if a Score value is present, it indicates the current value is partial and may be updated.
- `PendingManual` - Final Grade is pending, and it does require human intervention; if a Score value is present, it indicates the current value is partial and may be updated during the manual grading.
- `Failed` - The grading could not complete.
- `NotReady` - There is no grading process occurring; for example, the student has not yet made any submission.
#### scoreGiven
The `scoreGiven` field is the numeric grade that should be assigned to the user. This field is optional as the score can be used to provide feedback or update a task status without assigning a grade. If present, it MUST be positive number (including 0).
#### scoreMaximum
The `scoreMaximum` field is the maximum grade that can be assigned to the grade line to which the score belongs. **This field is required if `scoreGiven` is present**. If present, it MUST be a numeric non-null value, strictly greater than 0.
## Submitting Grades
**Tip:**
In the example bellow, we're hardcoding the line item ID (grade line ID), for information on how you would actually retrieve the correct line item ID for a context, please check the [Retrieving grade lines guide](/guides/api/manipulating-grade-lines#retrieving-grade-lines-for-a-context).
To submit a score to a grade line, you need to make a POST request to the `/api/lineitems/:lineItemID/scores` endpoint of your LTIAAS' account subdomain. The score will be submitted to the grade line (line item) specified in the request URL.
**Tip:**
For a description of all accepted fields, as well as possible response statuses, check the [[POST] Scores endpoint API Reference](/api/post-lineitem-scores).
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/lineitems/:lineItemID/scores POST request
const score = {
userId: '1',
activityProgress: 'Completed',
gradingProgress: 'FullyGraded',
scoreGiven: 95,
scoreMaximum: 100
}
const lineItemId = 'https://lms.example.com/course/1/lineitems/1'
const urlSafeLineItemId = encodeURIComponent(lineItemId)
requests.post(`https://your.ltiaas.com/api/lineitems/${urlSafeLineItemId}/scores`, score, { headers })
```
**Info:**
When submitting a score, the `userId`, `activityProgress` and `gradingProgress` fields are required.
A successful response will have a `204` status.
## The Result Object
**Info:**
This guide focuses on the most common and/or relevant fields of the result object, a detailed description of available fields can be found in the IMS Result Service documentation:
Learning Tools Interoperability (LTI®) Assignment and Grade Services Specification, IMS Final Release Spec Version 2.0, 1EdTech Consortium, April 2019, 3.9 Results Service, https://www.imsglobal.org/spec/lti-ags/v2p0/#result-service.
The *result* is the object returned when we retrieve the grades of a user from a grade line.
```json
{
"id": "https://lms.example.com/course/1/lineitems/1/results/1",
"scoreOf": "https://lms.example.com/course/1/lineitems/1",
"userId": "1",
"resultScore": 95,
"resultMaximum": 100,
}
```
#### id
The `id` field is the identifier for the result.
#### scoreOf
The `scoreOf` field is the identifier for the line item to which the result belongs.
#### userId
The `userId` field is the identifier for the user to whom the result belongs.
#### resultScore
The `resultScore` field is the numeric grade currently assigned to the user. This field may be empty.
#### resultMaximum
The `resultMaximum` field is the maximum grade that can be assigned to the grade line to which the result belongs.
## Retrieving Grades
**Tip:**
In the example bellow, we're hardcoding the line item ID (grade line ID), for information on how you would actually retrieve the correct line item ID for a context, please check the [Retrieving grade lines guide](/guides/api/manipulating-grade-lines#retrieving-grade-lines-for-a-context).
To retrieve results from a grade line you need to make a GET request to the `/api/lineitems/:lineItemID/scores` endpoint of your LTIAAS' account subdomain. The results will be retrieved from the grade line (line item) specified in the request URL.
**Tip:**
For a description of all accepted query parameters, as well as possible response bodies and statuses, check the [[GET] Line Items endpoint API Reference](/api/get-lineitem-scores).
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/lineitems/:lineItemID/scores GET request
const lineItemId = 'https://lms.example.com/course/1/lineitems/1'
const urlSafeLineItemId = encodeURIComponent(lineItemId)
const results = requests.get(`https://your.ltiaas.com/api/lineitems/${urlSafeLineItemId}/scores`, { headers })
```
A successful response will be an object with a `scores` field containing an array of result objects tied to that grade line.
```json
{
"scores":[
{
"id": "https://lms.example.com/course/1/lineitems/1/results/1",
"scoreOf":"https://lms.example.com/course/1/lineitems/1",
"userId": "1",
"resultScore": 95,
"resultMaximum": 100
},
{
"id":"https://lms.example.com/course/1/lineitems/1/results/2",
"scoreOf":"https://lms.example.com/course/1/lineitems/1",
"userId":"2",
"resultScore":90,
"resultMaximum":100
}
]
}
```
#### Response Pagination Fields
**Caution:**
This functionality might not be supported by every LMS.
The `next`, `prev`, `first` and `last` fields will only be present if there are more results to be retrieved from the grade line. You can read more about this in the [Handling pagination guide](/guides/api/pagination).
```json
{
"scores": [...],
"next": "https://lms.example.com/course/1/lineitems?page=3",
"prev": "https://lms.example.com/course/1/lineitems?page=1",
"first": "https://lms.example.com/course/1/lineitems?page=1",
"last": "https://lms.example.com/course/1/lineitems?page=4"
}
```
### Filtering Grades by the User ID
If you want to retrieve only the grades belonging to a specific user, you can populate the `userId` query parameter.
**Tip:**
The ID for a user can be retrieved from the `idtoken.user.id` field of the [ID Token](/guides/api/idtoken).
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const idtoken = requests.get('https://your.ltiaas.com/api/idtoken', { headers })
const userId = response['user']['id']
// Making /api/lineitems/:lineItemID/scores GET request
const lineItemId = 'https://lms.example.com/course/1/lineitems/1'
const urlSafeLineItemId = encodeURIComponent(lineItemId)
const results = requests.get(`https://your.ltiaas.com/api/lineitems/${urlSafeLineItemId}/scores?userId=${userId}`, { headers })
```
## Checking Service Availability
The *Assignment and Grades* service might not be available for every LTI® launch context. Before attempting to manipulate grades you can [make a call to the ID Token endpoint](/guides/api/idtoken) and check the [services section](/guides/api/idtoken#assignmentandgrades) to know if the service is available.
You can know if the service is available based on the value of the `idtoken.services.assignmentAndGrades.available` boolean field:
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const idtoken = requests.get('https://your.ltiaas.com/api/idtoken', { headers })
const isServiceAvailable = idtoken['services']['assignmentAndGrades']['available']
if (isServiceAvailable) {
// Making /api/lineitems/:lineItemID/scores GET request
const lineItemId = 'https://lms.example.com/course/1/lineitems/1'
const urlSafeLineItemId = encodeURIComponent(lineItemId)
const results = requests.get(`https://your.ltiaas.com/api/lineitems/${urlSafeLineItemId}/scores`, { headers })
}
```
## Generating OAuth2 Access Tokens
Source: https://ltiaas.com/guides/api/oauth
# Enhancing LTI with OAuth2
## What is OAuth2?
Some LMSes support the standard OAuth2 protocol for authenticating requests to their APIs. This allows LTI® tools to make API requests on behalf of a user, with the user's permission. These APIs often have way more features than the standard LTI protocol supports, like advanced grade manipulation and accessing content that is outside of the LTI scope. If these features are needed by your tool, complimenting LTI with OAuth2 is the way to go.
Here's a list of LMSes that support OAuth2 (that we are aware of):
- Canvas
- BrightSpace (D2L)
Check with the LMS manufacturer to see if they support OAuth2. If they do, then our service will work with that LMS.
## Typical OAuth2 Flow
1. Much like the LTI registration process, OAuth2 needs to be registered with both LTIAAS and the LMS.
2. Once registered, the LMS will provide a client ID and client secret, which you will enter in to the LTIAAS Portal.
3. After the client ID and client secret are configured, you simply redirect the user's browser to the LTIAAS-hosted OAuth start URL.
4. LTIAAS handles the whole OAuth handshake process, including redirecting the user to the LMS for authentication and obtaining the access token.
5. LTIAAS will redirect the browser to a URL that you specify in the LTIAAS Portal. That URL will contain a query parameter that you use to obtain the access token from LTIAAS.
6. Once you have the access token, you can use it to make API requests on behalf of the user, use the LMS's APIs.
## OAuth2 Setup
**Warning:**
Before you can set up OAuth2, you need to add this option to your plan. When setting up your account, make sure to select a plan that includes OAuth2 support, or upgrade your existing plan.
The first step is to register the OAuth2 configuration in both LTIAAS and the LMS. The exact steps for this will vary depending on the LMS. For this example, we'll show you how to do this with Canvas.
1. In the Canvas LMS, navigate to the Developer Keys section.

2. Fill out the form with whatever names and descriptions you want. The important part is the Redirect URI, which should be set to: `https://your.ltiaas.com/oauth/complete` where `your.ltiaas.com` is replaced with your LTIAAS account's subdomain. Some LMS admins will want to limit the scope of your API access, so ensure that they know which APIs you plan to use if they enable any _Scope Enforcement_ feature.

3. Once the key is created, make note of the client ID and client secret. In Canvas, the secret can be found by clicking on the `Show Key` button in the details column of the key that was just created. The client ID is the number shown above the button.

4. Enter in the client ID and client secret into the LTIAAS Portal along with the LMS's URLs. In the [LTIAAS Portal](https://portal.ltiaas.com), navigate to the `Registrations` page in the left sidebar and either add a new registration or edit an existing one.

1. Client ID: The client ID provided by the LMS.
2. Client Secret: The client secret provided by the LMS.
3. Login URL: The login endpoint URL for the LMS. (For Canvas, this is `https://[server-url]/login/oauth2/auth`)
4. Access Token URL: The token endpoint URL for the LMS. (For Canvas, this is `https://[server-url]/login/oauth2/token`)
5. Refresh Token URL: The refresh token endpoint URL for the LMS. (For Canvas, this is `https://[server-url]/login/oauth2/token`)
6. Scopes: The scopes that your application requires. (For Canvas, this might include `url:GET|/api/v1/courses/:course_id`). Each API listed in the Canvas docs will tell you what scope it requires.
Finally, if you are using OAuth without an LTI context, this is supported, but you will need to enter in 'dummy' data for the LTI related information in the LTIAAS registartion form.
## Generating an OAuth2 Access Token
**Tip:**
Make sure you've read about [authenticating API requests](/guides/api/authentication) before proceeding to the guide below.
**Info:**
The OAuth2 API endpoints only accept the [Bearer API Key authentication method](/guides/api/authentication#bearer-api-key-based-authentication).
To start the token generation process, you need to redirect the user's browser to the LTIAAS-hosted OAuth start URL. This could be done with a button on your website, for example. You must redirect tio the following URL: `https://your.ltiaas.com/oauth/start/{platformId}` where `your.ltiaas.com` is replaced with your LTIAAS account's subdomain, and `{platformId}` is the ID of the platform you want to connect to (found in the LTIAAS Portal registration).
```html
Connect With Canvas
```
The `/oauth/start/{platformId}` endpoint supports optionally sending a list of scopes to the LMS, otherwise the list of default scopes will be used. For example:
```html
Connect With Canvas
```
## Receiving OAuth2 Access Token Requests
After initiating the OAuth2 flow, the LMS will redirect the user back to the specified Redirect URI with an authorization code. Your application must handle this request and exchange the authorization code for an access token.
You must implement a server-side endpoint to handle the redirect and exchange the authorization code for an access token.
1. In the [LTIAAS Portal](https://portal.ltiaas.com), navigate to the `API Settings` page in the left sidebar.
2. In the `OAuth Redirect URL` field, enter the URL of the endpoint you created to handle the redirect from the LMS. This URL must be accessible by the user's browser.
3. Save and deploy the settings.
Your provisioned URL will receive two query parameters:
1. `authorizationCode`: The LTIAAS-specific code used to retrieve an access token for the current user.
2. `platformId`: The ID of the platform that this user has connected from.
Use these two variables to request an access token from the LTIAAS API.
**Tip:**
Always make Authenticated API requests from your server, never from client-side code. This is to ensure that your API key remains secret.
The API endpoint for retrieving an accessToken is `/api/oauth/access-token/:PlatformId`.
```javascript
// Data we will send
const receivedAuthorizationCode = 'your_received_authorization_code'
const platformId = 'your_platform_id'
// Building Bearer token based API authentication header
const authorizationHeader = `Bearer ${API_KEY}`
const headers = { Authorization: authorizationHeader }
// Making /api/oauth/access-token/:Id POST request
const authData = {
authorizationCode: receivedAuthorizationCode
}
const response = requests.post(`https://your.ltiaas.com/api/oauth/access-token/${platformId}`, authData, { headers })
const accessToken = response['accessToken'];
const refreshToken = response['refreshToken'];
const expiresIn = response['expiresIn']; //number of seconds
```
This request will return an access token that can be used to authenticate API requests on behalf of the user. The response format is:
```js
{
tokenType: string, // typically "Bearer"
accessToken: string,
expiresIn: string,
refreshToken: string,
...: // All other parameters from the raw access token sent by LMS
}
```
## Managing Access Token Expiration
Access tokens are short-lived and will expire after a certain period (depending on the LMS). To ensure uninterrupted access to the API, you should implement a mechanism to refresh the access token using the refresh token. In some cases, the tokens expire in as little as 1 hour.
1. When you receive the initial access token response, store the `refreshToken` and `expiresIn` securely.
2. Before making an API request, check if the access token is expired. If it is, use the refresh token to obtain a new access token.
The API endpoint for refreshing the access token is `/api/oauth/access-token/:PlatformId/refresh`.
Here's an example of how to refresh the access token:
```javascript
// Data we will send
const refreshToken = 'your_stored_refresh_token'
const platformId = 'your_platform_id'
// Building Bearer token based API authentication header
const authorizationHeader = `Bearer ${API_KEY}`
const headers = { Authorization: authorizationHeader }
// Making /api/oauth/access-token/:Id/refresh POST request
const authData = {
refreshToken: refreshToken
}
const response = requests.post(`https://your.ltiaas.com/api/oauth/access-token/${platformId}/refresh`, authData, { headers })
const newAccessToken = response['accessToken'];
const expiresIn = response['expiresIn'];
// the response is the same as the accessToken API, with the exception that the accessToken itself is not returned
```
By implementing this token management strategy, you can ensure that your application maintains access to the API without requiring the user to re-authenticate frequently.
## Pricing
Generating OAuth2 access tokens is metered separately from LTI® usage, because
it is billed per token rather than per active user.
| Monthly tokens generated | Price per token |
| -- | -- |
| Tokens 1-50 | free |
| Tokens 51-10,000 | $0.001 |
| Tokens 10,001+ | $0.0007 |
Every call to the token generation endpoints that creates a new token is
counted, including a request for a user who already has an active token. Check
the expiry you stored and only ask for a new token when the old one has run
out. See the [pricing page](/pricing) for LTI® usage rates.
## Handling Pagination
Source: https://ltiaas.com/guides/api/pagination
# Handling Pagination
**Caution:**
This functionality might not be supported by every LMS. Use with caution.
Some of the LTI® Advantage services provide the LMSes and users with a standardized way of handling a large ammount of results through pagination. LTIAAS allows you to handle pagination easily through special fields in API requests and responses.
## The Pagination Fields
If the results of a certain service API request are paginated, the response object will likely contain one or more of the following fields:
- `next` - The URL of the next page of results;
- `prev` - The URL of the previous page of results;
- `first` - The URL of the first page of results;
- `last` - The URL of the last page of results.
**Tip:**
Not every endpoint will return all of these fields. We detail expected fields in the guides for each specific service.
## Utilizing Pagination Fields
After retrieving one of the pagination fields from the API response, you can then use that to perform another API call fetching that specific page of results.
Every endpoint that supports pagination will accept a `url` query parameter, which you can use to specify the page you want to fetch.
**Info:**
The value of the pagination fields need to be URL encoded to be safely sent as a query parameter.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/memberships GET request
const response = requests.get(`https://your.ltiaas.com/api/memberships`, { headers })
// Retrieving next pagination field
const nextPage = response['next']
// Making /api/memberships GET request for the next page
const url = encodeURIComponent(nextPage)
const nextPageResponse = requests.get(`https://your.ltiaas.com/api/memberships?url=${url}`, { headers })
// Retrieving next page members
const members = nextPageResponse['members']
```
## Limiting Results
Every endpoint that supports pagination will accept a `limit` query parameter, which you can use to specify the maximum number of results you want to fetch.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/memberships GET request
const limit = 10
const response = requests.get(`https://your.ltiaas.com/api/memberships?limit=${limit}`, { headers })
// Retrieving next page members
const members = response['members'] // Maximum of 10 results
```
**Info:**
Some LMSes will have a default number of maximum results per page, which you can override by specifying the `limit` query parameter.
## Retrieving Course Roster
Source: https://ltiaas.com/guides/api/roster
# Retrieving Course Roster
**Tip:**
Make sure you've read about [authenticating API requests](/guides/api/authentication) before proceeding to the guide below.
**Info:**
The Memberships endpoint accepts both the [ltik based](/guides/api/authentication#ltik-based-authentication) and [service key based](/guides/api/authentication#service-key-based-authentication) API authentication methods.
The LTI® protocol allows you to retrieve roster information for an LMS context through the *Names and Roles Provisioning* service. Users that are part of an LMS context are called *members*. LTIAAS gives you access to this service through the `/api/memberships` API endpoint.
## The Membership Object
**Info:**
This guide focuses on the most common and/or relevant fields of the membership object, a detailed description of available fields can be found in the IMS Context Membership documentation:
Learning Tools Interoperability Names and Role Provisioning Services, 1EdTech Final Release Version 2.0, 1EdTech Consortium, April 2019, 2. Context Membership, https://www.imsglobal.org/spec/lti-nrps/v2p0#context-membership
The *membership* is the LTI® representation of a user in a certain LMS context. Each membership contains information about the user and their status and role within that context.
```json
{
"userId": "2",
"email": "user@email.com",
"name": "John Doe",
"roles": [...],
"status": "Active"
}
```
**Info:**
Fields containing personal information like `name` and `email` might not be present in the memberships information. The LMS can choose to omit this information based on their privacy rules. The only fields guaranteed to be present in this section are `userId` and `roles`.
#### userId
The `userId` field contains the user's identifier inside of the LMS. This ID will probably not be unique between LMSes, so it's not recommended to be used alone as a primary identifier. We go into more detail on this topic in the [Performing SSO guide](/guides/api/idtoken#performing-sso).
#### email
The `email` field contains the user's registered email inside of the LMS.
#### name
The `name` field contains the user's registered full name.
#### roles
The `roles` field contains a list of the user's roles within this specific launch context.
**Tip:**
A list of all possible roles can be found in the IMS Role Vocabulary:
Learning Tools Interoperability Core Specification, IMS Final Release Spec Version 1.3, 1EdTech Consortium, April 2019, A.2 Role vocabularies, https://www.imsglobal.org/node/162741#role-vocabularies
#### status
The `status` field contains the user's current status within this specific launch context. The value of this field can either be `Active` or `Inactive`.
## Calling the Memberships Endpoint
To retrieve the memberships related to the context that originated an LTI® launch, you need to make a GET request to the `/api/memberships` endpoint of your LTIAAS' account subdomain.
**Tip:**
For a description of all accepted query parameters, as well as possible response bodies and statuses, check the [[GET] Memberships endpoint API Reference](/api/get-memberships).
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/memberships GET request
const response = requests.get('https://your.ltiaas.com/api/memberships', { headers })
const members = response['members']
```
A successful response will be an object with a `members` field containing an array of membership objects tied to that LTI® context.
````json
{
"id": "https://lms.example.com/course/1/memberships?page=1",
"context": {...},
"members": [
{
"userId": "2",
"email": "user@email.com",
"name": "John Doe",
"roles": ["Learner"],
"status": "Active"
},
{
"userId": "3",
"email": "another_user@email.com",
"name": "Bob Doe",
"roles": ["Administrator", "Instructor"],
"status": "Active"
}
]
}
````
The response will also contain an `id` field, containing the ID for the current memberships page. And a `context` object, containing information about the LTI® context that originated the launch.
**Tip:**
You can find more information about the `context` object in the [ID Token context field documentation](/guides/api/idtoken#context) as the object here is the same as the one present in the ID Token.
#### Response Pagination Field
**Caution:**
This functionality might not be supported by every LMS.
The `next` field will only be present if there are more memberships to be retrieved from the context. You can read more about this in the [Handling pagination guide](/guides/api/pagination).
```json
{
"id": "https://lms.example.com/course/1/memberships?page=1",
"context": {...},
"members": [...],
"next": "https://lms.example.com/course/1/memberships?page=2"
}
```
### Filtering Results by User Role
**Caution:**
This functionality might not be supported by every LMS.
You can filter membership results by a specific role by sending the `?role=` query parameter when calling the `/api/memberships` endpoint.
**Tip:**
A list of all possible roles can be found in the IMS Role Vocabulary:
Learning Tools Interoperability Core Specification, IMS Final Release Spec Version 1.3, 1EdTech Consortium, April 2019, A.2 Role vocabularies, https://www.imsglobal.org/node/162741#role-vocabularies
**Info:**
Most roles in the vocabulary are represented by URLs, so their value needs to be URL encoded to be safely sent as a query parameter.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/memberships GET request
const role = encodeURIComponent('http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor')
const response = requests.get(`https://your.ltiaas.com/api/memberships?role=${role}`, { headers })
const members = response['members']
```
### Filtering Results by Activity Access
**Caution:**
This functionality might not be supported by every LMS.
You can filter membership results by users that have access to an activity in the LMS. You can achieve this by sending the `?resourceLinkId=` query parameter when calling the `/api/memberships` endpoint.
**Tip:**
The resource link ID for an activity can be retrieved from the `idtoken.launch.resourceLink.id` field of the [ID Token](/guides/api/idtoken) generated by launching the LTI® tool from that activity.
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const idtoken = requests.get('https://your.ltiaas.com/api/idtoken', { headers })
const resourceLinkId = idtoken['launch']['resourceLink']['id']
// Making /api/memberships GET request
const response = requests.get(`https://your.ltiaas.com/api/memberships?resourceLinkId=${resourceLinkId}`, { headers })
const members = response['members']
```
## Checking Service Availability
The *Names and Roles Provisioning* service might not be available for every LTI® launch context. Before attempting to retrieve memberships you can [make a call to the ID Token endpoint](/guides/api/idtoken) and check the [services section](/guides/api/idtoken#namesandroles) to know if the service is available.
You can know if the service is available based on the value of the `idtoken.services.namesAndRoles.available` boolean field:
```javascript
// Building Ltik based API authentication header
const authorizationHeader = `LTIK-AUTH-V2 ${API_KEY}:${LTIK}`
const headers = { Authorization: authorizationHeader }
// Making /api/idtoken GET request
const idtoken = requests.get('https://your.ltiaas.com/api/idtoken', { headers })
const isServiceAvailable = idtoken['services']['namesAndRoles']['available']
if (isServiceAvailable) {
// Making /api/memberships GET request
const response = requests.get(`https://your.ltiaas.com/api/memberships`, { headers })
const members = response['members']
}
```
# Customer portal
## Using the LTIAAS Portal
Source: https://ltiaas.com/guides/portal/introduction
# Using the LTIAAS Portal
## Section Summary
This section will teach you some of what you can do with the LTIAAS Portal.
## Deleting Your Account
Source: https://ltiaas.com/guides/portal/account-delete
This guide will show you how to delete an account.
**Danger:**
When an account is deleted, its service is terminated immediately.
## Step 1. Log in to the LTIAAS Portal
All LTIAAS services are managed via the customer portal. To access the portal, please log in using either your email address or Google account. If an account doesn't exist, it will be created for you.
## Step 2. Select Your Account
After logging in, you will be presented with a list of your accounts. Click the `Account Overview` button for the account you want to manage.
## Step 3. Find the Billing Page and Delete Account Button
On the left sidebar, click `Billing`, then click the `Delete Account` button.
## Step 4. Delete Your Account
You will be asked to confirm the account deletion. Click the `Yes, I want to delete the account` button to confirm the deletion.
**Tip:**
When an account is deleted, any subdomain that was provisioned for that account will remain locked. If you want to use a subdomain from one of your deleted accounts, please [contact support](/contact-us)
## Upgrading Your Account
Source: https://ltiaas.com/guides/portal/account-upgrades
You probably created your first account with the LTIAAS Free Tier. But now, you're ready to go to production and need to upgrade your account so that you can start receiving substantial traffic. This guide will show you how to upgrade your account while keeping the same settings with zero service interruption.
## Step 1. Log in to the LTIAAS Portal
All LTIAAS services are managed via the customer portal. To access the portal, please log in using either your email address or Google account. If an account doesn't exist, it will be created for you.
## Step 2. Select Your Account
After logging in, you will be presented with a list of your accounts. Click the `Account Overview` button for the account you want to manage.
## Step 3. Find the Billing Page and Change Plan Button
On the left sidebar, click `Billing`, then click the `Change Plan` button.
## Step 4. Change Your Plan
Once you have changed your plan options, we ask that you enter in your credit card to activate the subscription. If a credit card is not an available option for payment, please [reach out](/contact-us) to us. We can accommodate most payment methods with manual intervention.
When the form is complete. Click the `Subscribe` button.
If you don't click the `Subscribe` button, your changes will not be committed.
## Managing Account Users
Source: https://ltiaas.com/guides/portal/account-users
**Tip:**
To manage user access to your account, your account must be on a tier that supports the 'Multiple Admins/Roles' feature. To upgrade your account please see [Upgrading Your Account](./account-upgrades.mdx).
## Finding the User Management Page
### Step 1. Log in to the LTIAAS Portal
All LTIAAS services are managed via the customer portal. To access the portal, please log in using either your email address or Google account. If an account doesn't exist, it will be created for you.
### Step 2. Select Your Account
After logging in, you will be presented with a list of your accounts. Click the `Account Overview` button for the account you want to manage.
### Step 3. Find the Manage Users Page
Click on the `Manage Users` link on the left sidebar.
## Adding a User
Once here, you can invite new users to the account by clicking the `+ Add User` button.
This will open a form where you can enter the email address of the user you want to add to the account and the role you want them to have.
When adding a user, there are several roles that you can assign to that user.
| Role | Description |
| -- | -- |
| Owner | This is the user that created the account. This is the only account that has permission to change billing information, and delete the account. The owner cannot be changed once the account is created. If you need to change the owner, please [contact support](/contact-us). To change an account owner, you must show proof of domain and company ownership. |
| Admin | This role has the most access to the account aside from the owner, including the ability to view invoices, platform registrations, and edit settings. |
| Registrar | This role only has access to register/edit/delete platform registrations. |
| Financier | This role only has access to view (and pay) invoices. |
| User | This is a view-only role. It is mostly used to keep a user on the account list but remove their access to change the account. |
## Changing an Existing User's Role
If you are logged in as an account Owner or Admin, you can change the role of other account users. Simply click on the `Change Role` button next to the user you want to edit.
A form will be displayed where you can change the user's role. Select a new role and click `Submit`.
## Removing a User's Access
If you are logged in as an account Owner or Admin, you can remove users from the account. Simply click on the `Change Role` button next to the user you want to edit.
Then, in the form that is displayed, change the roll to be `-- Remove Access --`. The submit button will change colors to red indicating that the user will be deleted. Click `Submit` to delete the user.
## Custom Domain Setup
Source: https://ltiaas.com/guides/portal/custom-domain-setup
## Summary
With LTIAAS, it is possible to use your own custom domain/subdomain. Instead of having an LTIAAS subdomain for your API, like `mycompany.ltiaas.com`, you can enable the _Custom Domain_ option to make LTIAAS accessible through a domain you own like `lti.mycompany.com`.
This page outlines the steps needed to enable this feature.
1. [Choose The Custom Domain Option During Account Setup](#step-1-choose-the-custom-domain-option-during-account-setup)
2. [Enter Your Chosen Domain/Subdomain into the LTIAAS API Settings](#step-2-enter-your-chosen-domainsubdomain-into-the-ltiaas-api-settings)
3. [Verify That You Own The Domain](#step-3-verify-that-you-own-the-domain)
4. [Point Your Subdomain To LTIAAS Servers](#step-4-point-your-subdomain-to-the-ltiaas-servers)
5. [Verify The New DNS Settings Within LTIAAS](#step-5-verify-the-new-dns-setting-within-ltiaas)
5. [Deploy](#step-6-deploy)
## Step 1. Choose the Custom Domain Option During Account Setup
In order to enable this feature, it must be selected when [configuring your plan](../starting/account-setup.mdx#step-3-create-your-plan).
Some of our tiers have an option called **Custom Domain**. You must select one of those tiers to enable this option.
**Tip:**
If you want to add a custom Domain to an already existing account, you can do so as long as the account doesn't have an already provisioned LTIAAS subdomain. This can be done by going to _Billing -> Change Subscription Plan_. If a subdomain has already been provisioned, **[contact us](mailto:support@ltiaas.com)** and it can be resolved by LTIAAS staff.
## Step 2. Enter Your Chosen Domain/Subdomain into the LTIAAS API Settings
When going through the [account setup](../starting/account-setup.mdx#step-4-set-your-api-settings) process, enter your chosen domain/subdomain name in the `Subdomain` field and save the settings.
## Step 3. Verify That You Own the Domain
After selecting your domain and saving the settings, LTIAAS will provision the validation of the domain. This may take a minute or two, so feel free to click the `Check Status` button to refresh the screen.
You will then be asked to add a DNS entry to validate that you own the domain.
Copy the values and add a CNAME DNS entry into your DNS configuration. After that is done, click the `Check Status` button to see if LTIAAS has validated the domain. This could take up to 1 hour to complete.
## Step 4. Point Your Subdomain to the LTIAAS Servers
Once your domain has been validated, LTIAAS will share the endpoint for your custom API gateway.
1. Login to your DNS or hosting provider and navigate to the DNS section. This may be called 'DNS', 'Networking', or 'Zone Editor' depending on your provider.
2. Add a new `CNAME` (or ALIAS if you DNS provider supports it) record that points your subdomain to the LTIAAS API gateway domain name that is provided as shown above. In the screenshot below, a record is created for lti.mycompany.com, but there is no restriction on the subdomain name.
3. Wait up to 24 hours for the new DNS changes to propagate through the internet. We typically see the changes take effect in less than an hour, but in some cases it can take longer.
## Step 5. Verify the New DNS Setting Within LTIAAS
In the [LTIAAS portal](https://portal.ltiaas.com), on the `API settings` page, LTIAAS will attempt to do a DNS lookup to make sure the `CNAME` record you created is pointing to LTIAAS correctly. If it is, you will be able to [deploy your API](../starting/account-setup.mdx#step-5-deploy). If it is not, you will continue to see the message asking you to create the `CNAME` record.
Clicking the `Check Status` button or refreshing the page, will re-check the DNS settings.
## Step 6. Deploy
Once the DNS is correct, you will be able to [deploy your API](../starting/account-setup.mdx#step-5-deploy) by clicking the Deploy button on the API settings page.
# Troubleshooting
## Troubleshooting
Source: https://ltiaas.com/guides/troubleshooting/introduction
# Troubleshooting
## Section Summary
This section will give you tips on how to deal with some of the issues you might run into while using LTIAAS.
## Common Problems and Solutions
Source: https://ltiaas.com/guides/troubleshooting/common_problems
This page contains some handy tips when you run into issues using LTIAAS.
## Accessing a Test LMS
Many of our customers want to test LTIAAS before releasing it to their customers, but find Canvas and Blackboard LMS pricing prohibitive.
- One option is to test on the public [Moodle sandbox](https://sandbox.moodledemo.net/). It allows LTI® tools to be installed, but be warned that it gets reset every 24 hours.
- Brightspace now has a [developer sandbox](https://devcop.brightspace.com/d2l/login) that appears to have recently enabled support LTI® 1.3.
- LTIAAS can host private Moodle servers that can be made available on a monthly bases to our customers for testing. Feel free to [contact us](https://ltiaas.com/contact-us) for more information.
## Bitnami Canvas Caution
At LTIAAS, we have had many customers come to us telling us that they aren't able to get LTIAAS to work with Canvas. This, so far, has always been the case with using the [Canvas LMS Packaged By Bitnami on AWS](https://aws.amazon.com/marketplace/pp/prodview-fvjvjp65tfujm).
The image provided by Bitnami doesn't have the provisions required to use LTI®. There are some modifications that need to be made to get the image working. [This guide](https://community.canvaslms.com/t5/Canvas-Developers-Group/Canvas-LTI®-1-3-Error-Unknown-Key-Type/m-p/390285/highlight/true#M6345) explains more about what changes are needed to get Canvas working.
If Canvas testing is required and getting the Bitnami image working is not bearing fruit, we recommend reaching out to the Canvas partner team to [register as a partner](https://www.instructure.com/become-integration-services-partner). Using this program, you will get access to your own Canvas test instance hosted by Instructure. It is actually about the same annual price as running a Bitnami instance on AWS.
## Debugging a Failed Launch
After installing your LTIAAS tool into an LMS, sometimes it just doesn't work on the first try. Here are a couple of things to check if things go wrong during the LTI® launch process.
1. Check for any misspelling is the Launch URL, It should be either `https://your.ltiaas.com` or that same URL with query parameters added to it: `https://your.ltiaas.com?some_key=some_value`.
2. Make sure the [registration is activated](../lms/others.mdx#step-2-register-the-lms-into-the-ltiaas-tool) in the [LTIAAS Portal](https://portal.ltiaas.com).
### Getting a Detailed Log of the Launch Process
Sometimes, it can be helpful to get a detailed log of the LTI® launch process in your browser. You are always welcome to generate and send us a HAR file of the LTI® launch process by:
1. Open Chrome developer tools.
2. Navigate to the "Network" tab.
3. Click ion the "Doc" filter.
4. Refresh the page so that a new LTI® launch happens.
5. Feel free to inspect the traffic that Chrome collects to gain some understanding as to why the LTI® launch is failing.
5. Click on the download button in the network tab's toolbar to save the HAR file.
## Prerequisites
Source: https://ltiaas.com/guides/troubleshooting/prerequisites
This page outlines some of the LMS prerequisites needed before LTIAAS can be tested and used with an LMS.
## 1. A publicly accessible domain name
LTIAAS uses the internet to send and receive messages to the LMS. This requires LMSes to have publicly accessible domain names. If you are hosting an LMS on a personal machine (i.e. your laptop), that is not accessible from the internet, LTIAAS will not work.
The best option to access an LMS, even for testing purposes, is to host one properly on the internet. If this is not an option, you maybe be able to use the [ngrok](https://ngrok.com/) service to expose a private machine to the internet for testing purposes.
## 2. SSL
The LTI® protocol relies in HTTPS for security. LTIAAS will refuse all insecure connections to LMSes. If an LMS doens't support HTTPS, if an SSL certificate has expired, or if an SSL certificate is self-signed, the connection will be denied by LTIAAS.
[Let's Encrypt](https://letsencrypt.org/) is a good resource to generate trusted SSL certificates for free.
## Troubleshooting Error Messages
Source: https://ltiaas.com/guides/troubleshooting/troubleshooting_error_messages
A reference for the errors returned during an LTI® launch or by the LTIAAS API. Find the code or message you received below.
**Tip:**
It's not necessary to decode JWTs, inspect the OIDC handshake, or verify signatures to resolve these errors. LTIAAS performs the protocol work. A failure at the protocol level is normally caused by a configuration value — a URL, a client ID, a service permission, or a server clock — and is corrected in the LMS or in the [LTIAAS Portal](https://portal.ltiaas.com).
## Error Code Index
| Code | Cause | Fixed in |
|---|---|---|
| [`DUPLICATE_NONCE`](#duplicate_nonce) | Launch submitted twice | Retry |
| [`ERROR_RETRIEVING_KEYSET`](#error_retrieving_keyset) | Key set unreachable | LMS |
| [`IDTOKEN_NOT_FOUND`](#idtoken_not_found) | Launch record expired | Your app |
| [`INACTIVE_SERVICE`](#inactive_service) | Service not on your account | Portal |
| [`INVALID_BEARER_AUTHORIZATION_HEADER`](#authorization-header-codes) | Bad header or API key | Your app |
| [`INVALID_LINE_ITEM_ID_FOR_CONTEXT`](#invalid_line_item_id_for_context) | Line item from another context | Your app |
| [`INVALID_LTIK_AUTH_AUTHORIZATION_HEADER`](#authorization-header-codes) | Bad header, API key, or expired `ltik` | Your app |
| [`INVALID_REDIRECT_URI_PARAMETER`](#invalid_redirect_uri_parameter) | Launch URL not registered | LMS |
| [`INVALID_SERVICE_AUTH_AUTHORIZATION_HEADER`](#authorization-header-codes) | Bad header, API key, or service key | Your app |
| [`INVALID_SERVICE_ENDPOINT_URL`](#invalid_service_endpoint_url) | Malformed endpoint from the LMS | LMS |
| [`KEYSET_INCORRECTLY_FORMATTED`](#keyset_incorrectly_formatted) | Key set is not a valid JWKS | LMS |
| [`LINE_ITEM_ID_INCORRECTLY_FORMATTED`](#line_item_id_incorrectly_formatted) | Malformed line item ID | Your app |
| [`MONTHLY_TRIAL_ACTIVE_USERS_QUOTA_REACHED`](#trial-quota-codes) | Trial user limit | Portal |
| [`MONTHLY_TRIAL_LAUNCH_QUOTA_REACHED`](#trial-quota-codes) | Trial launch limit | Portal |
| [`MONTHLY_TRIAL_OAUTH_ACCESS_TOKEN_QUOTA_REACHED`](#trial-quota-codes) | Trial token limit | Portal |
| [`PUBLIC_KEY_NOT_FOUND_IN_KEYSET`](#public_key_not_found_in_keyset) | Signing key not published | LMS |
| [`SERVICE_NOT_AVAILABLE_FOR_CONTEXT`](#service_not_available_for_context) | Service not granted for this launch | LMS |
| [`UNREGISTERED_OR_INACTIVE_PLATFORM`](#unregistered_or_inactive_platform) | No matching registration | Portal |
Errors without a code are listed under [Messages Without an Error Code](#messages-without-an-error-code).
## How to Read an Error Response
Errors returned by LTIAAS are JSON objects containing `status`, `error` and `details`. A response that is not JSON, or that lacks these fields, did not come from LTIAAS.
The `details` object takes one of three forms.
| Form | Identified by | Meaning |
|---|---|---|
| Validation error | `details.errors` array | A request parameter was missing or invalid |
| Service error | `details.message` code | See the code index above |
| Forwarded LMS error | `details.externalError` | The request reached the LMS and the LMS rejected it |
A service error also carries `details.description`, which distinguishes causes that share a code. Some carry `service`, `keysetUrl`, `providedUrl` or `kid`.
A forwarded LMS error carries `externalUrl` (the endpoint called), `bodySent` (what LTIAAS sent) and `bodyReceived` (what the LMS returned):
```json
{
"status": 422,
"error": "Unprocessable Entity",
"details": {
"message": "HTTP Request error: Request failed with status code 422",
"externalUrl": "https://lms.example.com/api/lti/courses/1/line_items",
"bodySent": { },
"bodyReceived": { },
"externalError": true
}
}
```
**Tip:**
`bodySent` records the exact payload sent to the LMS. When an LMS reports a required field as absent, this field shows whether it was sent, which is the information the LMS support team needs.
## Error Codes
### DUPLICATE_NONCE
`400 Bad Request`
**Cause.** Each launch carries a single-use value that cannot be replayed. The same launch was submitted twice, usually by a double click, a page refresh, or an iframe loading twice.
**Fix.** Perform a new launch. If it occurs on every launch, the LMS is repeating requests and its administrator should investigate.
### ERROR_RETRIEVING_KEYSET
`400 Bad Request` · `details.keysetUrl`
**Cause.** LTIAAS could not retrieve the key set used to verify messages from the LMS.
**Fix.** Open `keysetUrl` in a browser. If it does not return JSON containing a `keys` array, the URL in the registration is wrong. If it loads in a browser but not for LTIAAS, the cause is usually an incomplete TLS certificate chain: the intermediate certificate must be bundled with the server certificate on the host serving the key set.
### IDTOKEN_NOT_FOUND
`401 Unauthorized`
**Cause.** The launch referenced by the token is no longer stored. Launch records are retained for approximately 25 hours.
**Fix.** Use a `serviceKey` for operations that run outside a live launch. See [Accessing the API asynchronously](../api/async.mdx).
### INACTIVE_SERVICE
`403 Forbidden` · `details.service`
**Cause.** The service is not enabled on your LTIAAS account. `service` is one of `Deep Linking`, `Names and Roles`, `Assignment and grades`, `Dynamic Registration` or `OAuth`.
**Fix.** [Add the service to your account](../portal/account-upgrades.mdx) in the Portal.
**Note:**
This is distinct from [`SERVICE_NOT_AVAILABLE_FOR_CONTEXT`](#service_not_available_for_context), which means the service is enabled on your account but was not granted by the LMS.
### INVALID_LINE_ITEM_ID_FOR_CONTEXT
`400 Bad Request`
**Cause.** The line item ID is well formed but belongs to a different context than the launch used to authenticate the request.
**Fix.** Retrieve line item IDs for the current context rather than caching them globally. See [Manipulating grade lines](../api/manipulating-grade-lines.mdx).
### INVALID_REDIRECT_URI_PARAMETER
`400 Bad Request`
**Cause.** The URL being launched is not present in the tool's registered redirect URLs. The LMS may report this as *"Redirect Uri must match one of the registered Redirect URL values"*.
**Fix.** The values must match as exact strings; a trailing slash present in one and absent in the other is sufficient to fail. Confirm the Target Link URI is correct and that the identical value appears in the redirect URL list.
### INVALID_SERVICE_ENDPOINT_URL
`400 Bad Request` · `details.providedUrl`, `details.service`
**Cause.** The LMS supplied a service endpoint that is not a valid URL.
**Fix.** This is malformed data from the LMS. Report `providedUrl` and `service` to the LMS support team.
### KEYSET_INCORRECTLY_FORMATTED
`400 Bad Request` · `details.keysetUrl`
**Cause.** The key set was retrieved but is not a valid JSON Web Key Set.
**Fix.** Confirm that `keysetUrl` returns a JSON object containing a `keys` array. If it returns HTML, an error page, or a single key, the URL in the registration is wrong.
### LINE_ITEM_ID_INCORRECTLY_FORMATTED
`400 Bad Request`
**Cause.** The line item ID sent is not in the expected format.
**Fix.** Use IDs returned by the API rather than constructing them. See [Manipulating grade lines](../api/manipulating-grade-lines.mdx).
### PUBLIC_KEY_NOT_FOUND_IN_KEYSET
`400 Bad Request` · `details.keysetUrl`, `details.kid`
**Cause.** The key set was read but does not contain the key the LMS signed with. `kid` identifies the key that was looked for.
**Fix.** The LMS is signing with a key it has not published. Ask the LMS administrator to confirm the key set is current, then perform a new launch.
### SERVICE_NOT_AVAILABLE_FOR_CONTEXT
`403 Forbidden` · `details.service`
**Cause.** The service is enabled on your account, but the LMS did not make it available for this launch. LTI® allows a platform to grant services per course or per placement, so the same tool can have a service in one course and not another.
This is also returned when the launch granted access to a single line item but not to the line item container, in which case scores can be submitted but line items cannot be listed or created.
**Fix.** The LMS administrator must enable the service for that course or tool placement. No change on the LTIAAS side will grant it.
**Caution:**
A permission granted in the LMS applies only to subsequent launches. After it is changed, perform a new launch and regenerate any stored `serviceKey`, as the existing one was issued under the previous permissions.
### UNREGISTERED_OR_INACTIVE_PLATFORM
`400 Bad Request`
**Cause.** The LMS could not be matched to a platform registration. An LMS identifies itself with its `url` and `clientId`, and both must match the registration exactly.
**Fix.** Check, in order:
1. A trailing slash present in the `url` in one system and absent in the other.
2. A space or hidden character in the `clientId`. Check the value in an editor that displays invisible characters.
3. A registration that exists but has not been activated. Activate it in the [LTIAAS Portal](https://portal.ltiaas.com).
4. On Canvas, an incorrect `url`. Canvas sends a different issuer than expected — see the [Canvas registration guide](../lms/canvas.mdx).
### Authorization Header Codes
`401 Unauthorized`
Three codes cover the authentication methods, and each is returned with more than one description. Read `details.description` to identify the cause.
| Code | Description | Cause |
|---|---|---|
| `INVALID_LTIK_AUTH_AUTHORIZATION_HEADER` | `Invalid API Key for account.` | The API key is wrong |
| `INVALID_LTIK_AUTH_AUTHORIZATION_HEADER` | `Invalid or expired ltik.` | The `ltik` is wrong or expired |
| `INVALID_SERVICE_AUTH_AUTHORIZATION_HEADER` | `Invalid service key.` | The service key is wrong |
| `INVALID_BEARER_AUTHORIZATION_HEADER` | `Authorization header doesn't match…` | The header format is wrong |
**Fix.** A description referring to the accepted format means the header itself is malformed rather than the credentials being wrong. See [Authenticating API requests](../api/authentication.mdx) for the accepted formats. A common cause is sending the API key alone to an endpoint that requires both the API key and the `ltik`.
**Expired `ltik`.** The `ltik` expires **24 hours** after the launch that generated it, so a request made after that window fails even when unchanged. For operations outside a live launch, such as scheduled grade submission, use a `serviceKey`, which does not expire. See [Accessing the API asynchronously](../api/async.mdx).
### Trial Quota Codes
`403 Forbidden` · `details.description`
**Cause.** A free trial account reached a monthly limit: `MONTHLY_TRIAL_LAUNCH_QUOTA_REACHED`, `MONTHLY_TRIAL_ACTIVE_USERS_QUOTA_REACHED` or `MONTHLY_TRIAL_OAUTH_ACCESS_TOKEN_QUOTA_REACHED`.
**Fix.** [Upgrade the account](../portal/account-upgrades.mdx).
### Validation Errors
`400 Bad Request` · `details.errors`
**Cause.** A request parameter was missing or invalid. Every failing field is listed:
```json
{
"status": 400,
"error": "Bad Request",
"details": {
"errors": [
{ "field": "scoreMaximum", "message": "Missing 'scoreMaximum' parameter." }
]
}
}
```
**Fix.** Compare the request against the [API reference](/api/ltiaas) and correct the listed fields.
## Messages Without an Error Code
| Message | Cause | Fixed in |
|---|---|---|
| [`"message": "Forbidden"`](#a-bare-forbidden-message) | Blocked by the web application firewall | LTIAAS support |
| [`lti_message_hint` is missing](#a-missing-lti_message_hint) | Wrong access token endpoint in the registration | LMS |
| [The token can't be used before _DATE_](#the-token-cant-be-used-before-date) | The LMS server clock has drifted | LMS |
| [401 during dynamic registration](#a-401-during-dynamic-registration) | The LMS could not retrieve its registration token | LMS |
| [Authlogic validation error](#authlogic-validation-error-you-did-not-provide-any-details-for-authentication) | Canvas is not creating secure sessions | Canvas |
| [`errorId` with a 500](#internal-server-errors) | A failure inside LTIAAS | LTIAAS support |
### A Bare Forbidden Message
A body containing only `"message": "Forbidden"`, without `status` or `details`, comes from our [web application firewall](https://en.wikipedia.org/wiki/Web_application_firewall), which blocked the request before it reached the API. Common reasons:
1. The request originated from a bare IP address. LTIAAS requires a fully qualified domain name; for local testing use a tunnelling service such as [ngrok](https://ngrok.com/).
2. The request contained content identified as malicious, such as SQL in query parameters, code in the body, or calls to paths that are not [documented endpoints](/api/ltiaas).
3. The source IP address appears on a public [blocklist](https://www.abuseipdb.com/).
Serverless platforms are a frequent cause, as their outbound IP addresses change regularly. [Contact support](mailto:support@ltiaas.com) with your API key so that traffic can be allowed by key rather than by IP address.
### A Missing lti_message_hint
A `400` reporting that `lti_message_hint` is missing usually means the LMS registration specifies an incorrect access token endpoint, preventing the LMS from completing the step that generates the hint. On Canvas this is commonly the authorization redirect URL used in place of the token URL.
Ask the LMS administrator to verify the access token endpoint against the platform's documentation.
### The Token Can't Be Used Before _DATE_
The LMS determined that a token was issued at a future time, which means its server clock has drifted. The clock must be within a few minutes of [NTP](https://developers.google.com/time) time for tokens to validate. Ask the LMS server administrator to synchronise it against `time.google.com`.
### A 401 During Dynamic Registration
If manual registration succeeds but dynamic registration fails with a `401` and a missing registration token, the cause is the LMS server's network path rather than the registration data: the LMS could not complete its own request for the token. Firewall rules or security group configuration on the LMS host are the usual cause.
### Third-Party Cookies Are Being Blocked
Browsers increasingly block third-party cookies, which prevents sessions from being created inside an iframe. See [managing third-party cookies with LTI®](../resources/cookies-and-lti.mdx).
### Internal Server Errors
A `5xx` response containing only an identifier indicates a failure inside LTIAAS:
```json
{
"status": 500,
"error": "Internal Server Error",
"details": { "errorId": "8f3c2a1b9d7e" }
}
```
These are logged and monitored automatically and cannot be resolved from your side. Send us the `errorId` if one is blocking your integration.
### Authlogic Validation Error: You did not provide any details for authentication.
This appears in the Canvas server logs rather than the browser, and means Canvas is not creating secure user sessions. The same cause presents as the user being returned to the Canvas login screen after having already logged in.
Copy `config/session_store.yml.example` to `config/session_store.yml` and set `secure` to `true`, as described in [this Canvas issue](https://github.com/instructure/canvas-lms/issues/1900):
```diff
development:
session_store: encrypted_cookie_store
expire_after: 86400 # 1 day in seconds
- # secure: true
+ secure: true
```
See also the [Bitnami Canvas caution](./common_problems.mdx#bitnami-canvas-caution), which accounts for a significant proportion of reported Canvas problems.
## Contacting Support
Contact [support@ltiaas.com](mailto:support@ltiaas.com) when the LMS returns HTML instead of a valid LTI® response, when a `500` returns an `errorId`, when the firewall is blocking requests that should be allowed, or when a launch fails and none of the checks above account for it.
Include:
1. A [HAR file](./common_problems.mdx#getting-a-detailed-log-of-the-launch-process) of the failing launch, which records what the LMS sent.
2. The complete error response, including `details`, and in particular `bodySent` and `bodyReceived` when `externalError` is present.
3. The LMS and the course the launch originated from, as services are granted per context.
# Resources
## Resources
Source: https://ltiaas.com/guides/resources/introduction
# Resources
## Section Summary
This section contains some helpful resources.
## Navigating the Cookie-Free Future with LTI®
Source: https://ltiaas.com/guides/resources/cookies-and-lti
###### A Guide to Fixing Your LTI® Tool for Chrome's Third-Party Cookie Block
LTI® tools are often displayed in iFrames within LMSes. This makes for a seamless interface with the LMS, but creates challenges when Cookies are involved.
Starting in 2024, the Google Chrome web browser has [started phasing in the blocking of third party Cookies](https://developers.google.com/privacy-sandbox/blog/cookie-countdown-2023oct). This shift is in response to growing concerns about user privacy and the need for more transparent and user-centric data practices.
**Info:**
A third-party Cookie is a Cookie that is originating from a different domain than the primary website.
## Does LTIAAS Require Cookies?
No, LTIAAS has been Cookie-Free since 2022. Instead of Cookies, we store temporary launch information in the local browser `sessionStorage`. If a browser doesn't support this, we fall back to a new LTI® protocol ([video](https://www.youtube.com/watch?v=60QY7HxPenk)) that allows us to store session information directly with the LMS. We default to using `sessionStorage` first, because not all LMSes support the the new LTI® cookie-less protocol.
## What Action Is Needed Of Me?
To understand if third-party Cookie blocking will affect your LTI® tool, there are a couple of tools available to help:
1. Using Google Chrome, [enable third-party cookie blocking](https://developers.google.com/privacy-sandbox/blog/cookie-countdown-2023oct#test).
This is a quick and easy pass/fail test. If the LTI® launch pricess works and you are able to use your tool as a teacher/student, you are probably not reliant on third-party cookies.
2. Install the [Google Chrome Privacy Sandbox Analysis Tool (PSAT)](https://developers.google.com/privacy-sandbox/blog/psat-announcement) into Google Chrome.
Using PSAT, you can get more insight on what Cookies your LTI® tool is reliant on and how to resolve them.
**Tip:**
When testing third-party Cookie blocking, make sure to test the deep-linking flow (if you tool uses it) as well as a launch **within an iframe**.
## Oh No, My Tool Is Broken! How Do I Fix It?
There are many ways to resolve this issue. Here are several that we recommend to our customers:
### Options That Modify or Remove Cookies
1. ***Leverage Browser sessionStorage:***
If you have full control over your authentication system, you can use the web browser's [sessionStorage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) to store session information instead of within a Cookie. This requires JavaScript to set and retrieve the session information within the front-end.
2. ***Using a JWT Query Parameter:***
Upon an LTI® launch, LTIAAS send a query parameter called `ltik`. This is a JWT that your tool uses to login/authenticate the user. After you authenticate the user, you can append your login JWT as a query parameter to your front-end so it is always accessible. The JWT can then be sent from your front-end to your back-end every time you make a request the requires authentication.
3. ***Using Partitioned Cookies:***
If you have control over the Cookie that is sent, you can simply add `SameSite=None; Secure; Partitioned;` to the Cookie. This is called [Cookies Having Independent Partitioned State (CHIPS)](https://developer.mozilla.org/en-US/docs/Web/Privacy/Partitioned_cookies). However this is very new. As of Feb 2024, this is only supported in Google Chrome version 100 and higher and Microsoft Edge version 114 and higher. Not Safari, Firefox, or older versions of Chrome and Edge.
### When Changing Cookies Is Not Possible
You might not be able to change your tool's use of Cookies. That's OK, there is still an option. Simply open the tool in a new browser tab, instead of an iFrame. There's a couple of ways to do this:
1. Ask the LMS to always open your tool in a new tab. Most LMSes support this, but sometimes admin mistakes are made and your tool doesn't open in a new tab. So option 2 is still recommended.
2. If you detect that your tool is within an iFrame, give the user a button that opens your tool in a new tab. Here are some example JavaScript/React code snipits.
```js
// JavaScript function to detect if the page is loaded in an iFrame
function inIframe () {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}
```
```jsx
// ReactJs Example Code
// When page loads, check if we are in an Iframe, and ask the user to open in a new Tab
{inIframe() === true &&
}
```
This makes the LTI® integration feel a little less magical, but it will allow your tool to continue to work, which is the most important thing.
**Danger:**
Specifically for Deep-Linking content selection: Simply opening your tool in a new tab will break the integration with most LMSes.
#### Handling Deep-Linking in a New Tab
Opening your tool in a new tab after a basic LTI® launch is totally fine to do. But, if you need to have your tool opened in a new tab for deep-linking, great care must be taken to not break the Deep-Linking process. Most LMSes use the [iFrame postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) to send deep-linking data from the tool iFrame to the LMS parent window. So if you leave the iFrame, the LMS will attempt to post a message to a parent window that no longer exists, breaking deep-linking with the LMS. Here's a couple options to handle this.
1. ***Don't require a login for deep-linking:***
For many tools, user login is only required for a normal tool launch and getting deep-linking data can simple be authenticated with a one-off call to the [LTIAAS idtoken API](../../guides/api/idtoken.mdx) with no user session created. This will allow you to keep the deep-linking flow within an iFrame.
2. ***Use Polling or WebSockets:***
If you must open the deep-linking view in a new tab, you must keep some communication between the new tab and the existing iFrame so the the deep-linking process can be completed back in the iFrame after the content selection is completed in the new tab. There are a couple of wasy to handle this.
1. ***Use iFrame Polling:*** This is a simple and robust solution. After the new tab is opened, the iFrame polls your tool's back-end to see if the content selection has completed in the new tab. We recommend polling once a second which creates an unnoticeable lag in most cases. Once the user completes the content selection in the new tab, the new tab stores the selection item in your tool's back-end database. We recommend using the LTIAAS-provided ltik as a database key to guarantee that no other user sessions could get mistakenly consumed by the iFrame. After the iFrame pools successfully, it then posts the form to the document body and [finishes the deep-linking process](../../guides/api/deeplinking#calling-the-deep-linking-endpoint).
2. ***Use WebSockets:*** Similar to using Polling above, you can use [WebSockets](https://developer.mozilla.org/en-US/docs/Glossary/WebSockets) to make a connection between the iFrame and new tab. This is not recommended because WebSockets require more effort to ensure their reliability. But if your application already uses WebSockets, they could be reused for this purpose.
## Getting Help
There's a lot of information to digest here, and coming up with the right solution is a challenging task. LTIAAS is here to support this transition for our customers the best we can.
**Tip:**
**[Contact LTIAAS support about third-party Cookies](/contact-us)**
## GitHub
Source: https://ltiaas.com/guides/resources/github
The [LTIAAS GitHub](https://github.com/LTIAAS) is a great place to explore helpful resources related to LTIAAS.
Here are some of the repositories we have available:
## Code Examples
## Plugins
## SDKs
## LTIAAS Firebase Test Tool
Source: https://ltiaas.com/guides/resources/ltiaas-firebase-test-tool-video
In this video Joe explains how to deploy a Firebase project from https://github.com/LTIAAS/Firebase-LTI-Test-Tool. He explains how to connect the Firebase project with an LTI® 1.3 service provided by https://ltiaas.com. Finally, he explains the code that was deployed to Firebase to provide a better understand on how an LTI® launch and Deep Linking work.
## Introduction to LTIAAS
Source: https://ltiaas.com/guides/resources/ltiaas-intro-video
Joe explains how LTIAAS works and how to use it to easily create an LTI® Advantage learning tool.
## Moodle as an LTI® CMS
Source: https://ltiaas.com/guides/resources/moodle-as-an-lti-cms
Using the Moodle LMS as an LTI®-Capable Content Management System
You have probably reached this guide because you have some amazing learning content. This might be in the form of:
- Videos,
- SCORM packages,
- H5P content,
- Quizes/Surveys/Polls,
- PDFs and other written material
And now you have universities knocking down your door, begging for access to this content. And chances are, they are asking you to deliver this content with LTI®. You've got a couple of options:
1. Contract a custom software development to create a content management and delivery system, build an LTI® adapter to connect with the universities,
2. Use a free, open-source system that was literally designed to solve this exact problem 😉.
In some cases, option \#1 has to be your choice, especially when the content you are delivering already requires custom software development. However, if your content is relatively conventional, you can use the Moodle LMS to easily solve this problem wholistically for you.
## Introduction
Moodle is a popular open-source Learning Management System (LMS) used worldwide for creating and managing online courses. With its robust features and flexibility, Moodle has become a go-to platform for educators and institutions. In this article, we will explore how to harness the potential of Moodle as an LTI®-capable Content Management System (CMS).
Using LTIAAS, you can turn a Moodle instance into a powerful learning content management system that can be integrated into all the popular LMSes, allowing you to share content across different platforms and institutions.
### Developing Learning Content in Moodle
Moodle is a versatile Learning Management System (LMS) that supports the management of various types of learning content. Here's a list of the types of learning content that Moodle can help you manage:
1. Text-based Content:
- Lecture notes
- Reading materials
- Articles
- Announcements
2. Multimedia Content:
- Audio files
- Video files
- Podcasts
- Interactive multimedia presentations
3. Interactive Assessments:
- Quizzes
- Surveys
- Polls
- Self-assessment tools
4. Web Links and External Resources:
- External website links
- Online articles
- Reference materials
5. SCORM Content:
- Sharable Content Object Reference Model (SCORM) packages
- E-learning modules
6. Certificates and Badges:
- Automated certificate generation
- Badge awards for achievements
7. Surveys and Feedback:
- Course evaluations
- Feedback forms
- Peer reviews
8. Moodle Books:
- Structured e-books within Moodle
- Chapter-wise content delivery
Moodle's flexibility and extensive features make it adaptable to various educational contexts, allowing you to manage a diverse range of learning content to meet the needs of your company. Moodle has great documentation about [managing content](https://docs.moodle.org/403/en/Managing_content) on their website.
## Understanding Learning Tools Interoperability
We have seen that Moodle is very capable at managing learning content within its walls. Let's now try to understand how we can get the content from Moodle and delivery it to any other LMS at a university.
Learning Tools Interoperability, commonly known as LTI®, is a standard that enables the integration of external tools and content into an LMS or other educational platforms. LTI® promotes interoperability between different systems, making it easy to connect learning content with LMSes. In LTI® terminology there are two parts, Providers and Consumers. Generally speaking, Providers 'provide' the learning content, and consumers 'consume' the learning content.
All the popular LMSes support this standard LTI® protocol:
- Canvas
- Schoology
- Blackboard
- Sakai
- Moodle
- D2L
All these LMSes are LTI® Consumers, not Providers. *Wait!* If Moodle is a Consumer, how make it a Provider so it can connect to other LMSes?
### LTIAAS-Powered Moodle
LTIAAS has developed a Moodle plugin that enables Moodle to be an LTI® Provider, letting it connect to other LMSes. The setup is simple:
1. Create a Moodle server to host your learning content. We recommend [https://moclusters.com](https://moclusters.com) to get started.
2. Build up your learning content on that Moodle server.
3. Create an account at [https://portal.ltiaas.com](https://portal.ltiaas.com).
4. Your LTIAAS account will have a domain and API key, add them to the Moodle plugin.
5. Download and install the [Open-source LTIAAS Moodle Plugin](https://github.com/LTIaaS/moodle-provider-plugin).
6. That's it! now you can integrate your Moodle server as an LTI® Provider into any LTI®-capable LMS.
### Why Use LTIAAS
LTIAAS is uniquely designed to work exceptionally well with Moodle any other systems as your content portfolio grows.
1. URLs generated by the LTIAAS Moodle plugin are portable. If you decide to move away form Moodle in the future, the same URLs and LMS registration will continue to work with any other content provider. With a simple micro-service and zero down-time, you can even support Moodle URLs alongside other content system URLs.
2. LTIAAS supports deep-linking, this makes it easy for you to provide institutions with a portfolio of content, letting them choose which content to integrate with their LMS instead of manually passing around URLs.
## Detailed Instructions
Ok, those instructions are brief, let's dive into detail on how to do this.
### Step 1. Create a Moodle Server
If you don't have a Moodle server, yet you need to create one. Moodle is open-source and can be [downloaded](https://download.moodle.org/) and hosted by you if you are savvy enough. If you don't want the hassle of managing your own Moodle server, [https://moclusters.com](https://moclusters.com) is a very affordable and reliable Moodle hosting provider.
### Step 2. Create an LTIAAS Account
Follow [this guide](../starting/account-setup.mdx) to create an LTIAAS account. Once the account is created you will choose a domain and other API settings. Please use the following values:
| Setting | Description |
|--------------------------------------|-------------|
| Data Region | The region in which you want your data hosted in. **After this is provisioned, it cannot be changed.** If you want to change your data region after provisioning, please contact support@ltiaas.com. If GDPR compliance is a concern, our EU servers are specifically designed to meet that compliance and fully reside within the EU. |
| Subdomain | This is the URL that we provision for your tool in our system. All of your API traffic will go through this URL. It is a subdomain of ltiaas.com. i.e. mytool.ltiaas.com. If you chose a plan with the _Custom Domain_ option, you will be able to enter in a fully-qualified domain that you have DNS control over. |
| Tool Name | This is the title that is displayed in the LMS after the tool is linked. |
| Tool Description | This is the short description that is displayed in the LMS after the tool is linked. Just a short sentence is best. Each LMS displays this differently. |
| Launch URL | `moodle_domain`/enrol/ltiaas/tool.php example: _https://moodle.mysite.com/enrol/ltiaas/tool.php_ |
| Deep Linking URL | `moodle_domain`/enrol/ltiaas/content-selection/index.html example: _https://moodle.mysite.com/enrol/ltiaas/content-selection/index.html_ |
| Logo URL | The URL to your tool's logo that will be shown in the LMS (through _Dynamic Registration_). If _Dynamic Registration_ is not enabled or used, this logo is still shown to the user in the unlikely event of an error during the LTI® launch process. Recommended size: 128x128px. This should be a URL to an image hosted by you. |
| Dynamic Registration Auto-Activation | Enabling this will activate all registrations done through the Dynamic Registration API, If Dynamic Registration is enabled. If you want to approve every activation request first (i.e. after the customer pays you), then leave this disabled. |
After saving and deploying the API settings, click the `Generate New API Key` button to get an API key.
### Step 3. Install the LTIAAS Moodle Plugin
1. Navigate to [https://github.com/LTIaaS/moodle-provider-plugin/releases](https://github.com/LTIaaS/moodle-provider-plugin/releases) and download `ltiaas.zip` from the latest release.
2. On your Moodle Service, as an administrator, navigate to `Site administration` -> `Plugins` -> `Install plugins`
3. Upload the `ltiaas.zip` file and click `Install plugin from the ZIP file`.
4. Moodle will guide you through the installation process.
5. After several guided steps you will be presented with the _plugin settings_ page. Enter in the URL that you choose when creating your LTIAAS account. Also enter your API key that can be retrieved from the API settings page of your LTIAAS account.
6. Change any other plugin settings to your liking and save the plugin settings by clicking the `Save changes` button at the bottom of the page.
7. After the plugin has finished installing, navigate to `Site administration` -> `Plugins` -> `Enrolments` -> `Manage enrol plugins`
8. Find the row with "Publish as LTI® Advantage tool with LTIAAS" and click the `eye` to the right of it to enable the plugin.
**Tip:**
The LTIAAS plugin needs a couple of Moodle services/settings enabled. IF you don't have them enabled yet, please do the following:
**Enable LTI® Authentication**
Navigate to `Site administration` -> `Plugins` -> `Authentication` -> `Manage authentication`. Enable the LTI® authentication method.
**Enable iFrame Embedding**
Navigate to `Site administration` -> `General` -> `Security` -> `HTTP security` and check the box for "Allow frame embedding", then click the `Save changes` button at the bottom of the page.
### Step 4. Publish Content for LMS Consumption
After installing and setting up the LTIAAS Moodle plugin, you need to tell which courses and activities to publish through LTI®.
1. As a teacher/Administrator, navigate to a Course.
2. Click `More` -> `Published LTI® Advantage tools with LTIAAS`
3. On the next page, click the `Add` button
4. On the next page, set the values for the activity or course that you want to publish. Choose the course or activity to publish using the `Tool to be published` drop-down field.
### Step 5. Connect Your Tool to an LMS
Follow [these guides](../lms/introduction.mdx) to install your tool into an LMS.
### Step 6. Accessing Content from an LMS
There are two ways that the LTIAAS Moodle plugin:
#### Option 1. Automatic assignment selection with [Deep Linking](../api/deeplinking.mdx)
In most cases, you will be publishing a lot of different content, and you want the teacher to be able to select the content they want within their parent LMSes. [LTI® Deep Linking](../api/deeplinking.mdx), which is fully supported in the [LTIAAS Moodle plugin](https://github.com/LTIaaS/moodle-provider-plugin), allows for this type of user experience. When installing your Moodle tool into an LMS, the LMS can choose to enable deep-linking. When they do this, the teacher will be presented with a content selection screen as shown below.
When the teacher selects a content-item to send back to their LMS, the [LTIAAS Moodle plugin](https://github.com/LTIaaS/moodle-provider-plugin) will generate a unique URL for that content for the LMS to use to launch that content.
#### Option 1. Manual URL Generation/Placement
You might not benefit from deep-linking if:
- you are not publishing several different content items, for example you want to publish an entire course, or
- you own both the Moodle server, and a single LMS that only you will be modifying.
In these cases, instead of using the content selection screen as described above, you can manually generate URLs that the LMS can use to access the specific content. When publishing content to the LTIAAS plugin in Moodle, there is a 'Launch URL' column of the published tools table.
When you tell the LMS to launch to this content, just have the LMS administrator use the URL from this table.
## Conclusion
Moodle's compatibility with Learning Tools Interoperability transforms it into a dynamic and versatile Content Management System. By leveraging the power of LTI®, educators can enhance the learning experience by integrating a wide array of external tools and content seamlessly. This not only simplifies content management but also opens up new possibilities for interactive and engaging online education. Embrace the potential of Moodle as an LTI®-capable CMS to create a more enriched and collaborative learning environment for your students.
By using LTIAAS, as your company and goals grow, we make it easy for you to maintain existing LMS connections while transitioning to a more custom content system that might be outside of Moodle.
## Videos
Source: https://ltiaas.com/guides/resources/videos
# Videos
## Section Summary
This section contains videos that can help you get started with LTIAAS demonstrate the API features.
# Connect: getting started
## Introduction to LTIAAS Connect
Source: https://ltiaas.com/connect/guides/introduction
# Introduction to LTIAAS Connect
LTIAAS Connect lets your learning management system launch third-party LTI® 1.3 tools. You make ordinary REST calls; LTIAAS performs the protocol work — the OIDC handshake, the JWT signing and verification, the OAuth2 token issuing, and the service proxying — as an LTI® platform acting on your behalf.
The practical effect is that adding LTI® support to an LMS becomes a handful of HTTP calls rather than a standards implementation project. You never generate a key pair, never sign a JWT, and never read the specification.
## Connect or Launch?
LTIAAS has two products, and they sit on opposite ends of the same protocol. Picking the wrong one is the most common false start, so it is worth being deliberate:
| | **LTIAAS Connect** | **LTIAAS Launch** |
|---|---|---|
| You are building | A learning management system | A learning tool |
| LTIAAS acts as | The LTI® **platform** (consumer) | The LTI® **tool** (provider) |
| Launches are | Started by you, into someone else's tool | Sent to you, by someone else's LMS |
| You provide | A roster, grades, and course context | An activity for learners to use |
If teachers add *your* content to *their* Canvas or Moodle course, you want [LTIAAS Launch](/guides/introduction). If teachers add *other people's* content to courses inside *your* platform, you want Connect.
**Caution:**
An LTIAAS account is either a Connect account or a Launch account — never both. Calling a Connect endpoint with a Launch account's API key fails with `403 INVALID_ACCOUNT_TYPE`, and vice versa. If you need both, you need two accounts.
## What LTIAAS Handles, and What You Handle
Connect draws a clean line down the middle of an LTI® integration.
**LTIAAS handles the protocol.** It maintains a key pair for every registered tool, publishes a public keyset, answers the tool's OIDC authorization requests, mints and signs ID Tokens, issues OAuth2 access tokens, validates every inbound message, and enforces the privacy level and service permissions you set on each tool.
**You handle everything that is specific to your LMS.** Only your system knows who the user is, which course they are in, whether they are a teacher or a learner, what the roster looks like, and where grades belong. Connect asks you those questions at the moments it needs answers, and you reply.
That division is why a Connect integration has two halves: endpoints you call, and requests you answer.
## The Three Surfaces
Before writing any code, it helps to know which endpoints are which.
1. **Endpoints you call.** [Launch](/connect/api/launch-core-form), [ID Token](/connect/api/idtoken-core-form) and [Tools](/connect/api/tools-create) are plain REST endpoints on your LTIAAS subdomain, authenticated with your API key. These are what you write code against.
2. **Endpoints the tool calls.** The [platform endpoints](/connect/api/lti-authenticate) — authentication, access token, keyset, deep linking return, and the service endpoints — are the LTI® interface LTIAAS exposes *to* registered tools. You never call them. You hand their URLs to the tool vendor when you register the tool.
3. **Requests LTIAAS makes to you.** When a tool asks for a course roster or writes a grade, LTIAAS forwards that request to a single **Service URL** on your server as a signed JWT. Your server answers it. These are the [service requests](/connect/guides/services/introduction).
**Tip:**
A first integration only needs surfaces 1 and 2. Service requests are needed once you support tools that read rosters or write grades, and you can add them later without changing anything you have already built.
## Section Summary
## Next Steps
Read [How a launch works](/connect/guides/starting/how-it-works) for the shape of the flow, then [configure your account](/connect/guides/starting/portal-setup) and [register your first tool](/connect/guides/starting/registering-a-tool). When you are ready to write code, [Your first launch](/connect/guides/flows/core-launch) walks through a complete working integration.
## How a Launch Works
Source: https://ltiaas.com/connect/guides/starting/how-it-works
# How a Launch Works
Every LTIAAS Connect launch follows the same four steps. Two of them are yours; two of them happen without any code on your side. Understanding the shape now makes the [worked example](/connect/guides/flows/core-launch) much easier to follow.
The whole exchange happens in the learner's browser, usually inside an iframe you open when they click an activity.
## Step 1 — You Start the Launch
A user clicks an activity in your LMS. Your front-end asks your back-end to start a launch, and your back-end calls LTIAAS:
```
POST https://your.ltiaas.com/api/launch/core/form
Authorization: Bearer ```
```json
{
"clientId": "U0gIhEYg2rdIr4ABscEJ",
"context": "0001",
"resource": "14113",
"user": "rKk4PdLgcRbqE4PdSW3iV0KhAmu2"
}
```
You are telling LTIAAS four things: which tool to launch, which course the launch belongs to, which activity within that course, and which user is launching. LTIAAS replies with a self-submitting HTML form. Render it in an iframe and the browser posts itself into the tool.
**Info:**
`context`, `resource` and `user` are **your** identifiers. LTIAAS treats them as opaque strings, carries them through the launch, and hands them back to you in step 3. Use whatever your database already uses.
**Caution:**
This call includes your API key, so it must happen on your back-end. Never call the LTIAAS API from a browser.
## Step 2 — LTIAAS and the Tool Complete the Handshake
The tool receives the form post and begins the standard LTI® 1.3 OIDC handshake, calling back to LTIAAS's [authentication endpoint](/connect/api/lti-authenticate). LTIAAS validates the request against the tool's registration — checking the redirect URI, the signed message hint, and the nonce — and confirms the tool is registered and active.
**No code of yours runs during this step.** Handling this handshake is the entire reason Connect exists.
## Step 3 — LTIAAS Asks You Who the User Is
Once the handshake checks out, LTIAAS redirects the browser to the **Launch URL** you configured in the portal, with a signed token attached:
```
GET https://yourlms.com/lti-validate?payload=```
This is your second piece of code. Verify the JWT with your [consumer public key](/connect/guides/api/authentication#verifying-requests-from-ltiaas), and you get back the `context`, `resource` and `user` values you sent in step 1, plus a `metadata` string that identifies this launch.
Now do the work only your LMS can do: confirm the user is logged in, confirm they may access this course, and gather their name, email and roles.
**Caution:**
The `metadata` value **expires ten minutes** after the launch begins. If this step waits on something slow — a login screen, a consent dialog — the launch will fail with `INVALID_METADATA_PARAMETER`. Authenticate the user before starting the launch, not during it.
## Step 4 — You Answer, and the User Lands in the Tool
Send what you learned back to LTIAAS:
```
POST https://your.ltiaas.com/api/idtoken/core/form
Authorization: Bearer ```
```json
{
"metadata": "",
"user": { "id": "41", "name": "John Doe", "email": "john.doe@myschool.com", "roles": ["CONTEXT_LEARNER"] },
"context": { "id": "0001", "label": "CS101", "title": "Computer Science 101", "type": ["CourseOffering"] },
"resource": { "id": "14113", "title": "A cool learning tool" }
}
```
LTIAAS builds and signs an LTI® ID Token and returns another self-submitting form. Return it as the body of your Launch URL response, the browser posts it to the tool, and the user is in — logged in, in the right course, with the right role.
**Note:**
Notice the shape change: `context` and `resource` were plain strings in step 1, but they are **objects** here. Step 1 is routing information; step 4 is descriptive information. This catches almost everyone once.
## After the Launch
If the tool uses LTI® services — reading a roster, creating a grade line, posting a score — it will now call LTIAAS with an access token. LTIAAS forwards each of those calls to your **Service URL** as a [service request](/connect/guides/services/introduction) and translates your answer back into the protocol.
Those requests arrive whenever the tool decides to make them, which may be long after the learner has gone. They are not part of the launch.
## Next Steps
- [Configure your account](/connect/guides/starting/portal-setup) — the URLs and keys this flow depends on.
- [Register a tool](/connect/guides/starting/registering-a-tool) — where `clientId` comes from.
- [Your first launch](/connect/guides/flows/core-launch) — the same four steps as working code.
## Configuring Your Account
Source: https://ltiaas.com/connect/guides/starting/portal-setup
# Configuring Your Account
Everything on this page lives in the [LTIAAS Portal](https://portal.ltiaas.com), under your account's **API Settings**. You only need to do it once.
## Your Subdomain
Every account gets its own domain, and it is the base URL for every call you make:
```
https://your.ltiaas.com
```
It is also the LTI® **issuer** — the identity tools know your platform by. Custom domains are supported if you would rather use your own.
## Your API Key
The API key authenticates every request you make to LTIAAS. It goes in an `Authorization` header as a bearer token:
```
Authorization: Bearer df06d55e-3b0f-4121-b60f-c39469b5b550
```
There is one key per account, and it grants full access, so treat it as a secret: keep it in your server's environment, never in front-end code, and rotate it from the portal if it is ever exposed.
## The Three Target URLs
Connect hands work back to you at three URLs. All three are endpoints on your own server, and you set them in the portal.
### Launch URL
Where LTIAAS sends the browser during [step 3 of a launch](/connect/guides/starting/how-it-works#step-3--ltiaas-asks-you-who-the-user-is). It receives a `GET` with a `?payload=` query parameter, and it answers with the form returned by [Complete Launch Form](/connect/api/idtoken-core-form).
```
https://yourlms.com/lti-validate
```
This one is required — without it, no launch can finish.
### Deep Linking URL
The same idea, for [deep linking launches](/connect/guides/flows/deep-linking). This is the page a teacher sees while choosing content from a tool, so it typically renders some UI of yours around the tool's picker.
```
https://yourlms.com/lti-deep-linking
```
Required only if you enable deep linking.
### Service URL
A single `POST` endpoint that receives every [service request](/connect/guides/services/introduction) — roster reads, grade writes, deep linking results. One endpoint handles all of them; you branch on the `type` claim inside the payload.
```
https://yourlms.com/lti-services
```
Required only if you enable deep linking, names and roles, or assignment and grades.
**Tip:**
All three can be the same host, or even the same server framework — they are just three routes. Keep them separate from your regular API routes so their authentication (JWT verification rather than a session cookie) stays obvious to whoever reads the code next.
## Your Consumer Public Key
LTIAAS signs the payload it sends to your Launch, Deep Linking and Service URLs with an RSA key belonging to your account. Copy the public half from the portal and store it in your server's environment:
```
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
```
Every request that arrives from LTIAAS must be verified against this key before you act on it. See [Authenticating API requests](/connect/guides/api/authentication#verifying-requests-from-ltiaas).
**Caution:**
This is a *different* key from the ones at `https://your.ltiaas.com/lti/keys`. Those are per-tool keys that tools use to verify your ID Tokens. This one is your account key, used only to sign requests to you.
## Enabling Services
Deep linking, names and roles, and assignment and grades are enabled per account in the portal. A launch that uses a disabled service is refused with `403 INACTIVE_SERVICE`, so switch on the ones you intend to support before testing.
Enabling a service at the account level makes it *available*; each tool then gets its own [permissions](/connect/guides/starting/registering-a-tool#permissions) controlling whether it may actually use it.
## Next Steps
[Register your first tool](/connect/guides/starting/registering-a-tool) and you have everything a launch needs.
## Registering a Tool
Source: https://ltiaas.com/connect/guides/starting/registering-a-tool
# Registering a Tool
Before your LMS can launch a tool, the two systems have to be introduced. Registration is a two-way exchange: the vendor gives you some URLs and a key, and you give them some URLs and an ID.
You can register a tool from the [LTIAAS Portal](https://portal.ltiaas.com) or through the [Tools API](/connect/api/tools-create). Both do the same thing.
## What You Need from the Vendor
Ask the tool vendor for these. Most LTI® 1.3 tools publish them on a setup page.
| Field | What it is |
|---|---|
| `loginEndpoint` | Their OIDC login initiation URL. LTIAAS posts here to start a launch. |
| `launchEndpoint` | Where launches should land by default. |
| `deeplinkingEndpoint` | Where deep linking launches land. Optional — falls back to `launchEndpoint`. |
| `redirectionUris` | Every URL the tool may be redirected back to. A launch aiming anywhere else is refused. |
| `authConfig` | How LTIAAS verifies messages the tool signs — usually a keyset URL. |
## Registering
```bash
curl -X POST https://your.ltiaas.com/admin/tools \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"name": "My Tool",
"loginEndpoint": "https://mytool.com/lti/login",
"launchEndpoint": "https://mytool.com/lti/launch",
"deeplinkingEndpoint": "https://mytool.com/lti/deeplink",
"redirectionUris": ["https://mytool.com/lti/launch"],
"authConfig": { "method": "JWK_SET", "key": "https://mytool.com/lti/keys" },
"permissions": ["MEMBERSHIPS_READ", "LINEITEMS_READ"],
"personalData": "COMPLETE",
"active": true
}'
```
**Note:**
Tool management lives under `/admin`, not `/api`. It uses the same API key.
LTIAAS responds with the registration, including three values it generated for you:
```json
{
"id": "3wT7bVhgsve6byG33fVV",
"clientId": "3wT7bVhgsve6byG33fVV",
"deploymentId": "EgAssgssdfgsfshshq8iw",
"publicKey": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n",
"...": "..."
}
```
The `clientId` is the value you will pass on every launch of this tool. It is always identical to `id`.
## What You Give the Vendor
These are the **registration settings** for your platform. They are the same for every tool except `clientId`.
| Setting | Value |
|---|---|
| Platform / Issuer URL | `https://your.ltiaas.com` |
| Client ID | The `clientId` from the registration response |
| Deployment ID | The `deploymentId` from the registration response |
| OIDC authentication endpoint | `https://your.ltiaas.com/lti/authenticate` |
| Access token endpoint | `https://your.ltiaas.com/lti/authorize` |
| Keyset (JWKS) URL | `https://your.ltiaas.com/lti/keys` |
**Tip:**
Vendors label these differently — "authorization endpoint", "auth login URL", "platform OIDC endpoint" — but there are only ever three URLs plus an issuer and a client ID. If a form has a field you cannot place, the [Platform Endpoints reference](/connect/api/lti-authenticate) describes what each one does.
## Choosing an Auth Method
`authConfig.method` tells LTIAAS how to check the tool's signatures.
| Method | `key` holds | Use when |
|---|---|---|
| `JWK_SET` | A URL to the tool's JWKS | Almost always. The tool can rotate keys without telling you. |
| `JWK_KEY` | A single JWK, as a JSON string | The tool publishes one static key, no endpoint. |
| `RSA_KEY` | A PEM-encoded public key | The vendor sends you a `.pem` file. |
Prefer `JWK_SET`. Static keys mean an outage the day the vendor rotates them.
## Permissions
`permissions` controls which LTI® services this tool may use. A tool requesting a scope it was not granted is refused at the token endpoint.
| Permission | Lets the tool |
|---|---|
| `MEMBERSHIPS_READ` | Read the course roster |
| `LINEITEMS_READ` | Read grade lines |
| `LINEITEMS_READ_WRITE` | Create, update and delete grade lines |
| `GRADES_READ` | Read learners' scores |
| `GRADES_WRITE` | Post scores |
Grant only what the tool needs. A tool that just displays content needs none at all — send an empty array.
**Caution:**
A permission granted here still does nothing unless the matching service is enabled on your account in the portal. Both have to be on.
## Privacy Level
`personalData` caps how much you can reveal about a user to this tool:
| Value | Tool receives |
|---|---|
| `NONE` | No name, no email |
| `EMAIL` | Email only |
| `NAME` | Full, given and family name |
| `COMPLETE` | Name and email |
LTIAAS enforces this when it builds the ID Token, so you can always send complete user records and let the setting do the filtering. It also applies to rosters returned through names and roles.
A single launch can lower or raise the level with the `personalData` field on the [launch request](/connect/api/launch-core-form) — useful when one tool serves both anonymous and identified activities.
## Custom Parameters
`customParameters` are key/value pairs sent to the tool on every launch. Individual launches can add their own; where both set the same key, the launch wins.
## Managing Tools Afterwards
- [List](/connect/api/tools-list) or [fetch](/connect/api/tools-get) registrations
- [Update](/connect/api/tools-update) one — every field is optional, and `PUT` and `PATCH` behave identically, so omitted fields keep their values
- [Deactivate](/connect/api/tools-deactivate) to stop launches while keeping the registration; [activate](/connect/api/tools-activate) to resume
- [Delete](/connect/api/tools-delete) to remove it and its keys permanently
**Tip:**
Reach for deactivate rather than delete when a contract lapses. Deleting destroys the tool's key pair, so re-registering later means the vendor has to reconfigure their side from scratch.
## Next Steps
You now have a `clientId`. [Your first launch](/connect/guides/flows/core-launch) puts it to work.
# Connect: integration flows
## Integration Flows
Source: https://ltiaas.com/connect/guides/flows/introduction
# Integration Flows
Where the [API reference](/connect/api/ltiaas-connect) describes endpoints one at a time, these walkthroughs show how they fit together into something a user can actually click.
Each flow is explicit about which side does what. LTIAAS handles the LTI® protocol — handshakes, signing, verification, token issuing. Your LMS handles what only it knows: who the user is, which course they are in, what role they hold, and where their grades belong.
Start with the core launch. Deep linking builds directly on it, and the [service requests](/connect/guides/services/introduction) only become relevant once a tool wants to read a roster or write a grade.
## Before You Start
Both flows assume you have [configured your account](/connect/guides/starting/portal-setup) and [registered at least one tool](/connect/guides/starting/registering-a-tool). If you have not read [how a launch works](/connect/guides/starting/how-it-works) yet, read that first — the code below will make much more sense.
## Core Launch
Source: https://ltiaas.com/connect/guides/flows/core-launch
# Core Launch
This is the flow every Connect integration needs. It takes a user from clicking an activity in your LMS to landing inside a third-party tool, signed in, in the right course, with the right role.
The code below is Node, but nothing here is framework-specific — it is two HTTP calls and one JWT verification.
**Info:**
This walkthrough assumes you have [configured your account](/connect/guides/starting/portal-setup) and have a `clientId` from [registering a tool](/connect/guides/starting/registering-a-tool).
## What You Need on Hand
```javascript
const LTIAAS_URL = 'https://your.ltiaas.com'
const LTIAAS_API_KEY = process.env.LTIAAS_API_KEY
const LTIAAS_PUBLIC_KEY = process.env.LTIAAS_PUBLIC_KEY // from the portal
```
## Step 1 — Start the Launch
Your front-end asks your back-end to open an activity. Send whatever identifies the activity in your system; the back-end turns it into a launch.
```javascript
// Front-end
async function openActivity(courseId, activityId) {
const response = await fetch('/api/lti/launch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ courseId, activityId })
})
const { form } = await response.json()
// Render the returned form in an iframe. It submits itself on load.
document.getElementById('toolContainer').innerHTML =
``
}
```
Your back-end looks up the activity, then calls LTIAAS:
```javascript
// Back-end — POST /api/lti/launch
app.post('/api/lti/launch', requireLogin, async (req, res) => {
const { courseId, activityId } = req.body
// Only your LMS can answer these questions.
const activity = await db.activities.find(activityId)
await assertUserCanAccess(req.user, courseId)
const { data } = await axios.post(
`${LTIAAS_URL}/api/launch/core/form`,
{
clientId: activity.toolClientId,
context: courseId,
resource: activityId,
user: req.user.id
},
{ headers: { Authorization: `Bearer ${LTIAAS_API_KEY}` } }
)
res.json({ form: data.form })
})
```
**Caution:**
This call carries your API key, so it belongs on the back-end. If the front-end called LTIAAS directly, anyone could read the key out of the network tab and launch anything as anyone.
### Optional Fields
| Field | Use it to |
|---|---|
| `launchEndpoint` | Point this launch at a specific URL instead of the tool's default. Deep-linked content needs this. |
| `personalData` | Override the tool's privacy level for this launch only. |
| `customParameters` | Send extra key/value pairs. Merged over the tool's registered parameters, so yours win. |
**Note:**
`context`, `resource` and `user` are your identifiers, passed through untouched. Use your real database IDs — you will be looking them up again in step 3.
## Step 2 — LTIAAS and the Tool Shake Hands
The iframe loads, the form submits itself into the tool, and the tool begins the OIDC handshake against your [authentication endpoint](/connect/api/lti-authenticate). LTIAAS validates it all.
**Nothing of yours runs here.** Move on.
## Step 3 — Verify the Payload and Identify the User
LTIAAS now redirects the iframe to your **Launch URL** with a signed token:
```
GET https://yourlms.com/lti-validate?payload=```
Verify it, then answer the question it is really asking: *who is this, and what are they opening?*
```javascript
const jwt = require('jsonwebtoken')
app.get('/lti-validate', async (req, res) => {
let decoded
try {
decoded = jwt.verify(req.query.payload, LTIAAS_PUBLIC_KEY)
} catch (err) {
return res.status(401).send('Invalid launch token')
}
// The identifiers you sent in step 1 come back here.
const { user: userId, context: courseId, resource: activityId } = decoded.parameters
const user = await db.users.find(userId)
const course = await db.courses.find(courseId)
const activity = await db.activities.find(activityId)
// Map your permission model onto LTI® roles.
const roles = user.isTeacherOf(course) ? ['CONTEXT_INSTRUCTOR'] : ['CONTEXT_LEARNER']
// ... continue to step 4
})
```
The decoded payload looks like this:
```json
{
"type": "CORE_LOGIN",
"metadata": "kR2mQ9dLxTn4vB8sYw1e",
"parameters": {
"user": "rKk4PdLgcRbqE4PdSW3iV0KhAmu2",
"context": "0001",
"resource": "14113"
},
"iat": 1700000000,
"exp": 1700000600
}
```
**Caution:**
`metadata` expires **ten minutes** after the launch started. Do not put a login screen or a consent dialog in this step — by the time the user finishes, the launch is dead. Authenticate before step 1.
## Step 4 — Complete the Launch
Send the user, context and resource back to LTIAAS. It signs an ID Token and returns a form; hand that form to the browser and the user is in the tool.
```javascript
const { data } = await axios.post(
`${LTIAAS_URL}/api/idtoken/core/form`,
{
metadata: decoded.metadata,
user: {
id: user.id,
name: user.name,
email: user.email,
givenName: user.firstName,
familyName: user.lastName,
roles
},
context: {
id: course.id,
label: course.code, // "CS101"
title: course.name, // "Computer Science 101"
type: ['CourseOffering']
},
resource: {
id: activity.id,
title: activity.name,
description: activity.description
}
},
{ headers: { Authorization: `Bearer ${LTIAAS_API_KEY}` } }
)
res.send(data.form) // self-submitting; lands the user in the tool
})
```
**Note:**
`context` and `resource` were plain strings in step 1 and are **objects** here. Step 1 routes the launch; step 4 describes it.
### About Roles
`roles` takes LTIAAS role *keys*, not the full IMS URLs — LTIAAS expands them for you. Most integrations only ever need two:
- `CONTEXT_INSTRUCTOR` for teachers
- `CONTEXT_LEARNER` for students
Tools commonly change what they show based on this, so getting it right matters more than it looks. The [full list](/connect/api/idtoken-core) covers system, institution and context roles.
### About Personal Data
Send complete user records. LTIAAS strips whatever the tool's [privacy level](/connect/guides/starting/registering-a-tool#privacy-level) does not permit, so a `NONE` tool sees no name or email even though you sent both. You do not need to branch on it yourself.
## Testing It
1. Click an activity. The iframe should fill with the tool, with no visible redirects.
2. If it stays blank, open the iframe in its own tab — errors from LTIAAS are JSON and easy to read once they are not hidden inside a frame.
3. `403 INACTIVE_TOOL` means the tool is deactivated. `404 UNREGISTERED_TOOL` means the `clientId` is wrong.
4. `400 INVALID_METADATA_PARAMETER` in step 4 means more than ten minutes passed since step 1.
More on failures in [Error handling](/connect/guides/api/error-handling).
## Next Steps
- [Deep linking](/connect/guides/flows/deep-linking) — let teachers pick which content to add.
- [Handling service requests](/connect/guides/services/introduction) — answer roster reads and grade writes.
## Deep Linking
Source: https://ltiaas.com/connect/guides/flows/deep-linking
# Deep Linking
A core launch opens content a teacher has already chosen. Deep linking is how they choose it: the tool renders its own content picker inside your LMS, the teacher selects something, and the tool hands the selection back for you to store.
The flow is the [core launch](/connect/guides/flows/core-launch) with three changes and one extra step at the end.
**Caution:**
Deep linking must be enabled on your account in the portal. If it is not, the launch endpoints return `403 INACTIVE_SERVICE`.
## What Changes
| Core launch | Deep linking |
|---|---|
| `POST /api/launch/core/form` | `POST /api/launch/deeplinking/form` |
| Send `resource` | **Omit `resource`** — nothing has been picked yet |
| LTIAAS redirects to your **Launch URL** | LTIAAS redirects to your **Deep Linking URL** |
| `POST /api/idtoken/core/form` | `POST /api/idtoken/deeplinking/form` — again, no `resource` |
| Ends with the user in the tool | Ends with a `DEEP_LINKING_RESPONSE` at your Service URL |
Everything else — verifying the payload, identifying the user, sending roles and context — is identical.
## Step 1 — Open the Picker
Typically triggered by a teacher clicking "Add content" while editing a course.
```javascript
app.post('/api/lti/deeplink', requireLogin, async (req, res) => {
const { courseId, toolClientId } = req.body
await assertUserIsTeacherOf(req.user, courseId)
const { data } = await axios.post(
`${LTIAAS_URL}/api/launch/deeplinking/form`,
{
clientId: toolClientId,
context: courseId,
user: req.user.id
// no `resource` — that is what the teacher is about to choose
},
{ headers: { Authorization: `Bearer ${LTIAAS_API_KEY}` } }
)
res.json({ form: data.form })
})
```
Render the form in an iframe exactly as in a core launch.
## Step 2 — Answer at Your Deep Linking URL
LTIAAS redirects to your **Deep Linking URL** with a `payload` to verify. Same code as a core launch, minus the resource lookup:
```javascript
app.get('/lti-deep-linking', async (req, res) => {
let decoded
try {
decoded = jwt.verify(req.query.payload, LTIAAS_PUBLIC_KEY)
} catch (err) {
return res.status(401).send('Invalid launch token')
}
const user = await db.users.find(decoded.parameters.user)
const course = await db.courses.find(decoded.parameters.context)
const { data } = await axios.post(
`${LTIAAS_URL}/api/idtoken/deeplinking/form`,
{
metadata: decoded.metadata,
user: {
id: user.id,
name: user.name,
email: user.email,
roles: ['CONTEXT_INSTRUCTOR']
},
context: {
id: course.id,
label: course.code,
title: course.name,
type: ['CourseOffering']
}
// still no `resource`
},
{ headers: { Authorization: `Bearer ${LTIAAS_API_KEY}` } }
)
res.send(data.form)
})
```
The teacher now sees the tool's content picker.
**Tip:**
The payload's `type` claim is `DEEP_LINKING_LOGIN` here rather than `CORE_LOGIN`. If you serve both flows from one route, branch on it.
## Step 3 — Receive the Selection
The teacher picks something. The tool posts its selection to LTIAAS, which validates it and forwards it to your **Service URL** as a [`DEEP_LINKING_RESPONSE`](/connect/api/deep-linking-response) service request.
```javascript
app.post('/lti-services', async (req, res) => {
const decoded = jwt.verify(req.body.payload, LTIAAS_PUBLIC_KEY)
if (decoded.type === 'DEEP_LINKING_RESPONSE') {
const { context, clientId, contentItems } = decoded.parameters
const course = await db.courses.find(context)
const tool = await db.tools.findByClientId(clientId)
// Store each selection as an activity in the course. The item's `url`
// becomes the launchEndpoint for future core launches of this activity.
for (const item of contentItems) {
await db.activities.create({
courseId: course.id,
toolClientId: clientId,
name: item.title || tool.name,
launchEndpoint: item.url
})
}
// The teacher's browser is sitting on this response, so close the picker.
return res.send(``)
}
// ... other service request types
})
```
The payload:
```json
{
"type": "DEEP_LINKING_RESPONSE",
"parameters": {
"context": "2022CSEa5e6c431b91",
"clientId": "qR8E0iHqSdR30DdfQAbcaBGjKT65",
"contentItems": [
{
"type": "ltiResourceLink",
"url": "https://mytool.com/lti/launch?resource=13",
"title": "Resource 13"
}
]
}
}
```
**Caution:**
This request arrives **form-urlencoded**, not as JSON — it is delivered by a self-submitting form in the teacher's browser rather than a server-to-server call. Every other service request uses a JSON body. Make sure your route parses both, or `req.body.payload` will be undefined.
**Note:**
`contentItems` only ever contains `ltiResourceLink` items. LTIAAS rejects other content types before they reach you.
### Storing the Item
The important field is `url`. Save it as the activity's launch endpoint and pass it as `launchEndpoint` on future core launches — that is what makes the launch open *this* piece of content rather than the tool's front page.
```javascript
await axios.post(`${LTIAAS_URL}/api/launch/core/form`, {
clientId: activity.toolClientId,
context: courseId,
resource: activity.id,
user: req.user.id,
launchEndpoint: activity.launchEndpoint // the deep-linked URL
}, { headers: { Authorization: `Bearer ${LTIAAS_API_KEY}` } })
```
### Closing the Picker
Because the teacher's browser is on your response, you decide what happens next. Two patterns work well:
1. **Message the parent window**, as above, and let your page close the iframe and refresh the activity list.
2. **Return a confirmation page** listing what was added, with your own "Done" button — useful when you want the teacher to rename or configure the item before it is saved.
## Next Steps
- [Handling service requests](/connect/guides/services/introduction) — the same Service URL also receives roster and grade traffic.
- [Core launch](/connect/guides/flows/core-launch) — how the content you just stored gets opened.
# Connect: handling service requests
## Handling Service Requests
Source: https://ltiaas.com/connect/guides/services/introduction
# Handling Service Requests
Service requests run in the opposite direction from everything else in Connect: LTIAAS calls **you**.
When a tool asks for a course roster or writes a grade, it makes a standard LTI® service call to LTIAAS. LTIAAS authenticates it, checks the tool's permissions, and then forwards the question to your **Service URL**, because only your LMS knows the answer. You reply, and LTIAAS translates your reply back into the protocol.
Every service request — all nine types — arrives at that one URL. You branch on the `type` claim.
## The Envelope
Every request is a `POST` with a single field containing a signed JWT:
```json
{
"payload": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}
```
Verify it with your [consumer public key](/connect/guides/api/authentication#verifying-requests-from-ltiaas), then read `type` and `parameters`:
```json
{
"type": "MEMBERSHIPS_GET",
"parameters": {
"context": "2022CSEa5e6c431b91",
"clientId": "qR8E0iHqSdR30DdfQAbcaBGjKT65"
}
}
```
`context` is the identifier you supplied when you started the launch. `clientId` identifies the tool making the request — useful for logging, and for refusing tools that should not be asking.
**Caution:**
`DEEP_LINKING_RESPONSE` arrives **form-urlencoded**; the other eight arrive as JSON. It is delivered by a form in the user's browser rather than a server-to-server call. Parse both content types on this route.
## The Skeleton
```javascript
const jwt = require('jsonwebtoken')
app.post('/lti-services',
express.json(),
express.urlencoded({ extended: true }), // for DEEP_LINKING_RESPONSE
async (req, res) => {
let decoded
try {
decoded = jwt.verify(req.body.payload, LTIAAS_PUBLIC_KEY)
} catch (err) {
return res.status(401).json({ error: 'Invalid payload' })
}
const { context, clientId } = decoded.parameters
switch (decoded.type) {
case 'MEMBERSHIPS_GET': return handleMemberships(decoded, res)
case 'DEEP_LINKING_RESPONSE': return handleDeepLinking(decoded, res)
case 'LINEITEMS_GET': return handleLineItemsList(decoded, res)
case 'LINEITEMS_POST': return handleLineItemCreate(decoded, res)
case 'LINEITEM_GET': return handleLineItemGet(decoded, res)
case 'LINEITEM_PUT': return handleLineItemUpdate(decoded, res)
case 'LINEITEM_DELETE': return handleLineItemDelete(decoded, res)
case 'SCORE_POST': return handleScore(decoded, res)
case 'RESULTS_GET': return handleResults(decoded, res)
default: return res.status(400).json({ error: 'Unknown type' })
}
}
)
```
## The Response Contract
This is the part worth reading twice, because success and failure are treated very differently.
**On success, return `200` with JSON in the documented shape.** LTIAAS validates the schema before passing it to the tool, so a wrong shape fails the tool's request even though you returned a 200.
**On failure, return any 4xx or 5xx with any body.** Non-2xx responses are converted to JSON and forwarded to the tool untouched — no schema validation. This is how you say "that line item does not exist" or "that tool may not see this course": return a 404 or a 403 and the tool receives it.
```javascript
// A grade line the tool asked for but which we do not have
if (!lineItem) {
return res.status(404).json({ error: 'Line item not found' })
}
```
**Tip:**
Prefer a truthful 404 over an empty 200. Tools handle "not found" correctly far more often than they handle "found, but empty".
## The Nine Request Types
| Type | Tool is asking | Guide |
|---|---|---|
| `MEMBERSHIPS_GET` | For the course roster | [Memberships](/connect/guides/services/memberships) |
| `DEEP_LINKING_RESPONSE` | To hand back selected content | [Deep linking](/connect/guides/flows/deep-linking) |
| `LINEITEMS_GET` | For the list of grade lines | [Line items](/connect/guides/services/line-items) |
| `LINEITEMS_POST` | To create a grade line | [Line items](/connect/guides/services/line-items) |
| `LINEITEM_GET` | For one grade line | [Line items](/connect/guides/services/line-items) |
| `LINEITEM_PUT` | To update a grade line | [Line items](/connect/guides/services/line-items) |
| `LINEITEM_DELETE` | To delete a grade line | [Line items](/connect/guides/services/line-items) |
| `SCORE_POST` | To record a grade | [Scores and results](/connect/guides/services/scores-and-results) |
| `RESULTS_GET` | For recorded grades | [Scores and results](/connect/guides/services/scores-and-results) |
You do not have to support all of them. Implement the ones matching the [permissions](/connect/guides/starting/registering-a-tool#permissions) you grant your tools, and return a 4xx for the rest — a tool without `LINEITEMS_READ_WRITE` will never send you a `LINEITEMS_POST` anyway.
## When These Arrive
Service requests are not part of a launch. A tool might read the roster the moment a teacher opens it, or post grades in a nightly batch hours after everyone has gone home. Your Service URL has to work without a user session — authentication is the JWT signature, nothing else.
**Caution:**
Do not put session middleware, CSRF protection or a login redirect in front of this route. There is no browser and no cookie; there is a server-to-server call carrying a signed token.
## Section Summary
## Line Items
Source: https://ltiaas.com/connect/guides/services/line-items
# Line Items
A *line item* is a column in your gradebook — one gradeable thing, with a maximum score. Tools create line items so they have somewhere to put the grades they later post.
Five request types cover the full lifecycle: [`LINEITEMS_GET`](/connect/api/lineitems-get), [`LINEITEMS_POST`](/connect/api/lineitems-post), [`LINEITEM_GET`](/connect/api/lineitem-get), [`LINEITEM_PUT`](/connect/api/lineitem-put) and [`LINEITEM_DELETE`](/connect/api/lineitem-delete).
**Caution:**
Requires *Assignment and Grades* enabled on your account. Reading needs `LINEITEMS_READ` in the tool's permissions; creating, updating and deleting need `LINEITEMS_READ_WRITE`.
## The Shape of a Line Item
```json
{
"id": "412",
"label": "My first grade line",
"scoreMaximum": 99,
"resourceLinkId": "412",
"tag": "new_tag"
}
```
| Field | Required | Meaning |
|---|---|---|
| `id` | yes | Your stable identifier. LTIAAS builds the tool-facing URL from it. |
| `label` | yes | The column name a teacher sees |
| `scoreMaximum` | yes | Highest possible score |
| `resourceLinkId` | no | Ties the line item to one activity |
| `tag` | no | A tool-defined label, used to group or find its own line items |
**Caution:**
`id` must be stable. The tool stores the URL built from it and uses that URL for every later read and write. If your IDs move, the tool's grade writes start landing in the wrong place — or nowhere.
## List Line Items
`LINEITEMS_GET` may carry optional filters. Apply them when present.
```json
{
"type": "LINEITEMS_GET",
"parameters": {
"context": "2022CSEa5e6c431b91",
"clientId": "348080fn9du9b9ufvb92rfb9l",
"filters": { "resourceLinkId": "17", "tag": "myLineItems" }
}
}
```
```javascript
async function handleLineItemsList(decoded, res) {
const { context, filters = {} } = decoded.parameters
let lineItems = await db.lineItems.forCourse(context)
if (filters.resourceLinkId) {
lineItems = lineItems.filter(li => li.resourceLinkId === filters.resourceLinkId)
}
if (filters.tag) {
lineItems = lineItems.filter(li => li.tag === filters.tag)
}
return res.status(200).json(lineItems.map(serializeLineItem))
}
```
Return an array — an empty one is fine when nothing matches.
## Create a Line Item
`LINEITEMS_POST` carries the proposed line item under `parameters.lineItem`. Store it and return the stored record, including the `id` you assigned.
```javascript
async function handleLineItemCreate(decoded, res) {
const { context, lineItem } = decoded.parameters
const created = await db.lineItems.create({
courseId: context,
label: lineItem.label,
scoreMaximum: lineItem.scoreMaximum,
resourceLinkId: lineItem.resourceLinkId ?? null,
tag: lineItem.tag ?? null
})
return res.status(200).json(serializeLineItem(created))
}
```
**Note:**
Return `200`, not `201` — LTIAAS converts your response into the status the tool expects.
## Fetch One
```javascript
async function handleLineItemGet(decoded, res) {
const { context, lineItemId } = decoded.parameters
const lineItem = await db.lineItems.find(lineItemId)
if (!lineItem || lineItem.courseId !== context) {
return res.status(404).json({ error: 'Line item not found' })
}
return res.status(200).json(serializeLineItem(lineItem))
}
```
Checking that the line item actually belongs to the requested context is worth the extra line — it stops a tool in one course from reading grade lines in another.
## Update
`LINEITEM_PUT` carries both `lineItemId` and a `lineItem` object. Apply the change and return the stored record.
```javascript
async function handleLineItemUpdate(decoded, res) {
const { context, lineItemId, lineItem } = decoded.parameters
const existing = await db.lineItems.find(lineItemId)
if (!existing || existing.courseId !== context) {
return res.status(404).json({ error: 'Line item not found' })
}
const updated = await db.lineItems.update(lineItemId, {
label: lineItem.label,
scoreMaximum: lineItem.scoreMaximum,
resourceLinkId: lineItem.resourceLinkId ?? null,
tag: lineItem.tag ?? null
})
return res.status(200).json(serializeLineItem(updated))
}
```
## Delete
Remove it and return `200` with an empty body.
```javascript
async function handleLineItemDelete(decoded, res) {
const { context, lineItemId } = decoded.parameters
const existing = await db.lineItems.find(lineItemId)
if (!existing || existing.courseId !== context) {
return res.status(404).json({ error: 'Line item not found' })
}
await db.lineItems.delete(lineItemId)
return res.status(200).send()
}
```
**Tip:**
Deleting a line item usually means deleting real grades. Consider soft-deleting and hiding the column instead — teachers who lose a gradebook column to a tool's cleanup routine are rarely pleased.
## A Shared Serializer
```javascript
function serializeLineItem(li) {
return {
id: String(li.id),
label: li.label,
scoreMaximum: li.scoreMaximum,
...(li.resourceLinkId && { resourceLinkId: String(li.resourceLinkId) }),
...(li.tag && { tag: li.tag })
}
}
```
`id` and `resourceLinkId` must be strings. Numeric IDs from your database will fail LTIAAS's schema validation, which is a common first bug here.
## Next Steps
[Scores and results](/connect/guides/services/scores-and-results) — putting grades into these columns.
## Memberships
Source: https://ltiaas.com/connect/guides/services/memberships
# Memberships
The Names and Roles Provisioning Service lets a tool fetch the roster of a course. A tool uses it to show a teacher their class list, to pre-create accounts, or to know who it should expect to see.
One request type is involved: [`MEMBERSHIPS_GET`](/connect/api/memberships-get).
**Caution:**
Requires *Names and Roles* enabled on your account, and `MEMBERSHIPS_READ` in the tool's [permissions](/connect/guides/starting/registering-a-tool#permissions).
## The Request
```json
{
"type": "MEMBERSHIPS_GET",
"parameters": {
"context": "2022CSEa5e6c431b91",
"clientId": "qR8E0iHqSdR30DdfQAbcaBGjKT65"
}
}
```
## Your Response
Return the context and everyone in it.
```javascript
async function handleMemberships(decoded, res) {
const { context } = decoded.parameters
const course = await db.courses.find(context)
if (!course) return res.status(404).json({ error: 'Course not found' })
const enrollments = await db.enrollments.forCourse(course.id)
return res.status(200).json({
context: {
id: course.id,
label: course.code,
title: course.name
},
members: enrollments.map(e => ({
id: e.user.id,
name: e.user.name,
givenName: e.user.firstName,
familyName: e.user.lastName,
email: e.user.email,
roles: [e.isTeacher ? 'CONTEXT_INSTRUCTOR' : 'CONTEXT_LEARNER']
}))
})
}
```
### Fields
| Field | Required | Notes |
|---|---|---|
| `context.id` | yes | Echo back the context you were asked about |
| `context.label`, `context.title` | no | Course code and name |
| `members[].id` | yes | Must match the `user` value you send on launches |
| `members[].roles` | yes | Role keys, not IMS URLs |
| `members[].name`, `givenName`, `familyName`, `middleName`, `email` | no | Filtered by the tool's privacy level |
**Tip:**
`members[].id` has to be the same identifier you pass as `user` when starting a launch. If they differ, the tool cannot match the person in the roster to the person who launched, and features like "grade this student" break in confusing ways.
## Personal Data Is Filtered for You
Send complete records. LTIAAS strips names and emails according to the tool's [privacy level](/connect/guides/starting/registering-a-tool#privacy-level) before the roster reaches it — a `NONE` tool receives IDs and roles only.
It also converts your role keys into the full IMS role URLs, so `CONTEXT_LEARNER` becomes `http://purl.imsglobal.org/vocab/lis/v2/membership#Learner` on the wire. You never write those URLs yourself.
## Large Courses
The roster is returned in one response. For very large enrollments, fetch only the columns you need and consider a short cache — tools tend to poll the roster far more often than it changes.
## Next Steps
- [Line items](/connect/guides/services/line-items) — the grade lines tools create against a course.
- [Scores and results](/connect/guides/services/scores-and-results) — recording grades for the people in this roster.
## Scores and Results
Source: https://ltiaas.com/connect/guides/services/scores-and-results
# Scores and Results
Once a tool has a [line item](/connect/guides/services/line-items), it can write grades to it and read them back. Two request types: [`SCORE_POST`](/connect/api/score-post) to record a grade, and [`RESULTS_GET`](/connect/api/results-get) to read them.
**Caution:**
Requires *Assignment and Grades* enabled on your account, plus `GRADES_WRITE` for posting and `GRADES_READ` for reading in the tool's [permissions](/connect/guides/starting/registering-a-tool#permissions).
## Recording a Score
```json
{
"type": "SCORE_POST",
"parameters": {
"context": "2022CSEa5e6c431b91",
"clientId": "348080fn9du9b9ufvb92rfb9l",
"lineItemId": "412",
"score": {
"userId": "41",
"activityProgress": "Completed",
"gradingProgress": "FullyGraded",
"comment": "Great work!",
"scoreGiven": 99,
"scoreMaximum": 100
}
}
}
```
```javascript
async function handleScore(decoded, res) {
const { context, lineItemId, score } = decoded.parameters
const lineItem = await db.lineItems.find(lineItemId)
if (!lineItem || lineItem.courseId !== context) {
return res.status(404).json({ error: 'Line item not found' })
}
// A score without scoreGiven is a progress update, not a grade.
if (score.gradingProgress === 'FullyGraded' && score.scoreGiven !== undefined) {
await db.grades.upsert({
lineItemId,
userId: score.userId,
score: score.scoreGiven,
maximum: score.scoreMaximum,
comment: score.comment
})
}
await db.activityProgress.upsert({
lineItemId,
userId: score.userId,
activityProgress: score.activityProgress,
gradingProgress: score.gradingProgress
})
return res.status(200).json({})
}
```
Return `200` with an empty JSON object.
**Info:**
Your status code is passed straight back to the tool. Reject a grade with a 4xx and the tool sees the rejection — many will retry later, which is usually what you want for a transient failure.
### Scores Are Not Always Grades
`scoreGiven` is optional. A tool may post a score purely to report progress — "the learner opened this", "the learner submitted, grading is pending" — with no number attached. Recording those without touching the gradebook is what the two progress fields are for.
**`activityProgress`** — how far the learner has got:
| Value | Meaning |
|---|---|
| `Initialized` | The activity exists but has not been started |
| `Started` | The learner has begun |
| `InProgress` | Actively working |
| `Submitted` | Handed in, awaiting grading |
| `Completed` | Finished |
**`gradingProgress`** — how far the tool has got with grading:
| Value | Meaning |
|---|---|
| `NotReady` | Nothing to grade yet |
| `Pending` | Queued for automatic grading |
| `PendingManual` | Waiting for a human |
| `FullyGraded` | Done — the score is final |
| `Failed` | Grading failed |
**Tip:**
Only treat a score as final when `gradingProgress` is `FullyGraded`. Writing `Pending` scores into the gradebook shows learners marks that are about to change.
**Caution:**
When `scoreGiven` is present, `scoreMaximum` is present too — and it may differ from the line item's `scoreMaximum`. Store the pair, or scale as you save. Treating `scoreGiven` as a percentage is a common and expensive mistake.
## Returning Results
```json
{
"type": "RESULTS_GET",
"parameters": {
"context": "2022CSEa5e6c431b91",
"clientId": "348080fn9du9b9ufvb92rfb9l",
"lineItemId": "412",
"filters": { "userId": "14" }
}
}
```
```javascript
async function handleResults(decoded, res) {
const { context, lineItemId, filters = {} } = decoded.parameters
const lineItem = await db.lineItems.find(lineItemId)
if (!lineItem || lineItem.courseId !== context) {
return res.status(404).json({ error: 'Line item not found' })
}
let grades = await db.grades.forLineItem(lineItemId)
if (filters.userId) {
grades = grades.filter(g => g.userId === filters.userId)
}
return res.status(200).json(grades.map(g => ({
id: `${lineItemId}-${g.userId}`,
userId: String(g.userId),
resultScore: g.score,
resultMaximum: g.maximum,
...(g.comment && { comment: g.comment })
})))
}
```
Always return an array, even for a single-user filter. `id` just has to be unique and stable — combining the line item and user IDs is fine.
| Field | Required | Meaning |
|---|---|---|
| `id` | yes | Identifier for the result |
| `userId` | yes | Must match the `user` value used on launches |
| `resultScore` | no | The grade |
| `resultMaximum` | no | What it was out of |
| `comment` | no | Feedback text |
## Grades a Teacher Changed
If a teacher overrides a tool-posted grade in your gradebook, return the override — `RESULTS_GET` should reflect what your gradebook actually holds, not what the tool last sent. Your LMS is the system of record.
## Next Steps
- [Memberships](/connect/guides/services/memberships) — the roster these grades belong to.
- [Error handling](/connect/guides/api/error-handling) — what to return when something goes wrong.
# Connect: using the API
## Using the Connect API
Source: https://ltiaas.com/connect/guides/api/introduction
# Using the Connect API
The guides in this section cover the mechanics that apply across every LTIAAS Connect endpoint: how requests are authenticated in both directions, and how failures are reported.
For the endpoints themselves, see the [Connect API Reference](/connect/api/ltiaas-connect).
## Conventions
**Base URL.** Every endpoint lives on your account's subdomain:
```
https://your.ltiaas.com
```
**Two prefixes.** Launch and ID Token endpoints sit under `/api`; tool management sits under `/admin`. Both use the same API key.
**Successful writes return `201`.** This includes endpoints that feel like reads, such as [Create Launch Form](/connect/api/launch-core-form) — it creates a launch, so it returns `201 Created`. Deletes return `204`.
**Everything is JSON.** Requests and responses are `application/json`, except the two places a browser is involved: the [deep linking return](/connect/api/lti-deeplinking-return) endpoint and the `DEEP_LINKING_RESPONSE` service request, which are form-encoded.
**Forms come back as strings.** The `/form` endpoints return `{ "form": "" }` — a JSON envelope containing HTML, not an HTML response. Render the string; it submits itself.
## Two Directions of Traffic
A Connect integration has requests going both ways, and they authenticate differently:
| Direction | Authentication |
|---|---|
| You → LTIAAS | `Authorization: Bearer ` |
| LTIAAS → You | A signed JWT you verify with your consumer public key |
[Authenticating API requests](/connect/guides/api/authentication) covers both.
## Section Summary
## Authenticating API Requests
Source: https://ltiaas.com/connect/guides/api/authentication
# Authenticating API Requests
Traffic flows both ways in a Connect integration, and each direction is secured differently. Both matter: getting the second one wrong means anyone who finds your Service URL can write grades into your gradebook.
## Calling LTIAAS
Every endpoint you call takes your account API key as a bearer token:
```
Authorization: Bearer ```
For example:
```
Authorization: Bearer df06d55e-3b0f-4121-b60f-c39469b5b550
```
```javascript
const headers = { Authorization: `Bearer ${LTIAAS_API_KEY}` }
const { data } = await axios.post(`${LTIAAS_URL}/api/launch/core/form`, body, { headers })
```
The key is in the [LTIAAS Portal](https://portal.ltiaas.com) under API Settings. It is the same key for `/api` and `/admin` endpoints.
**Caution:**
LTIAAS API endpoints must only be called from your back-end. Every request carries your API key, so a call from the browser leaks full control of your account. Keep the key in your server's environment.
### Failures
A missing or malformed header, or the wrong key, returns `401`:
```json
{
"status": 401,
"error": "Unauthorized",
"details": {
"message": "INVALID_BEARER_AUTHORIZATION_HEADER",
"description": "Invalid API Key for account."
}
}
```
A valid key on the wrong kind of account returns `403 INVALID_ACCOUNT_TYPE` — this is a Launch account being used against Connect endpoints, or the reverse.
## Verifying Requests from LTIAAS
LTIAAS calls your server at three points: your **Launch URL**, your **Deep Linking URL**, and your **Service URL**. Each carries a signed JWT, and **you must verify it before acting on the contents**.
The token is signed **RS256** with your account's private key. Verify it with the consumer public key from the portal.
```javascript
const jwt = require('jsonwebtoken')
// From the portal, stored as an environment variable.
const LTIAAS_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
${process.env.LTIAAS_PUBLIC_KEY}
-----END PUBLIC KEY-----`
let decoded
try {
decoded = jwt.verify(payload, LTIAAS_PUBLIC_KEY)
} catch (err) {
return res.status(401).send('Invalid payload')
}
```
**Caution:**
Never decode without verifying. `jwt.decode()` reads a token without checking its signature — anyone could then forge a launch as any user, or post grades for any learner. Always `jwt.verify()`.
### Where the Token Arrives
| Endpoint | How it arrives |
|---|---|
| Launch URL | `GET`, as the `payload` query parameter |
| Deep Linking URL | `GET`, as the `payload` query parameter |
| Service URL | `POST`, as a `payload` field in a JSON body |
| Service URL, for `DEEP_LINKING_RESPONSE` | `POST`, as a `payload` field in a **form-encoded** body |
### What Is Inside
A launch token:
```json
{
"type": "CORE_LOGIN",
"metadata": "kR2mQ9dLxTn4vB8sYw1e",
"parameters": {
"user": "rKk4PdLgcRbqE4PdSW3iV0KhAmu2",
"context": "0001",
"resource": "14113"
},
"iat": 1700000000,
"exp": 1700000600
}
```
- `type` — `CORE_LOGIN` or `DEEP_LINKING_LOGIN` at your launch URLs; one of the nine [service request types](/connect/guides/services/introduction#the-nine-request-types) at your Service URL.
- `metadata` — pass this straight back on the matching [ID Token](/connect/api/idtoken-core-form) call. Only present on launch tokens.
- `parameters` — the identifiers you supplied when starting the launch, or the details of the service request.
### Expiry
Tokens are valid for **ten minutes**. `jwt.verify()` enforces this for you, which is another reason not to hand-roll the check.
The `metadata` value expires on the same ten-minute clock. Anything slow between receiving the payload and completing the launch — a login prompt, a consent screen — will push you past it. Authenticate users before starting a launch.
## A Note on the Other Keys
`https://your.ltiaas.com/lti/keys` also serves public keys, and they are **not** the ones on this page. That endpoint publishes a per-tool key that *tools* use to verify the ID Tokens LTIAAS signs on your behalf. It has nothing to do with verifying requests sent to you.
- **Consumer public key** (portal) → you verify LTIAAS's requests to you.
- **Keyset** (`/lti/keys`) → tools verify LTIAAS's tokens to them.
## Next Steps
[Error handling](/connect/guides/api/error-handling) — the shape of failures and what the common codes mean.
## Error Handling
Source: https://ltiaas.com/connect/guides/api/error-handling
# Error Handling
Every LTIAAS error has the same envelope: a status, a human-readable error name, and a `details` object carrying a machine-readable code.
```json
{
"status": 403,
"error": "Forbidden",
"details": {
"message": "INACTIVE_SERVICE",
"service": "Deep Linking"
}
}
```
Branch on `details.message`, not on the prose. The extra keys in `details` vary by error and tell you which field, service or value caused it.
## Validation Errors
Malformed requests return `400` with a list of every field that failed, rather than stopping at the first:
```json
{
"status": 400,
"error": "Bad Request",
"details": {
"errors": [
{ "field": "clientId", "message": "Missing 'clientId' parameter." },
{ "field": "user", "message": "Parameter 'user' should be a non-empty string or positive integer." }
]
}
}
```
**Note:**
Nested objects — `user`, `context`, `resource`, `authConfig` — stop validation as soon as they fail, so a bad `user` object may mask problems further down the body. Fix what you are shown, then re-send.
## Common Failures
### `INVALID_ACCOUNT_TYPE` — 403
The account is a Launch account, not a Connect one. Accounts are one or the other; you cannot use Connect endpoints with a Launch key.
### `UNREGISTERED_TOOL` — 404
No tool with that `clientId` on this account. Usually a typo, or a client ID copied from a different environment. [List your tools](/connect/api/tools-list) to check.
### `INACTIVE_TOOL` — 403
The tool exists but is deactivated. [Activate it](/connect/api/tools-activate).
### `INACTIVE_SERVICE` — 403
The service this endpoint needs is off for your account. `details.service` names it. Enable it in the portal under API Settings — and remember the tool also needs the matching [permission](/connect/guides/starting/registering-a-tool#permissions).
### `INVALID_METADATA_PARAMETER` — 400
The `metadata` value is unknown or expired. Metadata lives for **ten minutes** from the start of the launch. If this appears intermittently, something in your Launch URL handler is slow — most often a login or consent step that should happen before the launch begins.
### `INVALID_BEARER_AUTHORIZATION_HEADER` — 401
Missing, malformed or wrong API key. The header must read exactly `Bearer `.
### `INVALID_REDIRECT_URI_PARAMETER` — 400
The tool tried to be redirected somewhere not in its `redirectionUris`. Ask the vendor for their full list and [update the registration](/connect/api/tools-update). This is the most common failure when onboarding a new tool.
### `MONTHLY_TRIAL_LAUNCH_QUOTA_REACHED` — 403
A trial account has used its monthly launches. `details.description` explains. Upgrade to continue.
### `DUPLICATE_NONCE` — 400
The tool replayed a nonce within the 30-second window. Almost always means the user double-clicked or the iframe loaded twice; if it happens on every launch, the tool's OIDC implementation is at fault.
## Errors from Your Own Endpoints
When LTIAAS calls your Launch URL or Service URL and the call fails, the failure is reported with the upstream details attached:
```json
{
"status": 500,
"error": "Internal Server Error",
"details": {
"message": "HTTP Request error: ...",
"externalUrl": "https://yourlms.com/lti-services",
"bodyReceived": "...",
"externalError": true
}
}
```
`externalError: true` means the problem is on your side, not LTIAAS's. `externalUrl` tells you which of your endpoints, and `bodyReceived` shows what you returned — usually enough to spot a schema mismatch immediately.
## Unexpected Errors
A `500` with only an `errorId` means something failed inside LTIAAS:
```json
{
"status": 500,
"error": "Internal Server Error",
"details": { "errorId": "8f3c2a1b9d7e" }
}
```
Send that `errorId` to support — it identifies the logged failure exactly.
## Debugging a Launch That Shows a Blank Frame
Because launches run in an iframe, errors are easy to miss. In order:
1. **Open the failing URL in its own tab.** Errors are JSON and readable once they are not inside a frame.
2. **Check the network tab** for a non-2xx from `your.ltiaas.com` and read its `details.message`.
3. **Log the decoded payload** at your Launch URL. If nothing arrives, the failure is earlier — between the tool and LTIAAS — and the tool's `redirectionUris` are the first thing to check.
4. **Check the tool's own error page.** Once LTIAAS has handed off, a blank frame is often the tool rejecting the ID Token — usually a keyset it cannot reach, or a deployment ID that does not match what it was configured with.
## Next Steps
- [Authenticating API requests](/connect/guides/api/authentication)
- [Connect API Reference](/connect/api/ltiaas-connect)