Before you start
- Create a personal access token with the Read learning activities scope (
learning_activities:read). See Authentication. - The token works only while the Admin who created it is still an Admin. If that person is no longer an Admin, requests return
403: create a new token.
Check if a learner completed a learning item
Send the learnerâs email and the learning itemâs ID:completed_at is set. That includes completions an Admin recorded with Mark as completed for a learner who already had the item (assigned or started). Email matching ignores case. An email that doesnât belong to a learner in your organization returns an empty data list.
A learner can have more than one row for the same learning item, for example one per live session they attended, or one per place they took it (on its own and inside a journey); consumption_context_id tells those apart. Treat the item as completed when any row has completed_at set, and count learners by distinct user_id, not by rows.
Status values
SCORM learning ends as
completed, not passed. A quiz ends as passed or failed, and gets completed_at only when itâs passed or an Admin marks it as completed.Request body
All fields are optional. A request with an empty body returns the first page of every learning activity in your organization.Filters
Each filter takes a single value or{ "$in": [ ... ] } with up to 500 values. Combine filters to narrow further.
Date filters take from (on or after) and to (on or before), the same as the xAPI search endpoint. Values are ISO 8601: a date-time with Z or an offset (2026-10-01T09:30:00Z, 2026-10-01T12:30:00+03:00), or a date (2026-10-01), which means midnight UTC at the start of that day. So "to": "2026-10-31" stops at the first moment of October 31: to include the whole day, use "to": "2026-10-31T23:59:59.999999Z".
Any other filter name or operator returns
400.
Fields returned
Dates are ISO 8601 in UTC. A field with no value is
null.
Page through results
Results are sorted bylast_activity_at, oldest first. Rows with no activity yet come first. Every response returns a cursor. Send it back to get the next page, and stop when a page has fewer rows than your limit:
activity_id. A page with no rows returns the cursor you sent.
Use the cursor to page through one run. Donât save it to resume a later run: activity can reach the API after rows with a later last_activity_at, and a saved cursor would skip it. For later runs, follow Keep your system in sync.
Keep your system in sync
To copy learner progress into another system on a schedule:1
Load everything once
Page through with no filters and store every row by
activity_id.2
Remember where you stopped
Keep the highest
last_activity_at you received.3
Fetch what changed
On each run, filter
"last_activity_at": { "from": "<that value minus 1 hour>" }, page through, and update rows by activity_id. The hour of overlap catches changes that reach the API a little late.last_activity_at moves forward when a learnerâs progress on the item changes: an assignment, a start, progress, a completion, or a new due date. A change to the learnerâs profile alone, such as a new name or email, doesnât move it. When an assignment is removed, its row stops appearing in results.
Data freshness
Learning activity usually reaches the API within about 15 minutes of happening in Juno, completions recorded with Mark as completed included (for learners who already had the item). Every response includesdataAsOf, the time the data was last updated. If you need an answer right after a learner finishes, allow for this delay, for example by checking again later.
Limits and errors
Each token can make 60 requests per minute. Above that, requests return429 until the minute resets; the Retry-After header says when to try again.

