Migrate importExternalCoursesStats
This guide details how to migrate from the v1 importExternalCoursesStats endpoint to the new v2 architecture.
Key changes
- From synchronous to asynchronous processing: API v2 processes statistics imports asynchronously, allowing bulk operations of up to 10,000 records per request.
- Simplified field structure: v2 consolidates activity tracking with clearer semantics and removes complex auto-calculation scenarios.
- Enhanced control: v2 introduces the
forceNewparameter to explicitly control whether statistics create new attempts or update existing ones.- Flexible user and course identification: v2 introduces identifier objects that allow you to reference courses and users by either external or internal IDs
Documentation links
- API v1 reference
- API v2 reference: Import integration statistics
Endpoint mapping
- v1:
/api/v1/externalContents/groups/:groupId/externalPlatforms/:externalPlatform/courses/stats - v2:
/api/v2/bulk/integrations/{integrationId}/stats
Prerequisites
Before migrating to v2, you must have an integration configuration created. If you haven't done this yet, see step 2 in Create an API-based integration for instructions on creating an integration using the POST /api/v2/integrations endpoint.
You'll need your integrationId (the _id returned when creating the integration).
Main input changes
| Change type | API v1 | API v2 |
|---|---|---|
| Modified Platform context | Path variables: groupId and externalPlatform | Path variable: integrationId (contains both platform and group context) |
| Modified | statistics (max 20 objects) | input (max 10,000 objects): Renamed. |
| Modified | externalId | courseIdentifier.value: Now nested in object with type field |
| Modified | mail | userIdentifier.value: Now nested in object with type field |
| Modified | globalTime | timeSpent: Renamed and now required; still in milliseconds |
| Modified | completedAt | lastActivityAt: Renamed and now required; when progress=100, this represents completion date |
| Modified | firstActivity | firstActivityAt: Renamed and now required |
| Modified | lastActivity | lastActivityAt: Renamed and now required |
| Added | courseIdentifier.type: New field: "externalId" or "internalId" | |
| Added | userIdentifier.type: New field: "mail" or "internalId" | |
| Added | forceNew: New field: controls attempt creation vs. update behavior | |
| Added | lang: New field: language in which learner played the course |
Behavioral changes
v1: Complex auto-calculation logic
v1 had four scenarios for calculating activity timestamps and globalTime.
v2 removes all auto-calculation logic. You must provide all required fields explicitly:
progress(0-100)timeSpent(in milliseconds)firstActivityAt(ISO 8601 datetime)lastActivityAt(ISO 8601 datetime)
Attempt creation/update logic
v2 uses the forceNew parameter to control behavior:
forceNew: true→ Always creates a new attemptforceNew: false(default) → Updates existing non-completed attempts OR creates new attempt if:- No attempt exists for this course/user combination
firstActivityAtis later than all existing attempts'lastActivityAtorcompletedAt
Main output changes
API v2 shifted from synchronous to asynchronous operations. Instead of an immediate result (200 OK), it returns a 202 Accepted status code indicating the bulk request has been queued. The full result must be tracked via the Location header.
Updated 4 months ago

