Migrate importExternalCourses

This guide details how to migrate from the v1 importExternalCourses endpoint to the new v2 architecture.

šŸ’”

Key changes

  • From a single request to a two-step proces: API v2 decouples the environment setup from the data transfer.
  • Asynchronous bulk operation: Unlike the synchronous v1 response, Step 2 handles up to 10,000 objects asynchronously. You will receive a 202 Accepted status and must poll the URL provided in the Location header to retrieve the final import results.

Documentation links

Here are the links to the API reference for:

Endpoint mapping

Here's the direct correlation between the v1 and v2 endpoint URLs:

  • API v1: POST /api/v1/externalContents/groups/:groupId/externalPlatforms/:externalPlatform/courses
  • API v2:
    • Step 1 (Configure): POST /api/v2/integrations
    • Step 2 (Sync): PUT /api/v2/bulk/integrations/{integrationId}/courses

Step 1 (Configure): Create an integration configuration

In v1, you had to define the platform, group, and author context for every single import. In v2, you define these once to create a persistent environment.

Target URL: POST /api/v2/integrations

Example v2 request (Step 1)

curl --location 'https://app.360learning.com/api/v2/integrations' \
--header '360-api-version: v2.0' \
--header 'Authorization: Bearer YOUR_OAUTH_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
    "externalPlatform": "myPlatform",
    "groupId": "62be0535045424d93edaad7b",
    "authorId": "62bdbc00293a604543f44386",
    "externalPlatformLogoUrl": "https://upload.wikimedia.org/wikipedia/commons/0/07/Wikipedia_logo_%28svg%29.svg",
    "defaultCompletion": true,
    "defaultProgress": 0,
    "noGroupPropagation": false
}'

Main input differences (Step 1)

Change type

API v1

API v2

Modified
Parameter

externalPlatform: Path parameter.

externalPlatform: Body parameter (serves as unique identifier for the configuration).

Modified
Parameter

groupId: Path parameter.

groupId: Body parameter (defines the root group where all content will be stored).

Modified
Parameter

authorId: Body parameter defined per batch.

authorId: Body parameter defined once as the default author for the entire integration.

Modified
Body parameter

externalPlatformLogoUrl: Single URL.

externalPlatformLogoUrl (Standard) AND externalPlatformUnicolorLogoUrl (for banner contrast).

Added
Header parameter


360-api-version (Required): API version identifier. Example: v2.0.

Added
Body parameter


noGroupPropagation: true to restrict content to the target group; false to share with all subgroups.

Added
Body parameter


defaultCompletion: true if the content is auto-completed at launch; false otherwise.

Added
Body parameter


defaultProgress: The completion rate at launch as a percentage.

Example v2 response (Step 1)

{
    "_id": "64a1b2c3d4e5f6a7b8c9d0e1",
    "externalPlatform": "myPlatform",
    "groupId": "62be0535045424d93edaad7b",
    "authorId": "62bdbc00293a604543f44386",
    "defaultCompletion": true,
    "defaultProgress": 0,
    "noGroupPropagation": false,
    "createdAt": "2023-07-02T10:30:00.000Z"
}
šŸ’”

Save the _id from the response: This is your integrationIdfor Step 2.

Step 2 (Sync): Bulk upsert content

Once your configuration is created, you use the resulting _id (the integrationId) to perform content synchronizations.

Target URL: PUT /api/v2/bulk/integrations/{integrationId}/courses

Example v2 request (Step 2)

curl --location --request PUT 'https://app.360learning.com/api/v2/bulk/integrations/64a1b2c3d4e5f6a7b8c9d0e1/courses' \
--header '360-api-version: v2.0' \
--header 'Authorization: Bearer YOUR_OAUTH_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
    "input": [
        {
            "externalId": "l3ssup3rh3r0s",
            "defaultLang": "en",
            "name": "Machine Learning Crash Course",
            "description": "Learn the basics of machine learning.",
            "launchUrl": "https://player.mylearningplatform.com/ql/fg-q6dfax2e_6ys",
            "mobileLaunchUrl": "https://player.mylearningplatform.com/mobile/fg-q6dfax2e_6ys",
            "imageUrl": "https://upload.wikimedia.org/wikipedia/commons/0/07/Wikipedia_logo_%28svg%29.svg",
            "difficultyLevel": "beginner",
            "contentType": "course",
            "duration": 10,
            "subjects": ["Machine learning"],
            "sources": [
                {
                    "name": "Wikipedia",
                    "logoUrl": "https://en.wikipedia.org/static/images/icons/wikipedia.png"
                }
            ],
            "translations": [
                {
                    "lang": "fr",
                    "translatedFields": {
                        "name": "Cours accƩlƩrƩ sur l'apprentissage automatique",
                        "description": "Apprenez les bases de l'apprentissage automatique.",
                        "subjects": ["Apprentissage automatique"]
                    }
                }
            ]
        }
    ]
}'

Main input differences (Step 2)

Change type

API v1

API v2

Removed

numberOfVideos


Removed

skills


Modified

externalCourses: Array of course objects

input: Array name changed, up to 10,000 per request

Modified

title

name: Renamed

Modified

thumbnailUrl

imageUrl: Renamed

Modified

courseDuration

duration: Renamed

Modified

language

defaultLang: Renamed

Modified

authors: Array of strings

authorId: single string

Modified

alternativeLanguages: array of strings

translations: Array of objects. Each translation requires a full translatedFields object with translated content. Any field not provided in translatedFields falls back to the defaultLang values.

Modified

sources.logo

sources.logoUrl: Renamed

Added
Header parameter


360-api-version (Required): API version identifier. Example: v2.0.

Added
Path parameter


integrationId: Context passed via this path variable.

Main output differences (Step 2)

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.