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
- If
disabled
istrue
, it means the user is de-activated - If
activatedAt
field is null, it means the user is inpending
state - If
isSsoEmail
field is true, user e-mail matches suffixes configured for organization SSO configuration. - If
lastInviteSentAt
field is null, no user invitation e-mail has been sent.
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" \
} \
}`
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",
"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 |
---|---|---|---|---|
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 |
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
Generate new invite link
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" \
} \
}`
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",
"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 |
---|---|---|---|---|
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 |
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": "b3a96b74-1b2e-49da-b5d8-bfa7c7b800be",
"name": "The Fruits"
}
],
"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": "b3a96b74-1b2e-49da-b5d8-bfa7c7b800be",
"name": "The Fruits"
},
"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": "program" \
}
}`
The above command returns JSON structured like this:
{
"data": {
"id": "b3a96b74-1b2e-49da-b5d8-bfa7c7b800be",
"name": "Fruits",
"type": "program"
},
"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 |
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"
}
]
}
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" }] }'
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 |
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 |
Assign content to a group
curl "https://<domain>.sana.ai/api/v0/groups/1f1ee52d-492b-4003-afa6-0b80a2259ceb/assignments" \
-X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <accessToken>" \
-d '{ "assignments": [{ "type": "course", "id": "bdBUCzdWq3CK" }] }'
This endpoint assigns content to the group
HTTP Request
POST https://<domain>.sana.ai/api/v0/groups/<groupId>/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 |
---|---|
groupId | The ID of the group |
Delete an assignment from a group
curl "https://<domain>.sana.ai/api/v0/groups/1f1ee52d-492b-4003-afa6-0b80a2259ceb/assignments" \
-H "Authorization: Bearer <accessToken>" \
-X DELETE \
-d '{ "assignments": [{ "type": "course", "id": "bdBUCzdWq3CK" }] }'
This endpoint deletes an assignment from the group.
HTTP Request
DELETE https://<domain>.sana.ai/api/v0/groups/<groupId>/assignments
URL Parameters
Parameter | Description |
---|---|
groupId | The ID of the group |
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 |
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 |
Create a link 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 |
Update a link course
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 |
Delete a link course
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 |
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 , 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:
- OAuth Server URL / Token endpoint URL
- xAPI statement endpoint URL
- Client ID
- Client secret
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:
- OAuth Server URL:
https://<domain>.sana.ai/api/token
- xAPI statement endpoint URL:
https://<domain>.sana.ai/xapi/v1/statements
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