Guide: SALTIRE 1.3 integration

saLTIre is a free, open-source LTI test tool that can act as either an LTI Tool or Platform to help us validate LTI 1.3 launches and Advantage services.

1. Test integration via LTI 1.3

  1. Visit https://saltire.lti.app/ .

  2. Select Test Tool option.

    1. image-20260129-045451.png
  3. Go to “Options” > “Sign in”. Sign in with google.

  4. You’ll see a form on the “Security model” page.

    1. image-20260129-045837.png
  5. Select “Signature method” as “LTI 1.3 > RS256”.

  6. In “Tool details” section, check the box in “Public Keyset URL”. Leave the rest of tool details as is.

    1. image-20260129-051233.png
  7. For “Platform details” section, create an LTI Consumer xblock in Open edX, select “LTI version” as 1.3 and click “Save”. After saving, it’ll show a bunch of information.

    1. image-20260129-052234.png
  8. Copy paste information from xblock to “Platform details” section in saLTIre, as seen in the image below. Leave “Authorization server ID”, “Public Key” and “Public Key ID” blank.

    1. image-20260129-052638.png
  9. Click “Save”. A pop will appear showing list of registered platforms.

  10. Turn ON the “Public?” toggle. This step is required for integration to work.

    1. image-20260129-062508.png
  11. Rename the platform you just saved if needed and close the pop up. Your platform configurations are now saved in saLTIre.

  12. Click “Save” on top right of the page to save platform and tool configurations.

    1. image-20260210-043852.png
  13. Now edit the Xblock again and copy paste the information from the “Tool details” section above, into the Xblock parameters, as shown in the table below (click to expand). Click “Save”.

#

xblock field

Value

1

Display name

Any

2

LTI Application Information

Any

3

LTI Version

LTI 1.3

4

Tool launch URL

https://saltire.lti.app/tool

5

Registered Redirect URIs

["https://saltire.lti.app/tool "]

6

Tool Initiate Login URL

https://saltire.lti.app/tool

7

Tool Public Key Mode

Key Set URL

8

Tool Keyset URL

https://saltire.lti.app/tool/jwks/...

9

Enable LTI NRPS

False (Default)

10

Deep linking

False (Default)

11

Deep Linking Launch URL

-

12

LTI Assignment and Grades Service

Disabled

13

Custom Parameters

-

14

LTI Launch Target

Inline (Default)

15

Button Text

-

16

Inline Height

800

17

Modal Height

80

18

Modal Width

80

19

Scored

False (Default)

20

Weight

1

21

Hide External Tool

False (Default)

22

Accept grades past deadline

True (Default)

23

Send extra parameters

False (Default)

  1. Publish the Xblock.

  2. View the Xblock from LMS with any Open edX role. The tool should load as seen in the image below.

    1. image-20260129-062940.png

2. Test Advantage services

Open edX supports 3 advantage services:

  1. Deep Linking

  2. Assignment and Grade Services (AGS)

  3. Names and Roles Provisioning Service (NPRS)

2.1 Test Deep Linking (without AGS & NPRS)

  1. Edit xblock. Set “Deep linking” to True and add URL for “Deep Linking Launch URL” from saltire. Click “Save”.

    1. image-20260129-065614.png
  2. Publish the unit.

  3. Click “Deep Linking Launch - Configure tool” hyperlink at the end of published xblock.

    1. image-20260129-065854.png
  4. It should open saltire in new window. Message “Type” should be LtiDeepLinkingRequest.

    1.  

  5. Click on “Sample Content” in the header. Check ONLY the first option, scroll to the bottom and click “Return selection”.

    1. image-20260129-085433.png
  6. You should see a message saying “The LTI Deep Linking content was successfully saved in the LMS.”

  7. Reload the unit page in Studio. The xblock should say “Deep Linking is configured on this tool.”

    1. image-20260129-090057.png
  8. Now launch the unit in LMS using any role. The tool should load with message type LtiResourceLinkRequest and verification Passed.assed

    1. image-20260129-090454.png

2.2 Assignment & Grade Service (AGS)

  1. Configure the xblock so that it advertises AGS endpoints at launch.

    1. In Open edX Studio, edit the xblock. Set “LTI Assignment and Grades Service” to Allow tools to manage and submit grade (programmatic).

    2. Save and publish the unit.

  2. Fetch the Oauth2 token using these steps, if not already fetched.

  3. Launch the unit in LMS using any role.

  4. From header, select “Services” > “Assignment And Grade Services”.

  5. You’ll see 3 services in AGS. We’ll go through each of these below.

    1. image-20260130-133303.png

2.2.1 Line Item Service

This service will allows us to test if Open edX platform:

  • Advertises AGS endpoints/scopes correctly in the launch JWT.

  • Authorizes line item operations based on scopes (especially scope/lineitem).

  • Correctly implement CRUD for gradebook columns (create/list/read/update/delete).

  • Correctly handle metadata like resourceId, resourceLinkId, tag, scoreMaximum.

Here are the steps that i took to use this service:

  1. Open “Line item service”.

    1. image-20260129-114818.png
  2. Add any values for Label, Possible points, Tag and Resource ID. Leave Resource Link ID and Release grades unchanged.

  3. Click “Create”. This should create an AGS line item in Open edX.

  4. To see the line item that has been created:

    1. See the “Lti ags line items” section in djagno admin panel.

      1. image-20260130-092146.png
    2. Select radio button in “Line item”, edit the URL with the number of line item you want to see (5 in this case) and click “Read”.

      1. image-20260130-092820.png
  5. Update the line item using the same process as read.

  6. Delete is rejected by the LMS with a message “You do not have permission to perform this action.” Even with an admin LMS account.

2.2.2 Score publish service

This service will allows us to test if Open edX platform:

  • Accepts incoming score submissions and validate required fields?

  • Does it correctly associate scores to a user + line item and update grade state?

Here are the steps that i took to use this service:

  1. Score publish service shows an endpoint that is not editable. You can edit this field by editing the “Line item” field in Line item service.

  2. Once you’ve selected the end point, add all the other data including comment and click Create/update.

  3. You can see the score you just published in section named “Lti ags scores” in django admin panel.

    1. image-20260130-110639.png

2.2.3 Results Service

This service will allows us to test if Open edX platform:

  • Does the platform store and expose outcomes back to tools after scores are submitted?

  • Does it enforce read permissions via scope/result.readonly?

  • Does it return results in the expected shape and support filtering/pagination.

I’m unable to get results service to work. I get an error when trying to read the results: “The data (null) must match the type: array”.

The problem i think is that Open edX redirects /results/results/ (301) but saLTIre expects JSON array immediately and doesn’t follow the redirect, so it tries to validate HTML/empty as an array and fails.

So i called the API using my local machine (after configuring token) and it returned the scores.

Call

aamir.ayub@A006-01388 ~ % curl -i \ -H "Authorization: Bearer $TOKEN" \ -H "Accept: application/vnd.ims.lis.v2.resultcontainer+json" \ "https://ulmo.openedx.io/api/lti_consumer/v1/lti/6/lti-ags/7/results/"

Result

HTTP/2 200 allow: GET, HEAD, OPTIONS alt-svc: h3=":443"; ma=2592000 content-language: en content-type: application/vnd.ims.lis.v2.resultcontainer+json server: Caddy set-cookie: sessionid=1|0ddlbbl4zok8w9hwvwm2xhkqmbvlaepu|QJogqKAGBxUw|IjVkNDViYjMxOWUwMTc3MTM5YWZiNmU4NGVhMGUyjkwYzhmODZkZDM5NzllNDdmZmNjY2ZhYzMxZDgwODJiM2Mi:1vlmeI:Iennvx_GDIviuk1p0feciVVWL3EN6LveQlGXX5qnMk; Domain=ulmo.openedx.io; expires=Fri, 13 Feb 2026 11:26:50 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=None; Secure vary: Accept-Language, origin, Cookie x-frame-options: SAMEORIGIN content-length: 300 date: Fri, 30 Jan 2026 11:26:50 GMT [{ "id": "https://ulmo.openedx.io/api/lti_consumer/v1/lti/6/lti-ags/7/results/1bedb02f-413c-4d5b-8d7c-9333f0f6fd65", "scoreOf": "https://ulmo.openedx.io/api/lti_consumer/v1/lti/6/lti-ags/7", "userId": "1bedb02f-413c-4d5b-8d7c-9333f0f6fd65", "resultScore": 23, "resultMaximum": 98, "comment": "Some comment" }]

2.3 Names and Roles Provisioning Service (NPRS)

  1. Configure the xblock so that it advertises NPRS endpoint at launch.

    1. In Open edX Studio, edit the xblock. Set “Enable NPRS” to True.

    2. Save and publish the unit.

  2. Launch saltire by navigating to the unit in LMS using any role.

  3. Fetch the Oauth2 token using these steps, if not already fetched.

  4. From header, select “Services” > “Names and Role Provisioning Service”.

  5. “Endpoint” should be selected by default.

  6. Select a filter if you need to.

  7. Click “Read”.

  8. A modal titled “Service Response” should show up with count of members returned in the API call.

  9. View request message > “Formatted Response Body” to verify users and their roles.

    1. image-20260204-125032.png

Call

GET /api/lti_consumer/v1/lti/6/memberships HTTP/1.1 Host: ulmo.openedx.io Authorization: Bearer Accept: application/vnd.ims.lti-nrps.v2.membershipcontainer+json User-Agent: saLTIre/3.3.1 (from ceLTIc Project)

Response

HTTP/1.1 200 OK Allow: GET, HEAD, OPTIONS Alt-Svc: h3=":443"; ma=2592000 Content-Language: en Content-Length: 1079 Content-Type: application/vnd.ims.lti-nrps.v2.membershipcontainer+json Server: Caddy Set-Cookie: sessionid=1|9ro0c; Domain=ulmo.openedx.io; expires=Wed, 18 Feb 2026 13:01:10 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=None; Secure Vary: Accept-Language, origin, Cookie X-Frame-Options: SAMEORIGIN Date: Wed, 04 Feb 2026 13:01:10 GMT { "id": "https://ulmo.openedx.io/api/lti_consumer/v1/lti/6/memberships", "context": { "id": "course-v1:edx+LTI101+2026" }, "members": [ { "status": "Active", "user_id": "91a4dd76-781d-4050-a8a5-aeb983849c67", "roles": [ "http://purl.imsglobal.org/vocab/lis/v2/membership#Learner", "http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor", "http://purl.imsglobal.org/vocab/lis/v2/membership#Administrator" ], "name": "admin", "email": "aayub@axim.org" }, { "status": "Active", "user_id": "1bedb02f-413c-4d5b-8d7c-9333f0f6fd65", "roles": [ "http://purl.imsglobal.org/vocab/lis/v2/membership#Learner", "http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor" ], "name": "aamir ayub", "email": "aamir.ayub@arbisoft.com" }, { "status": "Active", "user_id": "f8f5dc0b-8b3a-4d9c-991b-4c883ff0a82f", "roles": [ "http://purl.imsglobal.org/vocab/lis/v2/membership#Learner" ], "name": "learner 1", "email": "aayub02@gmail.com" }, { "status": "Active", "user_id": "97e3b58a-28a0-49dd-a5b8-4ec9e053b7f1", "roles": [ "http://purl.imsglobal.org/vocab/lis/v2/membership#Learner" ], "name": "Open edX LTI test", "email": "openedx-lti-test@axim.org" } ] }

2.4. Fetch Oauth2 token

This step is needed to test AGS and NPRS.

  1. Navigate to “Oauth2 Access Token” in “Services” menu in saltire header.

  2. Leave the Endpoint at default value.

  3. Check the following scopes:

    1. https://purl.imsglobal.org/spec/lti-ags/scope/lineitem (for AGS).

    2. https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly (for AGS).

    3. https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly (for AGS).

    4. https://purl.imsglobal.org/spec/lti-ags/scope/score (for AGS).

    5. https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly (for NPRS).

  4. Click “Request new token”.

  5. You should see the “Access token” field populate with the new token.

 

Other Findings

I am launching saltire as a Student and Open edX is still advertising AGS manage scopes to that launch.

Header

{ "alg": "RS256", "kid": "ca379a15-9e43-48c1-8ca1-92520fbb2785", "typ": "JWT" }

Payload

{ "https://purl.imsglobal.org/spec/lti/claim/message_type": "LtiResourceLinkRequest", "https://purl.imsglobal.org/spec/lti/claim/version": "1.3.0", "iss": "https://ulmo.openedx.io", "aud": "97383ca7-8e2c-4143-87fc-7b9d05b6c5b4", "azp": "97383ca7-8e2c-4143-87fc-7b9d05b6c5b4", "https://purl.imsglobal.org/spec/lti/claim/deployment_id": "1", "https://purl.imsglobal.org/spec/lti/claim/target_link_uri": "https://saltire.lti.app/tool", "sub": "f8f5dc0b-8b3a-4d9c-991b-4c883ff0a82f", "https://purl.imsglobal.org/spec/lti/claim/roles": [ "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Student" ], "https://purl.imsglobal.org/spec/lti/claim/resource_link": { "id": "block-v1:edx+LTI101+2026+type@lti_consumer+block@25751c54216343a188a4fa07e3be96b2" }, "https://purl.imsglobal.org/spec/lti/claim/launch_presentation": { "document_target": "iframe" }, "https://purl.imsglobal.org/spec/lti/claim/context": { "id": "course-v1:edx+LTI101+2026", "type": [ "http://purl.imsglobal.org/vocab/lis/v2/course#CourseOffering" ], "title": "LTI testing - aayub - edx", "label": "course-v1:edx+LTI101+2026" }, "https://purl.imsglobal.org/spec/lti/claim/tool_platform": { "guid": "76834479-ae8b-5249-a190-842b90f777ee", "name": "Ulmo" }, "https://purl.imsglobal.org/spec/lti/claim/custom": { "fullname": "$Person.name.full", "lusername": "$User.username", "page": "103", "last": "95" }, "https://purl.imsglobal.org/spec/lti-ags/claim/endpoint": { "scope": [ "https://purl.imsglobal.org/spec/lti-ags/scope/lineitem", "https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly", "https://purl.imsglobal.org/spec/lti-ags/scope/score" ], "lineitems": "https://ulmo.openedx.io/api/lti_consumer/v1/lti/6/lti-ags", "lineitem": "https://ulmo.openedx.io/api/lti_consumer/v1/lti/6/lti-ags/4" }, "nonce": "WQvo1jAY1K8vwmtDfPmQmiGIPwLA4eHW", "iat": 1769678039, "exp": 1769681639 }