Grade Sync via AGS
The Assignment and Grade Services (AGS) specification is the LTI 1.3 standard for sending scores from a tool back to the LMS gradebook. TutorFlow implements AGS to sync scores when an internal LTI grade-sync job is queued for a launch session.
How AGS Works
AGS defines a REST API that the LMS exposes for managing line items (grade columns) and posting scores. During an LTI launch, the LMS includes an AGS claim in the JWT that provides the endpoint URLs and scopes available to TutorFlow.
AGS Claim in the Launch Token
{
"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/score",
"https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly"
],
"lineitems": "https://canvas.instructure.com/api/lti/courses/1/line_items",
"lineitem": "https://canvas.instructure.com/api/lti/courses/1/line_items/42"
}
}| Field | Description |
|---|---|
scope | The AGS operations TutorFlow is authorized to perform |
lineitems | URL to manage all line items for the course (optional) |
lineitem | URL for the specific line item linked to this assignment |
Score Sync Flow
When a grade-sync job runs for an LTI launch session, the following happens:
1. Student launches TutorFlow via LTI
-> TutorFlow stores AGS endpoint and credentials in the launch session
2. A TutorFlow backend flow queues platform-lti-grade-sync for that launch session
-> The job context identifies the launch session
3. TutorFlow requests an OAuth2 access token from the LMS
POST {tokenEndpoint}
grant_type=client_credentials
scope=https://purl.imsglobal.org/spec/lti-ags/scope/score
4. TutorFlow posts the score to the LMS line item
POST {lineitem}/scores
Authorization: Bearer {access_token}
5. LMS updates the gradebookTutorFlow handles steps 3 through 5 inside the grade-sync job.
Triggering Grade Sync from the REST API
If you are building a custom integration where evaluations are created via the REST API (rather than through the LTI UI), you can persist the launch session reference on the evaluation request.
Include the ltiLaunchSessionId in the clientMetadata field of your evaluation request:
curl -X POST https://api.tutorflow.io/v1/platform/evaluations \
-H "Authorization: Bearer tf_platform_..." \
-H "Content-Type: application/json" \
-d '{
"evaluationType": "open_ended",
"questionText": "Explain the concept of supply and demand",
"learnerAnswer": "Supply and demand is the relationship between...",
"language": "en",
"maxScore": 10,
"clientMetadata": {
"ltiLaunchSessionId": "session_abc123"
}
}'The current REST evaluation endpoint stores this metadata for correlation. It does not
currently enqueue LTI grade sync by itself. Agents should treat
clientMetadata.ltiLaunchSessionId as correlation data until a public grade-sync trigger
is added.
Where to Get the Launch Session ID
After a successful launch, TutorFlow redirects the browser to:
{LTI_TOOL_REDIRECT_BASE_URL}/agent-platform/lti/launch?sessionId={sessionId}Read the sessionId query parameter inside your embedded tool UI and pass it to your
backend if you need to correlate REST-created evaluations with the LTI session.
Score Format
TutorFlow posts scores using the AGS Score service format:
{
"scoreGiven": 8.0,
"scoreMaximum": 10.0,
"activityProgress": "Completed",
"gradingProgress": "FullyGraded",
"userId": "lti-user-sub-claim",
"timestamp": "2026-03-25T12:05:00Z"
}Score Fields
| Field | Type | Description |
|---|---|---|
scoreGiven | number | The score from the grade-sync job payload, or 0 if no score is provided |
scoreMaximum | number | The maximum score from the grade-sync job payload, or 100 if no maximum is provided |
activityProgress | string | The student's progress on the activity |
gradingProgress | string | The grading status |
userId | string | The sub claim from the LTI launch token (identifies the student in the LMS) |
timestamp | string | ISO 8601 timestamp of when the score was recorded |
Activity Progress Values
| Value | When Used |
|---|---|
Initialized | Launch started but no submission yet |
Started | Student has begun the activity |
InProgress | Student is working on the activity |
Submitted | Student submitted but grading is pending |
Completed | Activity and grading are both finished |
Grading Progress Values
| Value | When Used |
|---|---|
FullyGraded | Evaluation completed successfully |
Pending | Evaluation is still processing |
PendingManual | Requires manual review (not currently used) |
Failed | Evaluation failed (score not posted to LMS) |
NotReady | Not yet ready for grading |
Score Mapping
TutorFlow maps the grade-sync job payload to AGS scores as follows:
| Job payload or fallback | AGS Field |
|---|---|
score, fallback 0 | scoreGiven |
maxScore, fallback 100 | scoreMaximum |
| Grade-sync job run | activityProgress: Completed, gradingProgress: FullyGraded |
| Job failure | Score is not posted, lti.grade_sync_failed is emitted after retries are exhausted |
Error Handling
If the grade sync fails (e.g., the LMS token endpoint is unreachable or the line item URL has expired), TutorFlow retries with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 5 seconds |
| 2nd retry | 25 seconds |
After the initial attempt plus 2 retries (3 total attempts), the sync is marked as
failed. The evaluation itself remains in the COMPLETED state. To check whether the
grade sync succeeded, subscribe to the
lti.grade_synced and lti.grade_sync_failed webhook events.
Webhook Events
When grade sync completes or fails, TutorFlow emits webhook events if you have Webhooks configured:
| Event | Description |
|---|---|
lti.grade_synced | Score successfully posted to LMS |
lti.grade_sync_failed | Grade sync failed after all retries |
Example Webhook Payload
{
"event": "lti.grade_synced",
"sessionId": "lti-launch-session-uuid",
"resourceLinkId": "lms-resource-link-id"
}Failure deliveries use this payload shape:
{
"event": "lti.grade_sync_failed",
"sessionId": "lti-launch-session-uuid",
"error": "AGS score sync failed with 401: invalid token"
}