Skip to main content
Use the learning activities endpoint to answer “did this learner complete this learning item?” in one call, or to keep your own system (an HRIS, ServiceNow, a data warehouse) in step with learner progress. It returns one row per learner per learning item: assignment, progress, completion, and score, as they are now.

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:
The learner completed the item when 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.
Each row is the learner’s current state for the item, and date filters match current values. If a completion is later reset, reopened or retaken, or the assignment is removed, the earlier completion no longer appears. Use this endpoint for the latest status, not for history such as “how many learners completed this on a past date”.

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.
Select only the fields you use. Smaller responses come back faster.

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".
For back-to-back windows, use $gte (on or after) and $lt (before) instead: $lt excludes its own value, so no row falls into two windows. Use one style per date filter: mixing from/to with $gte/$lt returns 400.
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 by last_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:
Each row appears once while you page. If a learner makes progress while you page, their row can appear again later with the newer values: update rows by 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 includes dataAsOf, 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 return 429 until the minute resets; the Retry-After header says when to try again.
Last modified on October 20, 2026