NAV
shell

Introduction

Our API lets you integrate Sana into your existing platforms. We're still actively developing the API and will be adding more functionality as we go.

Authentication

Request Access Token

Use client credentials to request access tokens. All requests need to be authenticated using an access token.

The retrieved access token can authenticate requests using the authorization header like Authorization: Bearer <accessToken>.

curl "https://<domain>.sana.ai/api/token" \
    -X POST \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=client_credentials&client_id=<client-id>&client_secret=<client-secret>&scope=read,write" \

The above command returns JSON structured like this:

{
  "data": {
    "accessToken": "<access-token>",
    "tokenType": "bearer",
    "expiresIn": 3600
  }
}

HTTP Request

POST https://<domain>.sana.ai/api/token

Body Parameters

Parameter Description
grant_type Must be set to client_credentials
client_id The ID of the client
client_secret The secret of the client
scope Comma-separated list of scopes. GET requests require read scope. POST, PATCH, and DELETE requests require write scope.

Users

A user can be in 3 states in Sana.

Parameter Description
active The user can login to Sana and use the platform
pending The user has been added to Sana either through an invite, API or user provisioning flow, but the user hasn't completed the signed up.
disabled The user is soft-deleted and cannot login to Sana

In the following API responses

List All Users

curl "https://<domain>.sana.ai/api/v0/users?limit=1" \
  -H "Authorization: Bearer <accessToken>"

The above command returns JSON structured like this:

{
  "data": [
    {
      "id": "eb87a2dc-3e78-4d0b-b868-9d3f49c64f68",
      "email": "sana@sanalabs.com",
      "firstName": "Sana",
      "lastName": "Banana",
      "role": "learner",
      "disabled": false,
      "status": "active",
      "customAttributes": {},
      "createdAt": "2023-02-21T13:00:11.851167Z",
      "activatedAt": "2023-02-22T13:00:10.342157Z",
      "lastInviteSentAt": null,
      "isSsoEmail": true
    }
  ],
  "links": {
    "next": "https://<domain>.sana.ai/api/v0/users?next=9399b472-199b-4d18-97d9-819c7e1bf22f&limit=1"
  },
  "error": null
}

This endpoint retrieves all users

HTTP Request

GET https://<domain>.sana.ai/api/v0/users

Query Parameters

Parameter Type Required Default Description
next String No - Set to fetch the next batch of users.
limit Integer No 100 The number of users to return. Max: 1000

Get a User

curl "https://<domain>.sana.ai/api/v0/users/eb87a2dc-3e78-4d0b-b868-9d3f49c64f68" \
  -H "Authorization: Bearer <accessToken>"

The above command returns JSON structured like this:

{
  "data": {
    "id": "eb87a2dc-3e78-4d0b-b868-9d3f49c64f68",
    "email": "sana@sanalabs.com",
    "firstName": "Sana",
    "lastName": "Banana",
    "role": "learner",
    "disabled": false,
    "status": "active",
    "customAttributes": {},
    "createdAt": "2023-02-21T13:00:11.851167Z",
    "activatedAt": "2023-02-22T13:00:10.342157Z",
    "lastInviteSentAt": "2023-02-21T13:00:11.871360Z",
    "isSsoEmail": true
  },
  "error": null
}

This endpoint retrieves a specific user

HTTP Request

GET https://<domain>.sana.ai/api/v0/users/<userId>

URL Parameters

Parameter Type Description
userId String The ID of the user to retrieve

Create a User

curl "https://<domain>.sana.ai/api/v0/users" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <accessToken>" \
  -d `{ \
        "user": { \
            "email": "sana@sanalabs.com", \
            "firstName": "Sana", \
            "lastName": "Banana", \
            "language": "en" \
        } \
      }`

The above command returns JSON structured like this:

{
  "data": {
    "id": "eb87a2dc-3e78-4d0b-b868-9d3f49c64f68",
    "email": "sana@sanalabs.com",
    "firstName": "Sana",
    "lastName": "Banana",
    "role": "learner",
    "status": "pending",
    "language": "en",
    "inviteLink": "https://<domain>.sana.ai/accept-invite?inviteCode=<code>",
    "customAttributes": {},
    "createdAt": "2023-02-21T13:00:11.851167Z",
    "activatedAt": null
  },
  "error": null
}

The inviteLink should be sent to the user to complete the sign up

This endpoint creates a user

HTTP Request

POST https://<domain>.sana.ai/api/v0/users

Body Parameters

Parameter Type Required Default Description
email String Yes - The user's email address
firstName String No - The first name of the user
lastName String No - The last name of the user
role String No - One of learner, group-admin or admin
language String No - A language tag (IETF BCP 47) supported by your organization.
customAttributes Map<String, String> No - For storing arbitrary key/value pairs. The keyset needs to be pre-configured.

Send invite

curl "https://<domain>.sana.ai/api/v0/users/<userId>/send-invite" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <accessToken>"

This endpoint will generate a new invite link and send it to the user. Calling this endpoint will invalidate the previous invite links.

If called after a user has accepted the invite the request will be rejected.

The endpoint can also reject requests if called too often to prevent sending multiple emails to the same user.

HTTP Request

POST https://<domain>.sana.ai/api/v0/users/<userId>/send-invite

curl "https://<domain>.sana.ai/api/v0/users/<userId>/invite-link" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <accessToken>"

Calling this endpoint will invalidate the previous invite links.

If called after a user has accepted the invite the request will be rejected.

HTTP Request

POST https://<domain>.sana.ai/api/v0/users/<userId>/invite-link

Update a User

curl "https://<domain>.sana.ai/api/v0/users/eb87a2dc-3e78-4d0b-b868-9d3f49c64f68" \
  -X PATCH \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <accessToken>"
  -d `{ \
        "user": { \
          "lastName": "Apple" \
        } \
      }`

⚠️ If you are updating customAttributes: the field does not support merge logic, you need to add the entire expected object in the patch request.

The above command returns JSON structured like this:

{
  "data": {
    "id": "eb87a2dc-3e78-4d0b-b868-9d3f49c64f68",
    "email": "sana@sanalabs.com",
    "firstName": "Sana",
    "lastName": "Apple",
    "role": "learner",
    "disabled": false,
    "status": "active",
    "language": null,
    "customAttributes": {},
    "createdAt": "2023-02-21T13:00:11.851167Z",
    "activatedAt": "2023-02-22T13:00:10.342157Z",
    "lastInviteSentAt": null,
    "isSsoEmail": true
  },
  "error": null
}

This endpoint updates a specific user.

A user account can be deactivated by setting disabled = true. This operation can be reversed to reactivate an account by setting disabled = false.

HTTP Request

PATCH https://<domain>.sana.ai/api/v0/users/<userId>

URL Parameters

Parameter Type Description
userId String The ID of the user to update

Body Parameters

Parameter Type Required Default Description
email String No - The user's email address
firstName String No - The first name of the user
lastName String No - The last name of the user
disabled Boolean No - If set to true the account is deactivated. If set to false the account is reactivated.
role String No - One of learner, group-admin or admin
language String No - A language tag (IETF BCP 47) supported by your organization.
customAttributes Map No - For storing arbitrary key/value pairs. The keyset needs to be pre-configured.

Delete a User

curl "https://<domain>.sana.ai/api/v0/users/eb87a2dc-3e78-4d0b-b868-9d3f49c64f68" \
  -X DELETE \
  -H "Authorization: Bearer <accessToken>"

This endpoint deletes a specific user.

HTTP Request

DELETE https://<domain>.sana.ai/api/v0/users/<userId>

URL Parameters

Parameter Type Description
userId String The ID of the user to delete

List All Groups for a User

curl "https://<domain>.sana.ai/api/v0/users/eb87a2dc-3e78-4d0b-b868-9d3f49c64f68/groups?limit=1" \
  -H "Authorization: Bearer <accessToken>"

The above command returns JSON structured like this:

{
  "data": [
    {
      "id": "b3a96b74-1b2e-49da-b5d8-bfa7c7b800be",
      "role": "learner"
    }
  ],
  "links": {
    "next": "https://<domain>.sana.ai/api/v0/users/eb87a2dc-3e78-4d0b-b868-9d3f49c64f68/groups?next=5f4818ba-16d1-43c0-8421-4a1782a999a1&limit=1"
  },
  "error": null
}

This endpoint retrieves all groups in a specific user

HTTP Request

GET https://<domain>.sana.ai/api/v0/users/<userId>/groups

URL Parameters

Parameter Description
userId The ID of the user

Query Parameters

Parameter Type Required Default Description
next String No - Set to fetch the next batch of groups.
limit Integer No 100 The number of groups to return. Max: 1000

Set user's manager

curl "https://<domain>.sana.ai/api/v0/users/eb87a2dc-3e78-4d0b-b868-9d3f49c64f68/manager/0e9e5c2e-5db4-42e7-a4ed-0d8aebe23094" \
  -X PUT \
  -H "Authorization: Bearer <accessToken>"

This endpoint sets the user's manager.

Cycles in user manager relationships are not allowed. The request that closes a cycle will be rejected.

HTTP Request

PUT https://<domain>.sana.ai/api/v0/users/<userId>/manager/<managerId>

Delete user's manager

curl "https://<domain>.sana.ai/api/v0/users/eb87a2dc-3e78-4d0b-b868-9d3f49c64f68/manager" \
  -X DELETE \
  -H "Authorization: Bearer <accessToken>"

This endpoint deletes the user's manager.

HTTP Request

DELETE https://<domain>.sana.ai/api/v0/users/<userId>/manager

Groups

List All Groups

curl "https://<domain>.sana.ai/api/v0/groups?limit=1" \
  -H "Authorization: Bearer <accessToken>"

The above command returns JSON structured like this:

{
  "data": [
    {
      "id": "237484cd-098e-477d-80d6-c5ce655fbef7",
      "name": "UI group",
      "type": "user_manual"
    },
    {
      "id": "2dac5403-9723-4534-ad01-9750cd13b230",
      "name": "Insights program",
      "type": "program"
    },
    {
      "id": "b3a96b74-1b2e-49da-b5d8-bfa7c7b800be",
      "name": "API group",
      "type": "user"
    }
  ],

  "links": {
    "next": "https://<domain>.sana.ai/api/v0/groups?next=5f4818ba-16d1-43c0-8421-4a1782a999a1&limit=1"
  },
  "error": null
}

This endpoint retrieves all groups

HTTP Request

GET https://<domain>.sana.ai/api/v0/groups

Query Parameters

Parameter Type Required Default Description
next String No - Set to fetch the next batch of groups.
limit Integer No 100 The number of groups to return. Max: 1000

Get a Group

curl "https://<domain>.sana.ai/api/v0/groups/b3a96b74-1b2e-49da-b5d8-bfa7c7b800be" \
  -H "Authorization: Bearer <accessToken>"

The above command returns JSON structured like this:

{
  "data": {
    "id": "d3846c40-4005-439d-b945-82868e9965f7",
    "name": "marketing group",
    "type": "user"
  },
  "links": null,
  "error": null
}

This endpoint retrieves a specific group

HTTP Request

GET https://<domain>.sana.ai/api/v0/groups/<groupId>

URL Parameters

Parameter Description
groupId The ID of the group to retrieve

Create a Group

curl "https://<domain>.sana.ai/api/v0/groups" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <accessToken>" \
  -d `{ \
        "group": {
          "name": "Fruits" \
          "type": "user" \
        }
      }`

The above command returns JSON structured like this:

{
  "data": {
    "id": "b3a96b74-1b2e-49da-b5d8-bfa7c7b800be",
    "name": "Fruits",
    "type": "user"
  },
  "error": null
}

This endpoint creates a group.

Note: The type attribute is disabled by default, once enabled existing groups will be of type program.

HTTP Request

POST https://<domain>.sana.ai/api/v0/groups

Body Parameters

Parameter Type Required Default Description
name String Yes - The name of the group
type Enum No program Type of group: program, user, user_manual

Update a Group

curl "https://<domain>.sana.ai/api/v0/users/b3a96b74-1b2e-49da-b5d8-bfa7c7b800be" \
  -X PATCH \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <accessToken>"
  -d `{ \
        "group": { \
          "name": "Fruit Loops" \
        }
      }`

The above command returns JSON structured like this:

{
  "data": {
    "id": "b3a96b74-1b2e-49da-b5d8-bfa7c7b800be",
    "name": "The Fruit Loops",
    "type": "program"
  },
  "error": null
}

This endpoint updates a specific group

HTTP Request

PATCH https://<domain>.sana.ai/api/v0/groups/<groupId>

URL Parameters

Parameter Type Description
groupId String The ID of the group to update

Body Parameters

Parameter Type Required Default Description
name String No - The name of the group

Delete a Group

curl "https://<domain>.sana.ai/api/v0/groups/b3a96b74-1b2e-49da-b5d8-bfa7c7b800be" \
  -X DELETE \
  -H "Authorization: Bearer <accessToken>"

This endpoint deletes a specific group

HTTP Request

DELETE https://<domain>.sana.ai/api/v0/groups/<groupId>

URL Parameters

Parameter Description
groupId The ID of the group to delete

List All Users in a Group

curl "https://<domain>.sana.ai/api/v0/groups/b3a96b74-1b2e-49da-b5d8-bfa7c7b800be/users?limit=1" \
  -H "Authorization: Bearer <accessToken>"

The above command returns JSON structured like this:

{
  "data": [
    {
      "id": "eb87a2dc-3e78-4d0b-b868-9d3f49c64f68",
      "role": "learner"
    }
  ],
  "links": {
    "next": "https://<domain>.sana.ai/api/v0/groups/b3a96b74-1b2e-49da-b5d8-bfa7c7b800be/users?next=9399b472-199b-4d18-97d9-819c7e1bf22f&limit=1"
  },
  "error": null
}

This endpoint retrieves all users in a specific group

HTTP Request

GET https://<domain>.sana.ai/api/v0/groups/<groupId>/users

URL Parameters

Parameter Description
groupId The ID of the group

Query Parameters

Parameter Type Required Default Description
next String No - Set to fetch the next batch of users.
limit Integer No 100 The number of users to return. Max: 1000

Add Users to a Group

curl "https://<domain>.sana.ai/api/v0/groups/b3a96b74-1b2e-49da-b5d8-bfa7c7b800be/users" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <accessToken>" \
  -d `{ \
        "users": [ \
          { \
            "id": "eb87a2dc-3e78-4d0b-b868-9d3f49c64f68", \
            "role": "learner" \
          } \
        ] \
      }`

This endpoint adds users to a specific group

HTTP Request

POST https://<domain>.sana.ai/api/v0/groups/<groupId>/users

URL Parameters

Parameter Description
groupId The ID of the group

Body Parameters

Parameter Type Required Default Description
id String Yes - The ID of the user to add
role String Yes - The user's role in the group. One of learner, group-admin.

Update a User in a Group

curl "https://<domain>.sana.ai/api/v0/groups/b3a96b74-1b2e-49da-b5d8-bfa7c7b800be/users/eb87a2dc-3e78-4d0b-b868-9d3f49c64f68" \
  -X PATCH \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <accessToken>" \
  -d `{ \
        "user": { \
          "role": "group-admin" \
        } \
      }`

This endpoint updates a user in a group

HTTP Request

PATCH https://<domain>.sana.ai/api/v0/groups/<groupId>/users/<userId>

URL Parameters

Parameter Description
groupId The ID of the group
userId The ID of the user

Body Parameters

Parameter Type Required Default Description
role String No - The user's role in the group. One of learner, group-admin.

Delete a User from a Group

curl "https://<domain>.sana.ai/api/v0/groups/b3a96b74-1b2e-49da-b5d8-bfa7c7b800be/users/eb87a2dc-3e78-4d0b-b868-9d3f49c64f68" \
  -X DELETE \
  -H "Authorization: Bearer <accessToken>"

This endpoint deletes a user from a specific group

HTTP Request

DELETE https://<domain>.sana.ai/api/v0/groups/<groupId>/users/<userId>

URL Parameters

Parameter Description
groupId The ID of the group
userId The ID of the user

Assignments

An assignment is a relationship between a content entity and an identity entity. Currently the only supported content entity is course and the supported identity entities are user and group. More entities should be expected in the future. New entities can be introduced with short notice and this needs to be considered when parsing this data.

List user assignments

curl "https://<domain>.sana.ai/api/v0/users/bdaf7ff4-c665-4f1c-8e7b-1ca7fcca5277/assignments" \
  -H "Authorization: Bearer <accessToken>"

The above command returns JSON structured like this:

{
  "data": [
    {
      "content": {
        "type": "course",
        "id": "bdBUCzdWq3CK"
      },
      "identity": {
        "type": "user",
        "id": "bdaf7ff4-c665-4f1c-8e7b-1ca7fcca5277"
      },
      "status": {
        "completed": false
      },
      "passedTime": null
    },
    {
      "content": {
        "type": "course",
        "id": "c4etyA_7VGq4"
      },
      "identity": {
        "type": "group",
        "id": "1f1ee52d-492b-4003-afa6-0b80a2259ceb"
      },
      "status": {
        "completed": true
      },
      "assignmentTime": "2020-12-15T18:40:53.553743439Z",
      "passedTime": "2021-12-15T18:40:53.553743439Z",
      "dueDateAbsolute": "2024-03-05"
    }
  ]
}

This endpoint lists the assignments for a user.

A user can have many assignments for the same content. This is because a user can be assigned content through a group which they are a member of. For this reason content should not be considered to be a unique value in the list.

A user assignment through a group can be identified by identity where type=group and id is the group's ID.

HTTP Request

GET https://<domain>.sana.ai/api/v0/users/<userId>/assignments

Query Parameters

Parameter Type Required Default Description
contentType String No - Filter assignments using content types. Allowed values: course, path
identityType String No - Filter assignments using identity types. Allowed values: user, group

URL Parameters

Parameter Description
userId The ID of the user

Assign content to a user

curl "https://<domain>.sana.ai/api/v0/users/bdaf7ff4-c665-4f1c-8e7b-1ca7fcca5277/assignments" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <accessToken>" \
  -d '{ "assignments": [{ "type": "course", "id": "c4etyA_7VGq4" }], "dueDateAbsolute": "2024-03-01" }'

This endpoint assigns content to the user

HTTP Request

POST https://<domain>.sana.ai/api/v0/users/<userId>/assignments

Body Parameters

Parameter Type Required Default Description
assignments.type String Yes - The content type. Possible values: course, path
assignments.id String Yes - The content ID
dueDateAbsolute LocalDate No - Due date for assignment

URL Parameters

Parameter Description
userId The ID of the user

Delete an assignment from a user

curl "https://<domain>.sana.ai/api/v0/users/bdaf7ff4-c665-4f1c-8e7b-1ca7fcca5277/assignments" \
  -H "Authorization: Bearer <accessToken>" \
  -X DELETE \
  -d '{ "assignments": [{ "type": "course", "id": "c4etyA_7VGq4" }] }'

This endpoint deletes an assignment from the user.

HTTP Request

DELETE https://<domain>.sana.ai/api/v0/users/<userId>/assignments

Body Parameters

Parameter Type Required Default Description
assignments.type String Yes - The content type. Possible values: course, path
assignments.id String Yes - The content ID

URL Parameters

Parameter Description
userId The ID of the user

List group assignments

curl "https://<domain>.sana.ai/api/v0/groups/1f1ee52d-492b-4003-afa6-0b80a2259ceb/assignments" \
  -H "Authorization: Bearer <accessToken>"

The above command returns JSON structured like this:

{
  "data": [
    {
      "content": {
        "type": "course",
        "id": "bdBUCzdWq3CK"
      },
      "identity": {
        "type": "group",
        "id": "1f1ee52d-492b-4003-afa6-0b80a2259ceb"
      }
    },
    {
      "content": {
        "type": "course",
        "id": "c4etyA_7VGq4"
      },
      "identity": {
        "type": "group",
        "id": "1f1ee52d-492b-4003-afa6-0b80a2259ceb"
      }
    }
  ]
}

List of content assigned to a specific group.

HTTP Request

GET https://<domain>.sana.ai/api/v0/groups/<groupId>/assignments

URL Parameters

Parameter Description
groupId The ID of the group

Query Parameters

Parameter Type Required Default Description
contentType String No - Filter assignments using content types. Allowed values: course, path

Courses

List Courses

curl "https://<domain>.sana.ai/api/v0/courses" \
  -H "Authorization: Bearer <accessToken>"

The above command returns JSON structured like this:

{
  "data": [
    {
      "id": "5_dl6jHhI78o",
      "title": "Agile Project Management",
      "description": "Agile is an approach to project management...",
      "imageUrl": "https://...",
      "durationMinutes": 15,
      "type": "live",
      "externalId": "123456",
      "link": null,
      "level": null
    }
  ]
}

This endpoint lists the published courses.

HTTP Request

GET https://<domain>.sana.ai/api/v0/courses

Query Parameters

Parameter Type Required Default Description
next String No - Set to fetch the next batch of courses.
limit Integer No 100 The number of courses to return. Max: 1000

Get a Course

curl "https://<domain>.sana.ai/api/v0/courses/5_dl6jHhI78o" \
  -H "Authorization: Bearer <accessToken>"

The above command returns JSON structured like this:

{
  "data": {
    "id": "5_dl6jHhI78o",
    "title": "Agile Project Management",
    "description": "Agile is an approach to project management...",
    "imageUrl": "https://...",
    "durationMinutes": 15,
    "type": "live",
    "externalId": "123456",
    "link": null,
    "level": null
  }
}

This endpoint returns a specific course.

HTTP Request

GET https://<domain>.sana.ai/api/v0/courses/<courseId>

URL Parameters

Parameter Description
courseId The ID of the course
curl "https://<domain>.sana.ai/api/v0/courses" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <accessToken>" \
  -d '{
          "course": {
            "title": "QA Basics",
            "description": "This is a link course...",
            "durationInMinutes": 14,
            "link": "https://docs.google.com/presentation/d/160a2j5_hsD0np0DOOxP-Lno_hc9V3celMfyD42t8z2o/edit?usp=sharing",
            "externalId": "123456"
          }
        }'

This endpoint create a link course

HTTP Request

POST https://<domain>.sana.ai/api/v0/courses

Body Parameters

Parameter Type Required Default Description
title String Yes - Title of the course
description String No - Description of the course
link String Yes - Link pointing to the course
durationInMinutes Int Yes - Duration of the course
imageUrl String No - URL for an image that is shown for the course.
level String No - One of beginner, intermediate or expert.
externalId String No - External identifier, must be a unique value
curl "https://<domain>.sana.ai/api/v0/courses/DVwfgGR7H2g2" \
  -X PATCH \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <accessToken>" \
  -d '{
          "course": {
            "title": "QA Basics - Part 2",
            "description": "This is a link course...",
            "durationInMinutes": 14,
            "level": "expert",
            "link": "https://docs.google.com/presentation/d/160a2j5_hsD0np0DOOxP-Lno_hc9V3celMfyD42t8z2o/edit?usp=sharing"
          }
        }'

This endpoint updates a link course

HTTP Request

PATCH https://<domain>.sana.ai/api/v0/courses/<courseId>

Body Parameters

Parameter Type Required Default Description
title String No - New title of the course
description String No - New description of the course
link String No - New link pointing to the course
durationInMinutes Int No - New duration of the course
imageUrl String No - New URL for an image that is shown for the course.
level String No - New level for course. One of beginner, intermediate or expert.
externalId String No - New external identifier, must be a unique value
curl "https://<domain>.sana.ai/api/v0/courses/DVwfgGR7H2g2" \
  -X DELETE \
  -H "Authorization: Bearer <accessToken>"

This endpoint deletes a course.

HTTP Request

DELETE https://<domain>.sana.ai/api/v0/courses/<courseId>

URL Parameters

Parameter Type Description
courseId String The ID of the course to delete

Mark course completion for a User

curl "https://<domain>.sana.ai/api/v0/courses/DVwfgGR7H2g2/completed/eb87a2dc-3e78-4d0b-b868-9d3f49c64f68" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <accessToken>"

This endpoint mark a course completed for a user

HTTP Request

POST https://<domain>.sana.ai/api/v0/courses/<courseId>/completed/<userId>

URL Parameters

Parameter Type Description
courseId String The ID of the course which user has completed
userId String The ID of the user who has completed the course

Reset course progress for a User

curl "https://<domain>.sana.ai/api/v0/courses/DVwfgGR7H2g2/reset/eb87a2dc-3e78-4d0b-b868-9d3f49c64f68" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <accessToken>"

This endpoint resets a course progress for a user

HTTP Request

POST https://<domain>.sana.ai/api/v0/courses/<courseId>/reset/<userId>

URL Parameters

Parameter Type Description
courseId String The ID of the course which user has made progress on
userId String The ID of the user who has made progress on the course

Paths

List Paths

curl "https://<domain>.sana.ai/api/v0/paths" \
  -H "Authorization: Bearer <accessToken>"

The above command returns JSON structured like this:

{
  "data": [
    {
      "id": "Idy-Q95bD5oW",
      "title": "Sales Onboarding Path",
      "description": "Contains a set of courses related to sales onboarding...",
      "imageUrl": "https://...",
      "contents": [
        "YC8xEv2WlKwh",
        "WZ1s39C3vFKl",
        "WhShL986qFFF"
      ]
    }
  ]
}

This endpoint lists the paths.

HTTP Request

GET https://<domain>.sana.ai/api/v0/paths

Query Parameters

Parameter Type Required Default Description
next String No - Set to fetch the next batch of paths.
limit Integer No 100 The number of paths to return. Max: 1000

Get a Path

curl "https://<domain>.sana.ai/api/v0/paths/Idy-Q95bD5oW" \
  -H "Authorization: Bearer <accessToken>"

The above command returns JSON structured like this:

{
  "data": {
    "id": "Idy-Q95bD5oW",
    "title": "Sales Onboarding Path",
    "description": "Contains a set of courses related to sales onboarding...",
    "imageUrl": "https://...",
    "contents": [
      "YC8xEv2WlKwh",
      "WZ1s39C3vFKl",
      "WhShL986qFFF"
    ]
  }
}

This endpoint returns a specific path.

HTTP Request

GET https://<domain>.sana.ai/api/v0/paths/<pathId>

URL Parameters

Parameter Description
pathId The ID of the path

Reporting

List available reports

curl "https://<domain>.sana.ai/api/v0/reports" \
  -H "Authorization: Bearer <accessToken>"

The above command returns JSON structured like this:

{
  "data": [
    {
      "id": "learner-progress",
      "title": "Learner Progress",
      "description": "Analyze progress for a set of learners on their assignments"
    }
  ],
  "links": null,
  "error": null
}

This endpoint retrieves all the available reports

HTTP Request

GET https://<domain>.sana.ai/api/v0/reports

Create a job to generate a report of a specific report type.

curl "https://<domain>.sana.ai/api/v0/reports/learner-progress/jobs" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <accessToken>" \
  -d '{ "contentTypes": ["course", "path"], "groups": [], "contentIds": [], "assignmentType": "assigned", "outputFormat": "xlsx" }'

The above command returns JSON structured like this:

{
  "data": {
    "jobId": "5bee6ce9-59dd-49c5-a66c-9547d345c02f"
  }
}

This endpoint creates a job to generate a report.

HTTP Request

POST https://<domain>.sana.ai/api/v0/reports/<reportId>/jobs

URL Parameters

Parameter Description
reportId The ID of the report

Body Parameters for learner-progress report

Parameter Type Required Default Description
contentTypes List<String> Yes - The content types to include. Possible values: course, live, external, path
groups List<String> No - The group IDs to include
contentIds List<String> No The content IDs to include
assignmentType String Yes The assignments to include. Possible values: all, assigned
outputFormat String Yes The output format. Possible values: csv, xlsx

Get status of a jobs for a specific report type.

curl "https://<domain>.sana.ai/api/v0/reports/learner-progress/jobs" \
  -H "Authorization: Bearer <accessToken>"

The above command returns JSON structured like this:

{
  "data": [
    {
      "jobId": "5bee6ce9-59dd-49c5-a66c-9547d345c02f",
      "status": "successful",
      "createdAt": "2022-07-28T06:40:03.658904Z",
      "startedAt": "2022-07-28T06:40:13.589207Z",
      "finishedAt": "2022-07-28T06:40:56.37516Z"
    }
  ],
  "links": null,
  "error": null
}

This endpoint retrieves the status of the jobs for a specific report type.

HTTP Request

GET https://<domain>.sana.ai/api/v0/reports/<reportId>/jobs

URL Parameters

Parameter Description
reportId The ID of the report

Get status of a specific job to generate a report

curl "https://<domain>.sana.ai/api/v0/reports/learner-progress/jobs/5bee6ce9-59dd-49c5-a66c-9547d345c02f" \
  -H "Authorization: Bearer <accessToken>"

The above command returns JSON structured like this:

{
  "data": {
    "jobId": "5bee6ce9-59dd-49c5-a66c-9547d345c02f",
    "status": "successful",
    "createdAt": "2022-07-28T06:40:03.658904Z",
    "startedAt": "2022-07-28T06:40:13.589207Z",
    "finishedAt": "2022-07-28T06:40:56.37516Z",
    "link": {
      "url": "<url>",
      "expiresAt": "2022-07-28T06:56:29.660261Z"
    }
  },
  "links": null,
  "error": null
}

This endpoint retrieves metadata for a specific job. If the job has status successful, a link is included in the response where the report can be downloaded.

HTTP Request

GET https://<domain>.sana.ai/api/v0/reports/<reportId>/jobs/<jobId>

URL Parameters

Parameter Description
reportId The ID of the report
jobId The ID of the job

xAPI

Sana can act as both an Learning Record Provider or Activity Provider and a Learning Record Store (LRS) in the context of the xAPI specification. This means that an integration with Sana can subscribe to Statements (events) to receive users' activity in Sana, while also allowing external LMS to send the same events to Sana.

Sana as a Record Provider

Status: Disabled by default

This endpoint is disabled by default. Please reach out to your Sana contact to setup this endpoint for your organization in Sana.

Authentication

The xAPI specification states that a client to server integration use the OAuth Client Credentials flow.

In the context of authentication Sana is the client and the integration is the server. This means that the integration must be able to issue tokens to Sana that can be used to send statements.

Request Access token

curl "https://example.com/token" \
  -X POST \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&client_id=<client-id>&client_secret=<client-secret>&scope=statements/write"

Sana will make a request like above and expect at least the following values to be returned.

{
  "access_token": "<accessToken>",
  "token_type": "bearer",
  "expires_in": "<timeout>"
}

The returned access token will be stored and used for sending statements. Sana will request a new token when the old one has expired or when a request returns 401 or 403 status.

Body Parameters

Parameter Description
grant_type Always set to client_credentials
client_id The ID of the client, provided by the integration
client_secret The secret of the client, provided by the integration
scope Defaults to statements/write, a custom scope can be provided in the if required.

Response Parameters

Parameter Description
access_token An access token that can be used by Sana to make send statements
token_type Must be set to bearer
expires_in The number of seconds until the access token expires

Configuration

xAPI integrations can be configured in https://<domain>.sana.ai/manage/api

After an integration has been created new events will be sent to the integration.

Retry logic

If a statement request fails it will be retried for some period with an exponential backoff. A statement will be retried for 6 days before discarded but the retry period can change in the future without notice.

Requests that return a 2xx status will be treated as successful, any other response will be considered a failure and will result in a retry.

Delivery semantics

Each statement (identified by its id) will be sent at least once. At least once means that the consumer should expect to process the same statement multiple times (in rare cases). Each statement type defines its own semantics for what is a unique event.

Statements

The following sections list the supported statements. This list will grow over time, so it's important that the receiving endpoint can handle unknown statements types.

Course completion

Sent when a user completes a course. The user does not have to be assigned to the course for an event to be sent.

A user can have multiple completions for the same course if the course progress has been reset in between.

{
  "id": "c66431c9-60ff-4997-8da6-6522a70e309a",
  "actor": {
    "objectType": "Agent",
    "account": {
      "homePage": "https://sanalabs.com",
      "name": "c4db5d53-3738-4c5d-b400-edad621dbeff"
    }
  },
  "verb": {
    "display": {
      "en-US": "completed"
    },
    "id": "http://adlnet.gov/expapi/verbs/completed"
  },
  "object": {
    "definition": {
      "type": "http://adlnet.gov/expapi/activities/course"
    },
    "id": "rVJIoKPfKLQK",
    "objectType": "Activity"
  },
  "timestamp": "2021-12-15T18:40:53.553743439Z"
}

Path completion

Sent when a user completes a path. The user does not have to be assigned to the path for an event to be sent.

The completion of a path is a derived state from the completion of the courses belonging to a path. This means that the path can be completed multiple times. Either due to an underlying course being completed multiple times or new courses being added to a path.

{
  "id": "c66431c9-60ff-4997-8da6-6522a70e309a",
  "actor": {
    "objectType": "Agent",
    "account": {
      "homePage": "https://sanalabs.com",
      "name": "c4db5d53-3738-4c5d-b400-edad621dbeff"
    }
  },
  "verb": {
    "display": {
      "en-US": "completed"
    },
    "id": "http://adlnet.gov/expapi/verbs/completed"
  },
  "object": {
    "definition": {
      "type": "https://sanalabs.com/xapi/activities/path"
    },
    "id": "th2V7hmRix_7",
    "objectType": "Activity"
  },
  "timestamp": "2021-12-15T18:40:53.553743439Z"
}

Sana as a Record Store

Sana can interface with a third-party LMS to receive activities corresponding to a learner’s progress and completion of a course.

Integration configuration

Each Learning Record Provider that you want to allow to communicate completions back to Sana will have its own configuration guide. However, all of them will ask you to collect the following details:

In order to obtain the last two, please create a new API client for each integration via Manage → API, and take note of the Client ID and Client secret. As for the two necessary endpoints, you can use the following values:

Statements

Sana supports the following statements:

Progress statement Complete statement Actor identifier Auth mechanism
Email(mbox) OAuth 2.0

Auth mechanism

Sanna supports OAuth 2.0 Client Credentials flow. Clients can request tokens to Sana to authenticate future request to statements.

Actor identifier

The actor in the xAPI statement needs to be identified using the mbox (i.e., email) property. Sana does not support account objects.

Actor object supported:

{
  "actor": {
    "mbox": "mailto:<mailbox>",
    "objectType": "Agent"
  }
}

Progress course

The progress statement indicates the learner’s progress within a course. In the result object, the completion attribute needs to be configured as false, and the progress must be set as a percentage of completion (e.g., 75 for 3/4 completion). Sana does not support scaled scores.

Keep in mind that this API only work with courses created via https://<domain>.sana.ai/api/v0/courses (using externalId); or with courses imported using certain integrations (e.g., LinkedIn Learning).

cURL example to progress a course:

curl -X POST -i \
  https://<domain>.sana.ai/xapi/statements \
  -H 'Authorization: Bearer <bearer_token>' \
  -H 'Connection: close' \
  -H 'Content-Type: application/json' \
  -H 'X-Experience-API-Version: 1.0.0' \
  -d @- << EOF
{
  "id": "85d8ccdd-55a9-4b2c-808e-9c43899b9a9a",
  "timestamp": "2023-02-02T13:15:00.000Z",
  "actor": {
    "mbox": "mailto:<mailbox>",
    "objectType": "Agent"
  },
  "verb": {
    "display": {
      "en-US": "PROGRESSED"
    },
    "id": "http://adlnet.gov/expapi/verbs/progressed"
  },
  "object": {
    "definition": {
      "type": "http://adlnet.gov/expapi/activities/course"
    },
    "id": "<external_course_id>",
    "objectType": "Activity"
  },
  "result": {
    "completion": false,
    "extensions": {
      "https://w3id.org/xapi/cmi5/result/extensions/progress": "10"
    }
  }
}
EOF

Complete course

The completion statement is used to indicate the learner's successful conclusion of a course. In the result object, the completion attribute needs to be configured as true, and the scaled score must be 100, symbolizing a 100% completion.

Keep in mind that this API only work with courses created via https://<domain>.sana.ai/api/v0/courses (using externalId); or with courses imported using certain integrations (e.g., LinkedIn Learning).

cURL example to complete a course:

curl -X POST -i \
  https://<domain>.sana.ai/xapi/statements \
  -H 'Authorization: Bearer <bearer_token>' \
  -H 'Connection: close' \
  -H 'Content-Type: application/json' \
  -H 'X-Experience-API-Version: 1.0.0' \
  -d @- << EOF
{
  "id": "88abd221-44b7-4e08-954b-e457e4699f3b",
  "timestamp": "2023-02-02T14:30:00.000Z",
  "actor": {
    "mbox": "mailto:<mailbox>",
    "objectType": "Agent"
  },
  "verb": {
    "display": {
      "en-US": "COMPLETED"
    },
    "id": "http://adlnet.gov/expapi/verbs/completed"
  },
  "object": {
    "definition": {
      "type": "http://adlnet.gov/expapi/activities/course"
    },
    "id": "<course_uri>",
    "objectType": "Activity"
  },
  "result": {
    "duration": "PT1H27M",
    "completion": true,
    "extensions": {
      "https://w3id.org/xapi/cmi5/result/extensions/progress": "100"
    }
  }
}
EOF