# Thought Industries Developer Hub > Complete developer documentation for the Thought Industries learning platform. Includes REST and GraphQL API references with endpoint specifications, authentication guides, webhook integration, the Helium SDK for custom storefront development, and tutorials for common integration patterns. This file contains all documentation content in a single document following the llmstxt.org standard. ## Authentication The REST API uses Bearer token authentication. Include your API key in the `Authorization` header of every request. ## Obtaining an API key Generate an API key from the Thought Industries admin panel under **Settings → API Keys**. Each key is scoped to a single instance and inherits the permissions of the role assigned to it at creation. :::warning Treat API keys as secrets. Never commit them to version control or expose them in client-side code. ::: ## Making authenticated requests Pass the API key verbatim after the `Bearer` scheme — the value is the key itself, with no prefix added by Thought Industries. ```bash title="curl" curl -X GET "https://api.thoughtindustries.com/incoming/v2/users" \ -H "Authorization: Bearer $TI_API_KEY" ``` ```javascript title="JavaScript" const response = await fetch("https://api.thoughtindustries.com/incoming/v2/users", { headers: { "Authorization": `Bearer ${process.env.TI_API_KEY}`, "Content-Type": "application/json" } }); const data = await response.json(); ``` ```python title="Python" response = requests.get( "https://api.thoughtindustries.com/incoming/v2/users", headers={"Authorization": f"Bearer {os.environ['TI_API_KEY']}"} ) data = response.json() ``` ```php title="PHP" $ch = curl_init("https://api.thoughtindustries.com/incoming/v2/users"); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Authorization: Bearer " . getenv("TI_API_KEY"), "Content-Type: application/json" ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); $data = json_decode($response, true); ``` ## Unauthorized responses When authentication fails, the API returns `401 Unauthorized` with an empty response body. Common causes: - Missing `Authorization` header - Incorrect API key (typo, wrong instance, or revoked) - Instance does not have API access enabled - Key lacks permission for the requested resource Because the body is empty, applications should treat any `401` from the REST API as an authentication failure — inspect the request headers rather than a response payload. ## Security best practices - Rotate API keys on a regular schedule. - Store keys in environment variables or a secrets manager — never in source control. - Assign each key the minimum role required for its use case. - Revoke keys immediately if you suspect compromise. ## Related - [Rate limits](/docs/rest/rate-limits/) — request quotas and throttling behavior - [Status codes](/docs/rest/status-codes/) — HTTP response code reference --- ## Assessments ### AssessmentAttempts Returns a paginated list of assessment attempts across your platform, optionally filtered by date range, course, or user. ### GET /v2/assessmentAttempts #### Example request ```bash curl "$BASE_URL/assessmentAttempts" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque pagination cursor returned by a previous response; use to fetch the next page of results. | | `startDate` | `epoch milliseconds` | No | query | Filters results to attempts created at or after this Unix timestamp, in milliseconds. | | `endDate` | `epoch milliseconds` | No | query | Filters results to attempts created at or before this Unix timestamp, in milliseconds. | | `courseId` | `uuid` | No | query | Filters results to attempts belonging to the specified course. | | `userId` | `uuid` | No | query | Filters results to attempts belonging to the specified user. | | `perPage` | `integer` | No | query | Number of assessment attempts to return per page. | #### Example response ```json { "data": { "AssessmentAttempts": { "pageInfo": { "hasMore": true, "cursor": "string", "perPage": 0 }, "assessmentAttempts": [ { "id": "00000000-0000-0000-0000-000000000000", "courseId": "00000000-0000-0000-0000-000000000000", "course": { "id": "00000000-0000-0000-0000-000000000000", "title": "string" }, "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "grade": 0, "passed": true, "answers": [ { "id": "00000000-0000-0000-0000-000000000000", "question": "string", "answer": "string", "correct": true } ], "status": "string", "timeElapsedInSeconds": 0, "assessment": { "id": "00000000-0000-0000-0000-000000000000", "type": "string", "title": "string" }, "user": { "id": "00000000-0000-0000-0000-000000000000", "email": "string", "firstName": "string", "lastName": "string", "clientId": "00000000-0000-0000-0000-000000000000", "ref1": "string", "ref2": "string", "ref3": "string", "ref4": "string", "ref5": "string", "ref6": "string", "ref7": "string", "ref8": "string", "ref9": "string", "ref10": "string" } } ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `pageInfo.cursor` | `string` | Opaque cursor to pass in the next request to retrieve the following page. | | `pageInfo.perPage` | `integer` | Number of items returned per page. | | `assessmentAttempts` | `AssessmentAttempt[]` | Array of assessment attempts matching the query. | | `assessmentAttempts.id` | `uuid` | Unique identifier of the assessment attempt. | | `assessmentAttempts.courseId` | `uuid` | Identifier of the course the assessment belongs to. | | `assessmentAttempts.course` | `Course` | Course the assessment attempt belongs to. | | `assessmentAttempts.course.id` | `uuid` | Unique identifier of the course. | | `assessmentAttempts.course.title` | `string` | Title of the course. | | `assessmentAttempts.createdAt` | `ISO 8601 timestamp` | Date and time the attempt was created. | | `assessmentAttempts.updatedAt` | `ISO 8601 timestamp` | Date and time the attempt was last modified. | | `assessmentAttempts.grade` | `integer` | Grade recorded for the attempt. | | `assessmentAttempts.passed` | `boolean` | Indicates whether the attempt passed. | | `assessmentAttempts.answers` | `AssessmentAnswer[]` | Array of answers submitted for the attempt. | | `assessmentAttempts.answers.id` | `uuid` | Unique identifier of the answer. | | `assessmentAttempts.answers.question` | `string` | The question text. | | `assessmentAttempts.answers.answer` | `string` | The submitted answer. | | `assessmentAttempts.answers.correct` | `boolean` | Indicates whether the answer was graded correct. | | `assessmentAttempts.status` | `string` | Current status of the assessment attempt. | | `assessmentAttempts.timeElapsedInSeconds` | `integer` | Time spent on the attempt, in seconds. | | `assessmentAttempts.assessment` | `Assessment` | Assessment the attempt belongs to. | | `assessmentAttempts.assessment.id` | `uuid` | Unique identifier of the assessment. | | `assessmentAttempts.assessment.type` | `string` | Type of the assessment. | | `assessmentAttempts.assessment.title` | `string` | Title of the assessment. | | `assessmentAttempts.user` | `User` | User who made the attempt. | | `assessmentAttempts.user.id` | `uuid` | Unique identifier of the user. | | `assessmentAttempts.user.email` | `string` | Email address of the user. | | `assessmentAttempts.user.firstName` | `string` | First name of the user. | | `assessmentAttempts.user.lastName` | `string` | Last name of the user. | | `assessmentAttempts.user.clientId` | `uuid` | Identifier of the client the user belongs to. | | `assessmentAttempts.user.ref1` | `string` | Custom reference field 1 associated with the user. | | `assessmentAttempts.user.ref2` | `string` | Custom reference field 2 associated with the user. | | `assessmentAttempts.user.ref3` | `string` | Custom reference field 3 associated with the user. | | `assessmentAttempts.user.ref4` | `string` | Custom reference field 4 associated with the user. | | `assessmentAttempts.user.ref5` | `string` | Custom reference field 5 associated with the user. | | `assessmentAttempts.user.ref6` | `string` | Custom reference field 6 associated with the user. | | `assessmentAttempts.user.ref7` | `string` | Custom reference field 7 associated with the user. | | `assessmentAttempts.user.ref8` | `string` | Custom reference field 8 associated with the user. | | `assessmentAttempts.user.ref9` | `string` | Custom reference field 9 associated with the user. | | `assessmentAttempts.user.ref10` | `string` | Custom reference field 10 associated with the user. | --- ### Forgive Forgives a failed assessment attempt, marking it as passed regardless of the learner's original score. ### PUT /v2/assessmentAttempts/:id/forgive #### Example request ```bash curl -X PUT "$BASE_URL/assessmentAttempts/:id/forgive" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{}' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The unique identifier of the assessment attempt to forgive. | #### Example response ```json { "data": { "APIForgiveAssessmentAttempt": { "id": "00000000-0000-0000-0000-000000000000", "user": { "id": "00000000-0000-0000-0000-000000000000", "email": "string", "firstName": "string", "lastName": "string", "externalCustomerId": "00000000-0000-0000-0000-000000000000" }, "course": { "id": "00000000-0000-0000-0000-000000000000", "title": "string" }, "assessment": { "id": "00000000-0000-0000-0000-000000000000", "type": "string", "title": "string" }, "grade": 0, "passed": true, "forgiven": true } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Unique identifier of the assessment attempt. | | `user` | `User` | User who made the attempt. | | `user.id` | `uuid` | Unique identifier of the user. | | `user.email` | `string` | Email address of the user. | | `user.firstName` | `string` | First name of the user. | | `user.lastName` | `string` | Last name of the user. | | `user.externalCustomerId` | `uuid` | External customer identifier associated with the user. | | `course` | `Course` | Course the assessment attempt belongs to. | | `course.id` | `uuid` | Unique identifier of the course. | | `course.title` | `string` | Title of the course. | | `assessment` | `Assessment` | Assessment the attempt belongs to. | | `assessment.id` | `uuid` | Unique identifier of the assessment. | | `assessment.type` | `string` | Type of the assessment. | | `assessment.title` | `string` | Title of the assessment. | | `grade` | `integer` | Grade recorded for the attempt. | | `passed` | `boolean` | Indicates whether the attempt passed. | | `forgiven` | `boolean` | Indicates whether the attempt was forgiven. | --- ### Update Grade Updates the grade for a specific assessment attempt by ID. ### PUT /v2/assessmentAttempts/:id/updateGrade #### Example request ```bash curl -X PUT "$BASE_URL/assessmentAttempts/:id/updateGrade" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "grade": 0 }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The unique identifier of the assessment attempt to update. | | `grade` | `integer` | Yes | body | The new grade to assign to the assessment attempt, expressed as a whole number percentage (e.g., `85` for 85%). | #### Example response ```json { "data": { "UpdateAssessmentGrade": { "id": "00000000-0000-0000-0000-000000000000", "user": { "id": "00000000-0000-0000-0000-000000000000", "firstName": "string", "lastName": "string", "email": "string", "externalCustomerId": "00000000-0000-0000-0000-000000000000" }, "course": { "id": "00000000-0000-0000-0000-000000000000", "title": "string" }, "assessment": { "id": "00000000-0000-0000-0000-000000000000", "type": "string", "title": "string" }, "originalGrade": 0, "grade": 0, "passed": true } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Unique identifier of the assessment attempt. | | `user` | `User` | User who made the attempt. | | `user.id` | `uuid` | Unique identifier of the user. | | `user.firstName` | `string` | First name of the user. | | `user.lastName` | `string` | Last name of the user. | | `user.email` | `string` | Email address of the user. | | `user.externalCustomerId` | `uuid` | External customer identifier associated with the user. | | `course` | `Course` | Course the assessment attempt belongs to. | | `course.id` | `uuid` | Unique identifier of the course. | | `course.title` | `string` | Title of the course. | | `assessment` | `Assessment` | Assessment the attempt belongs to. | | `assessment.id` | `uuid` | Unique identifier of the assessment. | | `assessment.type` | `string` | Type of the assessment. | | `assessment.title` | `string` | Title of the assessment. | | `originalGrade` | `integer` | The attempt's grade before this update. | | `grade` | `integer` | The updated grade recorded for the attempt. | | `passed` | `boolean` | Indicates whether the attempt passed at the new grade. | --- --- ## Assignments & External Activity ### AssignmentSubmissions Returns a paginated list of assignment submissions across all courses. ### GET /v2/assignmentSubmissions #### Example request ```bash curl "$BASE_URL/assignmentSubmissions" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `query` | `string` | No | query | Full-text search string to filter submissions by matching content. | | `cursor` | `string` | No | query | Opaque pagination cursor returned by a previous response; use to fetch the next page of results. | #### Example response ```json { "data": { "AssignmentSubmissions": { "pageInfo": { "total": 0, "cursor": "string", "hasMore": true }, "assignmentSubmissions": [ { "assignment": { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "description": "string" }, "id": "00000000-0000-0000-0000-000000000000", "courseId": "00000000-0000-0000-0000-000000000000", "asset": "https://example.com/path", "additionalAsset": "https://example.com/path", "body": "string", "numericGrade": 0, "grade": "string", "gradeFeedback": "string", "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "user": { "id": "00000000-0000-0000-0000-000000000000", "email": "string", "firstName": "string", "lastName": "string", "ref1": "string", "ref2": "string", "ref3": "string", "ref4": "string", "ref5": "string", "ref6": "string", "ref7": "string", "ref8": "string", "ref9": "string", "ref10": "string" } } ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.total` | `integer` | Total number of submissions matching the query. | | `pageInfo.cursor` | `string` | Opaque cursor to pass in the next request to retrieve the following page. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `assignmentSubmissions` | `AssignmentSubmission[]` | Array of assignment submissions matching the query. | | `assignmentSubmissions.assignment` | `Assignment` | The assignment this submission was made for. | | `assignmentSubmissions.assignment.id` | `uuid` | Unique identifier of the assignment. | | `assignmentSubmissions.assignment.title` | `string` | Title of the assignment. | | `assignmentSubmissions.assignment.description` | `string` | Description of the assignment. | | `assignmentSubmissions.id` | `uuid` | Unique identifier of the submission. | | `assignmentSubmissions.courseId` | `uuid` | Identifier of the course the assignment belongs to. | | `assignmentSubmissions.asset` | `string` | URL of the primary submitted asset. | | `assignmentSubmissions.additionalAsset` | `string` | URL of an additional submitted asset, if any. | | `assignmentSubmissions.body` | `string` | Text body of the submission. | | `assignmentSubmissions.numericGrade` | `integer` | Numeric grade assigned to the submission. | | `assignmentSubmissions.grade` | `string` | Grade status assigned to the submission. | | `assignmentSubmissions.gradeFeedback` | `string` | Feedback text provided with the grade. | | `assignmentSubmissions.createdAt` | `ISO 8601 timestamp` | Date and time the submission was created. | | `assignmentSubmissions.updatedAt` | `ISO 8601 timestamp` | Date and time the submission was last modified. | | `assignmentSubmissions.user` | `User` | User who made the submission. | | `assignmentSubmissions.user.id` | `uuid` | Unique identifier of the user. | | `assignmentSubmissions.user.email` | `string` | Email address of the user. | | `assignmentSubmissions.user.firstName` | `string` | First name of the user. | | `assignmentSubmissions.user.lastName` | `string` | Last name of the user. | | `assignmentSubmissions.user.ref1` | `string` | Custom reference field 1 associated with the user. | | `assignmentSubmissions.user.ref2` | `string` | Custom reference field 2 associated with the user. | | `assignmentSubmissions.user.ref3` | `string` | Custom reference field 3 associated with the user. | | `assignmentSubmissions.user.ref4` | `string` | Custom reference field 4 associated with the user. | | `assignmentSubmissions.user.ref5` | `string` | Custom reference field 5 associated with the user. | | `assignmentSubmissions.user.ref6` | `string` | Custom reference field 6 associated with the user. | | `assignmentSubmissions.user.ref7` | `string` | Custom reference field 7 associated with the user. | | `assignmentSubmissions.user.ref8` | `string` | Custom reference field 8 associated with the user. | | `assignmentSubmissions.user.ref9` | `string` | Custom reference field 9 associated with the user. | | `assignmentSubmissions.user.ref10` | `string` | Custom reference field 10 associated with the user. | --- ### Record Assignment External Activity Record an external activity submission from a third-party LTI tool as an assignment submission. ### POST /v2/assignmentExternalActivity :::note **Feature flag:** Requires the `externalActivity` feature to be enabled on the company. If not enabled, the request returns `400` with `{ errors: ["externalActivity feature is not enabled"] }`. ::: #### Example request ```bash curl -X POST "$BASE_URL/assignmentExternalActivity" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "status": "completed", "assignmentId": "123e4567-e89b-12d3-a456-426614174000", "learnerId": "223e4567-e89b-12d3-a456-426614174000", "grade": 95, "gradeFeedback": "Nice work" }' ``` You may alternatively pass the attributes in a signed `jwt` query param instead of a JSON body: ```bash curl -X POST "$BASE_URL/assignmentExternalActivity?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `status` | `enum` | Yes | body | One of `completed` or `incomplete`. | | `assignmentId` | `uuid` | Yes | body | Assignment UUID. | | `learnerId` | `uuid` | Yes | body | Learner UUID. | | `grade` | `number` | No | body | Numeric grade. | | `gradeFeedback` | `string` | No | body | Feedback text. | | `returnUrl` | `string` | No | body | Relative URL to redirect to on success. | Both `assignmentId` and `learnerId` must be valid UUIDs or the request returns `400` with `{ errors: ["Invalid assignmentId or learnerId (both are required)"] }`. #### Example response On success the endpoint issues a `302` redirect to `returnUrl` (or `/learn` when none is supplied) — it does not return a JSON body: ``` HTTP/1.1 302 Found Location: /learn ``` #### Validation errors If the underlying mutation reports errors, the endpoint responds `400`: ```json { "valid": false, "errors": ["Assignment submission could not be recorded"], "assignmentSubmission": null } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `(redirect)` | `302` | On success, redirects to `returnUrl` (or `/learn`). No JSON body. | | `valid` | `boolean` | False on validation error. | | `errors` | `string[]` | Validation error messages. | | `assignmentSubmission` | `object` | Null on validation error. | --- ### Record Course External Activity Record an external activity completion against a course (LTI-style), optionally granting awards and an external certificate. ### POST /v2/courseExternalActivity :::note **Feature flag:** Requires the `externalActivity` feature to be enabled on the company. ::: #### Example request ```bash curl -X POST "$BASE_URL/courseExternalActivity" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "courseId": "123e4567-e89b-12d3-a456-426614174000", "userEmail": "learner@example.com", "completionDate": "2026-01-15T00:00:00.000Z" }' ``` You may alternatively pass the attributes in a signed `jwt` query param instead of a JSON body: ```bash curl -X POST "$BASE_URL/courseExternalActivity?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `courseId` | `uuid` | No | body | Course UUID. **`courseId` or `courseSku` is required.** | | `courseSku` | `string` | No | body | Course SKU. **`courseId` or `courseSku` is required.** | | `userId` | `string` | No | body | Learner ID. **`userId` or `userEmail` is required.** | | `userEmail` | `string` | No | body | Learner email. **`userId` or `userEmail` is required.** | | `completionDate` | `ISO 8601 timestamp` | No | body | Date and time the external activity was completed. | | `awards` | `array` | No | body | Award-claiming entries to grant the learner upon completion. | | `certificateUrl` | `string` | No | body | External certificate URL (only applied when `transcriptFields` are also supplied and external certificate uploads are enabled). | | `transcriptFields` | `array` | No | body | Transcript fields for the external certificate. | #### Example response On success the endpoint returns `200` with an empty body: ``` HTTP/1.1 200 OK ``` #### Validation errors Validation failures return `400`: ```json { "errors": ["userId or userEmail is required"] } ``` Other validation messages include: - `"courseSku or courseId is required"` - `"invalid completionDate. please use ISO 8601 format"` - `"Invalid course or user"` #### Response fields | Field | Type | Description | |-------|------|-------------| | `(empty)` | `200` | On success, returns 200 with an empty body. | | `errors` | `string[]` | Validation error messages on 400. | --- ### Record Learning Path External Activity Record an external activity completion or progress status against a learning path. ### POST /v2/learningPathExternalActivity :::note **Feature flag:** Requires the `externalActivity` feature to be enabled on the company. ::: #### Example request (completion) ```bash curl -X POST "$BASE_URL/learningPathExternalActivity" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "userId": "123e4567-e89b-12d3-a456-426614174000", "learningPathId": "223e4567-e89b-12d3-a456-426614174000", "status": "completed", "completionDate": "2026-01-15T00:00:00.000Z" }' ``` #### Example request (in-progress) Record in-progress activity with `timeStampDate` (only valid for non-`completed` statuses): ```bash curl -X POST "$BASE_URL/learningPathExternalActivity" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "userId": "123e4567-e89b-12d3-a456-426614174000", "learningPathId": "223e4567-e89b-12d3-a456-426614174000", "status": "started", "timeStampDate": "2026-01-10T00:00:00.000Z" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `userId` | `string` | Yes | body | Learner to credit. | | `learningPathId` | `string` | Yes | body | Learning path ID. | | `status` | `enum` | No | body | Defaults to `completed`. One of: `not-started`, `completed`, `started`, `not-completed`. | | `completionDate` | `ISO 8601 timestamp` | No | body | Only valid with `status: completed`. | | `timeStampDate` | `ISO 8601 timestamp` | No | body | Only valid with a non-`completed` status. | #### Example response On success the endpoint returns `200`: ``` HTTP/1.1 200 OK ``` #### Validation errors Validation failures return `400`: ```json { "errors": ["completion date cannot be passed with non-completed status"] } ``` Other validation messages include: - `"invalid status"` — status is not one of the allowed values. - `"timestamp date cannot be passed with completed status or no status"` - `"invalid completionDate. please use ISO 8601 format"` - `"invalid timestampDate. please use ISO 8601 format"` #### Response fields | Field | Type | Description | |-------|------|-------------| | `(empty)` | `200` | On success, returns 200 with an empty body. | | `errors` | `string[]` | Validation error messages on 400. | --- ### Bulk Update Assignment Submission License Sets or clears the license associated with many assignment submissions in a single request. ### POST /v3/assignmentSubmissions/bulkUpdateLicense :::note **Served by the v3 API.** The full path is `https://{instance}.thoughtindustries.com/v3/assignmentSubmissions/bulkUpdateLicense` — the `/v3` prefix, not the `/incoming/v2` base URL used by the other endpoints on this page. ::: Requires the `assignments.grade` permission. A standard company API key satisfies this. #### Example request — set a license per submission ```bash curl -X POST "https://{instance}.thoughtindustries.com/v3/assignmentSubmissions/bulkUpdateLicense" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "submissions": [ { "id": "123e4567-e89b-12d3-a456-426614174000", "licenseId": "223e4567-e89b-12d3-a456-426614174111" } ] }' ``` #### Example request — apply one license to many submissions ```bash curl -X POST "https://{instance}.thoughtindustries.com/v3/assignmentSubmissions/bulkUpdateLicense" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "ids": [ "123e4567-e89b-12d3-a456-426614174000", "323e4567-e89b-12d3-a456-426614174222" ], "licenseId": "223e4567-e89b-12d3-a456-426614174111" }' ``` Pass `"licenseId": null` in either shape to clear the license instead of setting it. #### Parameters Send `submissions`, or `ids` with a sibling `licenseId`, or both. At least one of the two lists must be present and non-empty. | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `submissions` | `BulkUpdateSubmissionItem[]` | One of `submissions` / `ids` | body | Per-submission updates, each naming its own target license. | | `ids` | `uuid[]` | One of `submissions` / `ids` | body | Submission IDs that all receive the same `licenseId`. Requires the sibling `licenseId` field. | | `licenseId` | `uuid \| null` | Required when `ids` is present | body | License applied to every entry in `ids`. Pass `null` to clear. Ignored when `ids` is absent. | Each object in `submissions` (`BulkUpdateSubmissionItem`): | Field | Type | Required | Description | |-------|------|----------|-------------| | `id` | `uuid` | Yes | Assignment submission to update. | | `licenseId` | `uuid \| null` | Yes | Target license for this submission. Pass `null` to clear the current license. | **Batch limit: 1000 submissions per request**, counted as the number of unique submission IDs after `submissions` and `ids` are merged — not per array. Each array is also capped at 1000 entries on its own, enforced earlier during request validation, so a single array of 1001 entries returns the opaque `400` below rather than the `Batch limit exceeded` message. **Precedence:** when the same submission ID appears in both `submissions` and `ids`, the shared `licenseId` from `ids` wins. #### Example response Returns `200`. The payload is nested under `data`, which is the standard v3 envelope. ```json { "data": { "updatedRecordCount": 2, "errors": [] } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `data.updatedRecordCount` | `integer` | Number of submissions whose license was actually changed. | | `data.errors` | `object[]` | Per-item failures. Empty when every submission was updated. | | `data.errors[].id` | `uuid` | The ID that triggered the failure — a submission ID, or a license ID when `record` is `license`. | | `data.errors[].error` | `string` | Human-readable reason the entry failed. | | `data.errors[].record` | `string` | Which side of the update failed: `submission` or `license`. | #### Partial success Per-item failures never fail the batch. Unknown submissions, unknown licenses, and IDs owned by another instance are collected into `errors[]` and skipped; everything else still updates. ```json { "data": { "updatedRecordCount": 0, "errors": [ { "id": "123e4567-e89b-12d3-a456-426614174000", "error": "Submission not found or does not belong to company", "record": "submission" } ] } } ``` An unrecognized license produces one error entry per affected submission, each carrying the license ID rather than the submission ID. Duplicate submission IDs are reported in `errors[]` but are not skipped, so `updatedRecordCount` can be positive alongside a non-empty `errors[]`. Always read `updatedRecordCount` rather than relying on the status code alone. Only the submission's license and updated-at timestamp change; grades, feedback, and submission content are untouched. Search reindexing happens asynchronously after the response is sent, so the search index is eventually consistent. #### Validation errors Three conditions fail the whole request with a message naming the problem. All return `400` with `code: "VALIDATION_ERROR"`: - Neither `submissions` nor `ids` supplied. - `ids` supplied without a sibling `licenseId`. - More than 1000 unique IDs after merging, each array being within 1000. ```json { "code": "VALIDATION_ERROR", "message": "At least one of `submissions` or `ids` must be provided", "category": "VALIDATION", "timestamp": "2026-08-12T00:00:00.000Z", "statusCode": 400, "metadata": { "errors": ["submissions", "ids"] } } ``` A missing or invalid API key returns `401` with `code: "UNAUTHENTICATED"`; a key lacking `assignments.grade` returns `403` with `code: "FORBIDDEN"`. Both use the same body shape with `category: "CLIENT_ERROR"` and carry no `metadata`. A malformed UUID, an unrecognized property, or more than 1000 entries in a single array returns `400` with `code: "BAD_REQUEST"`, `category: "CLIENT_ERROR"` and `message: "Bad Request Exception"`, with no indication of which field was at fault. #### Rate limit 500 requests per 60 seconds, counted per instance. This limit is specific to this endpoint and is independent of the per-family rate limits that apply to `/incoming/v2` endpoints. --- --- ## Certifications ### Bulk Certify Users Grants certifications to multiple users in a single request. Returns a count of successful grants and a list of any per-user errors. ### POST /v2/users/certify/bulk #### Example request ```bash curl -X POST "$BASE_URL/users/certify/bulk" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "users": [] }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `users` | `BulkCertifyUserInput[]` | Yes | body | Array of user-certification pairs specifying which certifications to grant to which users. | #### Example response ```json { "data": { "APIBulkCertifyUsers": { "grantedCount": 0, "errors": [ { "userId": "00000000-0000-0000-0000-000000000000", "email": "string", "externalCustomerId": "string", "resourceId": "00000000-0000-0000-0000-000000000000", "error": "string", "record": "string" } ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `grantedCount` | `integer` | Number of certifications successfully granted. | | `errors` | `BulkCertifyUserError[]` | List of errors for individual users whose certifications could not be granted. | | `errors.userId` | `uuid` | Unique identifier of the user whose certification grant failed. | | `errors.email` | `string` | Email address of the user whose certification grant failed. | | `errors.externalCustomerId` | `string` | External customer identifier associated with the user whose certification grant failed. | | `errors.resourceId` | `uuid` | Unique identifier of the certification resource that could not be granted. | | `errors.error` | `string` | Human-readable message describing why the certification grant failed. | | `errors.record` | `string` | Raw record string associated with the failed entry, useful for debugging bulk input. | --- ### Certify Manually certifies a user against a course, learning path, or other supported resource, returning the resulting certificate record. ### POST /v2/users/:id/certify #### Example request ```bash curl -X POST "$BASE_URL/users/:id/certify" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "resourceType": "string", "resourceId": "00000000-0000-0000-0000-000000000000", "expirationDate": "string" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | Unique identifier of the user to certify. | | `resourceType` | `CertificateResourceType` | Yes | body | The type of resource the certificate is issued against (e.g., course, learning path). | | `resourceId` | `uuid` | Yes | body | Unique identifier of the resource the certificate is issued against. | | `expirationDate` | `ISO 8601 timestamp` | No | body | Date and time at which the certificate expires, as an ISO 8601 timestamp. | | `sendEmail` | `boolean` | No | body | When `true`, sends a certificate notification email to the user. | | `issuedAt` | `ISO 8601 timestamp` | No | body | Date and time the certificate was issued, as an ISO 8601 timestamp. Defaults to the current time if omitted. | | `recertificationDate` | `ISO 8601 timestamp` | No | body | Date and time by which the user must recertify, as an ISO 8601 timestamp. | #### Example response ```json { "data": { "APICertifyUser": { "id": "00000000-0000-0000-0000-000000000000", "issuedAt": "2024-01-01T00:00:00.000Z", "url": "https://example.com/path", "resourceId": "00000000-0000-0000-0000-000000000000", "resourceType": "string", "expirationDate": "2024-01-01T00:00:00.000Z", "recertificationDate": "2024-01-01T00:00:00.000Z", "pdfAsset": "https://example.com/path", "user": { "id": "00000000-0000-0000-0000-000000000000", "learnerUserId": "00000000-0000-0000-0000-000000000000", "managerUserId": "00000000-0000-0000-0000-000000000000", "clientId": "00000000-0000-0000-0000-000000000000", "createdAt": "2024-01-01T00:00:00.000Z", "disabled": true, "sfAccountId": "00000000-0000-0000-0000-000000000000", "sfContactId": "00000000-0000-0000-0000-000000000000", "roleKey": "string", "address1": "string", "address2": "string", "city": "string", "state": "string", "zipCode": "string", "telephone": "string", "country": "string", "stripeCustomerId": "00000000-0000-0000-0000-000000000000", "externalCustomerId": "00000000-0000-0000-0000-000000000000", "shippingName": "string", "asset": "string", "email": "string", "firstName": "string", "lastName": "string", "lastActiveAt": "2024-01-01T00:00:00.000Z", "ref1": "string", "ref2": "string", "ref3": "string", "ref4": "string", "ref5": "string", "ref6": "string", "ref7": "string", "ref8": "string", "ref9": "string", "ref10": "string", "language": "string", "customFields": {}, "purchasedBundles": [ { "bundleId": "00000000-0000-0000-0000-000000000000" } ], "allocatedLicenses": [ { "licenseId": "00000000-0000-0000-0000-000000000000" } ], "allocatedLearningPaths": [ { "learningPathId": "00000000-0000-0000-0000-000000000000", "status": "string" } ], "purchasedCourses": [ { "courseId": "00000000-0000-0000-0000-000000000000", "status": "string" } ], "waitlistedCourses": [ { "courseId": "00000000-0000-0000-0000-000000000000" } ], "balance": 0, "preferredCurrency": "string" } } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Unique identifier of the certificate record. | | `issuedAt` | `ISO 8601 timestamp` | Date and time the certificate was issued. | | `url` | `string` | URL where the certificate can be viewed. | | `resourceId` | `uuid` | Unique identifier of the resource the certificate was issued against. | | `resourceType` | `CertificateResourceType` | Type of resource the certificate was issued against. | | `expirationDate` | `ISO 8601 timestamp` | Date and time at which the certificate expires. | | `recertificationDate` | `ISO 8601 timestamp` | Date and time by which the user must recertify. | | `pdfAsset` | `string` | URL of the certificate's PDF asset. | | `user` | `User` | The certified user. | | `user.id` | `uuid` | Unique identifier of the user. | | `user.learnerUserId` | `uuid` | Identifier of the learner-facing user record, if distinct from the admin/manager record. | | `user.managerUserId` | `uuid` | Identifier of the user's manager, if any. | | `user.clientId` | `uuid` | Identifier of the client the user belongs to. | | `user.createdAt` | `ISO 8601 timestamp` | Date and time the user was created. | | `user.disabled` | `boolean` | Indicates whether the user account is disabled. | | `user.sfAccountId` | `uuid` | Associated Salesforce account identifier. | | `user.sfContactId` | `uuid` | Associated Salesforce contact identifier. | | `user.roleKey` | `string` | The user's role. | | `user.address1` | `string` | First line of the user's address. | | `user.address2` | `string` | Second line of the user's address. | | `user.city` | `string` | City of the user's address. | | `user.state` | `string` | State or region of the user's address. | | `user.zipCode` | `string` | Postal code of the user's address. | | `user.telephone` | `string` | The user's telephone number. | | `user.country` | `string` | Country of the user's address. | | `user.stripeCustomerId` | `uuid` | Associated Stripe customer identifier. | | `user.externalCustomerId` | `uuid` | External customer identifier associated with the user. | | `user.shippingName` | `string` | Name used for shipping physical goods to the user. | | `user.asset` | `string` | URL of the user's profile image. | | `user.email` | `string` | Email address of the user. | | `user.firstName` | `string` | First name of the user. | | `user.lastName` | `string` | Last name of the user. | | `user.lastActiveAt` | `ISO 8601 timestamp` | Date and time the user was last active. | | `user.ref1` | `string` | Custom reference field 1 associated with the user. | | `user.ref2` | `string` | Custom reference field 2 associated with the user. | | `user.ref3` | `string` | Custom reference field 3 associated with the user. | | `user.ref4` | `string` | Custom reference field 4 associated with the user. | | `user.ref5` | `string` | Custom reference field 5 associated with the user. | | `user.ref6` | `string` | Custom reference field 6 associated with the user. | | `user.ref7` | `string` | Custom reference field 7 associated with the user. | | `user.ref8` | `string` | Custom reference field 8 associated with the user. | | `user.ref9` | `string` | Custom reference field 9 associated with the user. | | `user.ref10` | `string` | Custom reference field 10 associated with the user. | | `user.language` | `string` | The user's preferred language. | | `user.customFields` | `object` | Key-value map of custom fields defined for the user. | | `user.purchasedBundles` | `object[]` | Bundles the user has purchased. | | `user.purchasedBundles.bundleId` | `uuid` | Identifier of the purchased bundle. | | `user.allocatedLicenses` | `object[]` | Licenses allocated to the user. | | `user.allocatedLicenses.licenseId` | `uuid` | Identifier of the allocated license. | | `user.allocatedLearningPaths` | `object[]` | Learning paths allocated to the user. | | `user.allocatedLearningPaths.learningPathId` | `uuid` | Identifier of the allocated learning path. | | `user.allocatedLearningPaths.status` | `string` | The user's status on the allocated learning path. | | `user.purchasedCourses` | `object[]` | Courses the user has purchased. | | `user.purchasedCourses.courseId` | `uuid` | Identifier of the purchased course. | | `user.purchasedCourses.status` | `string` | The user's status on the purchased course. | | `user.waitlistedCourses` | `object[]` | Courses the user is waitlisted for. | | `user.waitlistedCourses.courseId` | `uuid` | Identifier of the waitlisted course. | | `user.balance` | `integer` | The user's current account balance. | | `user.preferredCurrency` | `string` | The user's preferred currency. | --- ### Recertify Triggers recertification for a user on a specific certified resource. ### POST /v2/users/:id/recertify #### Example request ```bash curl -X POST "$BASE_URL/users/:id/recertify" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "resourceType": "string", "resourceId": "00000000-0000-0000-0000-000000000000" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The UUID of the user to recertify. | | `resourceType` | `CertificateResourceType` | Yes | body | The type of resource for which recertification is being issued (e.g., a course or learning path). | | `resourceId` | `uuid` | Yes | body | The UUID of the resource for which recertification is being issued. | #### Example response ```json { "data": { "APIRecertifyUser": true } } ``` --- --- ## Commerce ### List Non-Recurring Purchases List non-recurring purchases (one-time payments), paginated. Used for reporting and reconciliation. ### GET /v2/ecommerce/nonRecurringPurchases #### Example request ```bash curl "$BASE_URL/ecommerce/nonRecurringPurchases" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque pagination cursor from a previous response. | | `query` | `string` | No | query | Free-text search to filter transactions by keyword. | #### Example response Because of how this endpoint wraps its GraphQL result, the response is returned nested under `data.APIAnalyticsFoxyTransactionItems`. ```json { "data": { "APIAnalyticsFoxyTransactionItems": { "pageInfo": { "total": 142, "hasMore": true, "cursor": "eyJwYWdlIjoyfQ==" }, "analyticsFoxyTransactionItems": [ { "amountCharged": 49.99, "amountDiscounted": 0, "amountNet": 49.99, "amountRefunded": 0, "amountTaxed": 4.50, "companyId": "c1ie7700-1111-2222-3333-444455556666", "contentId": "8f2a1c34-5b6d-4e7f-9a01-2b3c4d5e6f70", "contentType": "course", "count": 1, "couponCode": null, "couponId": null, "createdAt": "2026-01-15T10:30:00.000Z", "currencyCode": "USD", "currencyConfigurationId": "cc000000-0000-0000-0000-000000000001", "currencyConfigurationName": "US Dollar", "currencyName": "US Dollar", "giftRecipientEmail": null, "id": "txn00000-1111-2222-3333-444455556666", "isBulkPurchase": false, "paymentGatewayId": "pg000000-0000-0000-0000-000000000001", "paymentGatewayIsLive": true, "paymentGatewayName": "Stripe", "paymentGatewaySubscriptionId": null, "paymentGatewayTransactionId": "pi_3abc123def456", "paymentMethodId": "pm000000-0000-0000-0000-000000000001", "paymentMethodName": "Credit Card", "purchaseType": "individual", "quantity": 1, "storeId": "st000000-0000-0000-0000-000000000001", "storeName": "Main Store", "subscriptionFailureMessage": null, "subscriptionPeriod": null, "success": true, "taxProviderName": "Avalara", "title": "Onboarding Essentials", "transactionId": "txn00000-1111-2222-3333-444455556666", "updatedAt": "2026-01-15T10:30:00.000Z", "userAddress": "123 Main St, Springfield, IL 62701", "userId": "u0000000-1111-2222-3333-444455556666", "variation": null } ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `object` | Pagination metadata for the result set. | | `pageInfo.total` | `integer` | Total number of transaction items matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `pageInfo.cursor` | `string` | Opaque cursor for the next page. Present only when `hasMore` is `true`. | | `analyticsFoxyTransactionItems` | `AnalyticsFoxyTransactionItem[]` | Array of non-recurring purchase transaction items. | | `analyticsFoxyTransactionItems.amountCharged` | `number` | Total amount charged to the buyer for this transaction item, in the transaction currency. | | `analyticsFoxyTransactionItems.amountDiscounted` | `number` | Total discount amount applied to this transaction item, in the transaction currency. | | `analyticsFoxyTransactionItems.amountNet` | `number` | Net amount after discounts and refunds, in the transaction currency. | | `analyticsFoxyTransactionItems.amountRefunded` | `number` | Total amount refunded for this transaction item, in the transaction currency. | | `analyticsFoxyTransactionItems.amountTaxed` | `number` | Tax amount applied to this transaction item, in the transaction currency. | | `analyticsFoxyTransactionItems.companyId` | `string` | Unique identifier of the company associated with this transaction. | | `analyticsFoxyTransactionItems.contentId` | `string` | Unique identifier of the purchased content item (e.g., course or bundle). | | `analyticsFoxyTransactionItems.contentType` | `string` | Type of content purchased, such as a course or learning path. | | `analyticsFoxyTransactionItems.count` | `integer` | Number of transaction records aggregated in this item. | | `analyticsFoxyTransactionItems.couponCode` | `string` | Coupon code applied to this transaction, if any. | | `analyticsFoxyTransactionItems.couponId` | `string` | Unique identifier of the coupon applied to this transaction. | | `analyticsFoxyTransactionItems.createdAt` | `string` | ISO 8601 timestamp indicating when this transaction item was created. | | `analyticsFoxyTransactionItems.currencyCode` | `string` | ISO 4217 currency code for the transaction (e.g., `USD`). | | `analyticsFoxyTransactionItems.currencyConfigurationId` | `string` | Unique identifier of the currency configuration used for this transaction. | | `analyticsFoxyTransactionItems.currencyConfigurationName` | `string` | Display name of the currency configuration used for this transaction. | | `analyticsFoxyTransactionItems.currencyName` | `string` | Human-readable name of the currency used in this transaction. | | `analyticsFoxyTransactionItems.giftRecipientEmail` | `string` | Email address of the gift recipient, if this was a gift purchase. | | `analyticsFoxyTransactionItems.id` | `string` | Unique identifier of this transaction item. | | `analyticsFoxyTransactionItems.isBulkPurchase` | `boolean` | Indicates whether this transaction was part of a bulk purchase. | | `analyticsFoxyTransactionItems.paymentGatewayId` | `string` | Unique identifier of the payment gateway that processed this transaction. | | `analyticsFoxyTransactionItems.paymentGatewayIsLive` | `boolean` | Indicates whether the payment gateway was operating in live (production) mode. | | `analyticsFoxyTransactionItems.paymentGatewayName` | `string` | Display name of the payment gateway that processed this transaction. | | `analyticsFoxyTransactionItems.paymentGatewaySubscriptionId` | `string` | Payment gateway's identifier for the associated subscription, if applicable. | | `analyticsFoxyTransactionItems.paymentGatewayTransactionId` | `string` | Payment gateway's identifier for this specific transaction. | | `analyticsFoxyTransactionItems.paymentMethodId` | `string` | Unique identifier of the payment method used in this transaction. | | `analyticsFoxyTransactionItems.paymentMethodName` | `string` | Display name of the payment method used in this transaction (e.g., `Credit Card`). | | `analyticsFoxyTransactionItems.purchaseType` | `string` | Classification of the purchase type (e.g., individual or gift). | | `analyticsFoxyTransactionItems.quantity` | `integer` | Number of units purchased in this transaction item. | | `analyticsFoxyTransactionItems.storeId` | `string` | Unique identifier of the store where the purchase was made. | | `analyticsFoxyTransactionItems.storeName` | `string` | Display name of the store where the purchase was made. | | `analyticsFoxyTransactionItems.subscriptionFailureMessage` | `string` | Error message describing why a subscription payment failed, if applicable. | | `analyticsFoxyTransactionItems.subscriptionPeriod` | `string` | Billing period of the subscription associated with this transaction, if applicable. | | `analyticsFoxyTransactionItems.success` | `boolean` | Indicates whether the transaction completed successfully. | | `analyticsFoxyTransactionItems.taxProviderName` | `string` | Name of the tax provider used to calculate tax for this transaction. | | `analyticsFoxyTransactionItems.title` | `string` | Display title of the purchased content item. | | `analyticsFoxyTransactionItems.transactionId` | `string` | Unique identifier of the parent transaction containing this item. | | `analyticsFoxyTransactionItems.updatedAt` | `string` | ISO 8601 timestamp indicating when this transaction item was last updated. | | `analyticsFoxyTransactionItems.userAddress` | `string` | Billing address of the user who made the purchase. | | `analyticsFoxyTransactionItems.userId` | `string` | Unique identifier of the user who made the purchase. | | `analyticsFoxyTransactionItems.variation` | `string` | Product variation selected at the time of purchase, if applicable. | --- ### Course Coupons Returns a paginated list of course coupons, optionally filtered by course, coupon code, or active status. ### GET /v2/course-coupons #### Example request ```bash curl "$BASE_URL/course-coupons" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `courseIds` | `uuid[]` | No | query | Comma-separated list of course UUIDs to filter by. Maximum 50 IDs per request. | | `page` | `integer` | No | query | Page number to retrieve for paginated results. | | `code` | `string` | No | query | Filters results to the coupon matching this code. | | `active` | `boolean` | No | query | When `true`, returns only active coupons; when `false`, returns only inactive coupons. | Requests with more than 50 `courseIds` return `400 Bad Request` with `{"error":"Too many courseIds provided. Maximum allowed is 50, received N."}`. #### Example response ```json { "pageInfo": { "currentPage": 1, "total": 12, "hasMore": false }, "courseCoupons": [ { "id": "00000000-0000-0000-0000-000000000000", "courseId": "00000000-0000-0000-0000-000000000000", "code": "string", "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "maxRedemptions": 0, "percentOff": 0, "amountOffInCents": 0, "timesRedeemed": 0, "redeemBy": "2024-01-01T00:00:00.000Z", "appliesToUser": "existing", "active": true, "generated": true, "accessDays": 0 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `object` | Pagination metadata for the result set. | | `pageInfo.currentPage` | `integer` | The current page number. | | `pageInfo.total` | `integer` | Total number of coupons matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results exist. | | `courseCoupons` | `CourseCoupon[]` | Array of course coupon objects matching the query. | | `courseCoupons.id` | `uuid` | Unique identifier for the coupon. | | `courseCoupons.courseId` | `uuid` | Unique identifier of the course this coupon applies to. | | `courseCoupons.code` | `string` | The coupon code string a learner enters at checkout. | | `courseCoupons.createdAt` | `ISO 8601 timestamp` | Date and time the coupon was created, in ISO 8601 format. | | `courseCoupons.updatedAt` | `ISO 8601 timestamp` | Date and time the coupon was last modified, in ISO 8601 format. | | `courseCoupons.maxRedemptions` | `integer` | Maximum number of times this coupon can be redeemed; `0` indicates no limit. | | `courseCoupons.percentOff` | `integer` | Percentage discount applied to the course price when this coupon is redeemed. | | `courseCoupons.amountOffInCents` | `integer` | Fixed discount amount in cents applied to the course price when this coupon is redeemed. | | `courseCoupons.timesRedeemed` | `integer` | Number of times this coupon has been redeemed so far. | | `courseCoupons.redeemBy` | `ISO 8601 timestamp` | Expiration date and time after which the coupon can no longer be redeemed, in ISO 8601 format. | | `courseCoupons.appliesToUser` | `AppliesToUser` | Restricts coupon eligibility by user type; for example, `"existing"` limits redemption to existing users. | | `courseCoupons.active` | `boolean` | Indicates whether the coupon is currently active and available for redemption. | | `courseCoupons.generated` | `boolean` | Indicates whether the coupon code was system-generated rather than manually created. | | `courseCoupons.accessDays` | `integer` | Number of days of course access granted when this coupon is redeemed. | --- ### Create Credit Batch Creates a new credit batch for the specified client, setting an initial balance and optionally associating the batch with a credit account and expiration date. ### POST /v2/clients/:id/creditBatches #### Example request ```bash curl -X POST "$BASE_URL/clients/:id/creditBatches" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "clientId": "00000000-0000-0000-0000-000000000000", "startingBalance": 0, "creditAccountId": "00000000-0000-0000-0000-000000000000", "name": "string" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `string` | Yes | path | The client UUID. Used as `clientId` on the mutation. | | `startingBalance` | `number` | Yes | body | Initial number of credits to assign to the batch. | | `name` | `string` | Yes | body | Display name for the credit batch. | | `creditAccountId` | `uuid` | No | body | UUID of the credit account to link to this batch. | | `expiresOn` | `ISO 8601 timestamp` | No | body | Date and time at which the credit batch expires, in ISO 8601 format. | #### Example response ```json { "id": "00000000-0000-0000-0000-000000000000", "name": "Q1 Onboarding", "startingBalance": 100, "currentBalance": 100, "expiresOn": "2026-04-01T00:00:00.000Z" } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Unique identifier of the newly created credit batch. | | `name` | `string` | Display name of the credit batch. | | `startingBalance` | `number` | Number of credits assigned to the batch at creation. | | `currentBalance` | `number` | Number of credits currently remaining in the batch. | | `expiresOn` | `ISO 8601 timestamp` | Date and time at which the credit batch expires, in ISO 8601 format. | --- ### Create Credit Account Creates a credit account for the specified client. ### POST /v2/clients/:id/creditAccounts #### Example request ```bash curl -X POST "$BASE_URL/clients/:id/creditAccounts" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "batchDaysUntilExpire": 0, "clientId": "00000000-0000-0000-0000-000000000000", "startingBalance": 0, "minimumBatchAmount": 0 }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `string` | Yes | path | The client UUID. Used as `clientId` on the mutation. | | `name` | `string` | Yes | body | Display name for the credit account. | | `startingBalance` | `number` | Yes | body | Initial credit balance assigned to the account at creation. | | `licenseIds` | `string[]` | Yes | body | List of license IDs to associate with this credit account. | | `notifyEmails` | `string[]` | No | body | Email addresses to notify when account activity occurs. Defaults to `[]`. | | `creditBatchName` | `string` | No | body | Label applied to credit batches issued under this account. | | `batchDaysUntilExpire` | `integer` | No | body | Number of days until a credit batch expires after being issued. | | `minimumBatchAmount` | `integer` | No | body | Minimum number of credits per credit batch. | The `type` field is hardcoded to `client` server-side and is not accepted from the request body. #### Example response ```json { "__typename": "CreditAccount", "id": "00000000-0000-0000-0000-000000000000", "name": "Acme Onboarding Credits" } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Unique identifier of the newly created credit account. | | `name` | `string` | Display name of the credit account. | | `__typename` | `string` | GraphQL type name, always `CreditAccount`. | --- ### Archive Deletes a credit batch from the specified client. ### DELETE /v2/clients/:id/creditBatches/:batchId #### Example request ```bash curl -X DELETE "$BASE_URL/clients/:id/creditBatches/:batchId" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The client UUID. | | `batchId` | `string` | Yes | path | The credit batch UUID to archive. | #### Example response ```json { "deleted": true } ``` --- ### Show Credit Batches Returns a paginated list of credit batches for a specified client and credit account. ### GET /v2/clients/:id/creditBatches #### Example request ```bash curl "$BASE_URL/clients/:id/creditBatches" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `string` | Yes | path | The client UUID (used as `clientId`). | | `creditAccountId` | `uuid` | No | query | Filters results to credit batches belonging to the specified credit account. | | `cursor` | `string` | No | query | Opaque pagination cursor from a previous response. | #### Example response ```json { "clientId": "00000000-0000-0000-0000-000000000000", "creditAccountId": "00000000-0000-0000-0000-000000000000", "creditBatches": [ { "__typename": "CreditBatch", "id": "00000000-0000-0000-0000-000000000000", "name": "Q1 Onboarding", "creditAccountId": "00000000-0000-0000-0000-000000000000", "startingBalance": 100, "currentBalance": 82, "createdAt": "2026-01-01T00:00:00.000Z", "expiresOn": "2026-04-01T00:00:00.000Z" } ], "pageInfo": { "__typename": "CursorPageInfo", "perPage": 50, "total": 12, "hasMore": false } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `clientId` | `uuid` | Unique identifier of the client associated with the returned credit batches. | | `creditAccountId` | `uuid` | Unique identifier of the credit account associated with the returned credit batches. | | `creditBatches` | `CreditBatch[]` | Array of credit batch objects belonging to the client and credit account. | | `creditBatches.id` | `uuid` | Unique identifier of the credit batch. | | `creditBatches.name` | `string` | Display name of the credit batch. | | `creditBatches.creditAccountId` | `uuid` | Unique identifier of the credit account this batch belongs to. | | `creditBatches.startingBalance` | `number` | Total number of credits originally issued in the batch. | | `creditBatches.currentBalance` | `number` | Number of credits remaining in the batch after allocations and usage. | | `creditBatches.createdAt` | `ISO 8601 timestamp` | Date and time when the credit batch was created, in ISO 8601 format. | | `creditBatches.expiresOn` | `ISO 8601 timestamp` | Date and time when the credit batch expires, in ISO 8601 format. | | `pageInfo` | `CursorPageInfo` | Pagination metadata for the result set. | | `pageInfo.perPage` | `integer` | Number of credit batches returned per page. | | `pageInfo.cursor` | `string` | Cursor value for the next page. Present only when `hasMore` is `true`. | | `pageInfo.total` | `integer` | Total number of credit batches matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | --- ### Show Credit Actions Returns a paginated list of credit actions for a specific credit account, filtered by client and action type. ### GET /v2/clients/:id/creditAccount/:creditAccountId/creditActions #### Example request ```bash curl "$BASE_URL/clients/:id/creditAccount/:creditAccountId/creditActions" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `string` | Yes | path | The client UUID (used as `clientId`). | | `creditAccountId` | `uuid` | Yes | path | The credit account UUID. | | `actionType` | `string` | Yes | query | Filter by action type. One of `batch_created`, `batch_deleted`, or `credit_redemption`. Any other value returns a GraphQL error `Invalid actionType`. | | `cursor` | `string` | No | query | Opaque pagination cursor from a previous response. | #### Example response ```json { "clientId": "00000000-0000-0000-0000-000000000000", "creditAccountId": "00000000-0000-0000-0000-000000000000", "creditActions": [ { "__typename": "CreditAction", "id": "00000000-0000-0000-0000-000000000000", "initiator": "00000000-0000-0000-0000-000000000000", "action": "credit_redemption", "amount": 10, "targetType": "user", "target": "00000000-0000-0000-0000-000000000000", "creditAccountName": "Acme Onboarding Credits", "source": "00000000-0000-0000-0000-000000000000", "sourceType": "credit_batch", "timestamp": "2026-02-15T10:12:00.000Z" } ], "pageInfo": { "__typename": "CursorPageInfo", "perPage": 50, "total": 87, "hasMore": true, "cursor": "eyJwYWdlIjoyfQ==" } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `clientId` | `uuid` | The UUID of the client associated with the returned credit actions. | | `creditAccountId` | `uuid` | The UUID of the credit account associated with the returned credit actions. | | `creditActions` | `CreditAction[]` | The list of credit actions matching the specified filters. | | `creditActions.id` | `uuid` | The unique identifier of the credit action. | | `creditActions.action` | `string` | The action name, matching the `actionType` filter. | | `creditActions.amount` | `number` | The number of credits involved in this action. | | `creditActions.initiator` | `uuid` | The UUID of the entity that triggered the credit action. | | `creditActions.target` | `uuid` | The UUID of the entity receiving or affected by the credit action. | | `creditActions.targetType` | `string` | The type of entity identified by `target` (for example, `user`). | | `creditActions.creditAccountName` | `string` | The display name of the credit account associated with this action. | | `creditActions.source` | `uuid` | The UUID of the entity from which credits were drawn or transferred. | | `creditActions.sourceType` | `string` | The type of entity identified by `source`. | | `creditActions.timestamp` | `ISO 8601 timestamp` | The date and time when the credit action occurred. | | `pageInfo` | `CursorPageInfo` | Pagination metadata for the returned results. | | `pageInfo.perPage` | `integer` | The number of credit actions returned per page. | | `pageInfo.cursor` | `string` | Cursor for the next page. Present only when `hasMore` is `true`. | | `pageInfo.total` | `integer` | The total number of credit actions matching the specified filters. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | --- ### Update Credit Batch Updates an existing credit batch by ID, allowing you to modify its name, expiration date, or current balance. ### PUT /v2/clients/creditBatches/:id #### Example request ```bash curl -X PUT "$BASE_URL/clients/creditBatches/:id" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "name": "string", "expiresOn": "string", "currentBalance": 0 }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The credit batch UUID. | | `name` | `string` | No | body | Display name for the credit batch. | | `expiresOn` | `ISO 8601 timestamp` | No | body | Date and time when the credit batch expires, in ISO 8601 format. | | `currentBalance` | `integer` | No | body | Number of credits currently available in the batch. | #### Example response ```json { "id": "00000000-0000-0000-0000-000000000000", "name": "Q1 Onboarding — extended", "expiresOn": "2026-05-01T00:00:00.000Z", "currentBalance": 75 } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Unique identifier of the credit batch. | | `name` | `string` | Display name of the credit batch. | | `expiresOn` | `ISO 8601 timestamp` | Date and time when the credit batch expires, in ISO 8601 format. | | `currentBalance` | `number` | Number of credits currently remaining in the batch. | --- ### List Redemption Code Groups List redemption code groups, paginated. Results are 50 per page. ### GET /v2/redemptionCodes #### Example request ```bash curl "$BASE_URL/redemptionCodes" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Pagination cursor (opaque token returned in `pageInfo.cursor`). | | `query` | `string` | No | query | Free-text search. | | `sort` | `string` | No | query | Sort order (e.g. `label:asc`). | #### Example response ```json { "redemptionCodeGroups": [ { "id": "rc100000-1111-2222-3333-444455556666", "label": "Spring Promo", "redeemedCodesCount": 18, "totalCodesCount": 100, "expirationDate": "2025-06-30T23:59:59.000Z", "accessDays": 90, "accessExpirationDate": null, "uploaded": false, "gift": false, "client": null, "courses": [ { "id": "8f2a1c34-5b6d-4e7f-9a01-2b3c4d5e6f70", "title": "Onboarding Essentials" } ], "learningPaths": [], "bundles": [], "licenses": [] }, { "id": "rc100001-7777-8888-9999-aaaabbbbcccc", "label": "Partner Gift Codes", "redeemedCodesCount": 250, "totalCodesCount": 250, "expirationDate": null, "accessDays": null, "accessExpirationDate": "2026-01-01T00:00:00.000Z", "uploaded": true, "gift": true, "client": { "id": "c1ie7700-1111-2222-3333-444455556666", "name": "Acme Corp" }, "courses": [], "learningPaths": [ { "id": "1d9e8f70-4a3b-4c2d-8e1f-0a9b8c7d6e5f", "name": "Manager Fundamentals" } ], "bundles": [], "licenses": [] } ], "pageInfo": { "total": 2, "hasMore": true, "cursor": "eyJwYWdlIjoyfQ==" } } ``` #### Example queries Free-text search by label: ```bash curl "$BASE_URL/redemptionCodes?query=spring" \ -H 'Authorization: Bearer APIKEY' ``` Sort results: ```bash curl "$BASE_URL/redemptionCodes?sort=label:asc" \ -H 'Authorization: Bearer APIKEY' ``` Paginate using cursor from previous response: ```bash curl "$BASE_URL/redemptionCodes?cursor=eyJwYWdlIjoyfQ==" \ -H 'Authorization: Bearer APIKEY' ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `redemptionCodeGroups[].id` | `uuid` | Group ID. | | `redemptionCodeGroups[].label` | `string` | Group label. | | `redemptionCodeGroups[].redeemedCodesCount` | `integer` | Number of codes redeemed. | | `redemptionCodeGroups[].totalCodesCount` | `integer` | Total codes in group. | | `redemptionCodeGroups[].expirationDate` | `ISO 8601 date` | When codes expire. | | `redemptionCodeGroups[].accessDays` | `integer` | Days of access after redemption. | | `redemptionCodeGroups[].accessExpirationDate` | `ISO 8601 date` | Absolute access expiry. | | `redemptionCodeGroups[].uploaded` | `boolean` | True if codes were imported (vs generated). | | `redemptionCodeGroups[].gift` | `boolean` | Whether group is gift-type. | | `redemptionCodeGroups[].client` | `object` | `{ id, name }` or null. | | `redemptionCodeGroups[].courses` | `object[]` | Array of `{ id, title }`. | | `redemptionCodeGroups[].learningPaths` | `object[]` | Array of `{ id, name }`. | | `redemptionCodeGroups[].bundles` | `object[]` | Array of `{ id, name }`. | | `redemptionCodeGroups[].licenses` | `object[]` | Array of `{ id, name }`. | | `pageInfo.total` | `integer` | Total number of groups. | | `pageInfo.hasMore` | `boolean` | Whether more pages exist. | | `pageInfo.cursor` | `string` | Cursor for next page (only present when `hasMore` is true). | --- ### Get Redemption Code Group Returns a single redemption code group by ID. ### GET /v2/redemptionCodes/:id #### Example request ```bash curl "$BASE_URL/redemptionCodes/:id" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | Redemption code group UUID. | #### Example response ```json { "id": "rc100000-1111-2222-3333-444455556666", "label": "Spring Promo", "redeemedCodesCount": 18, "totalCodesCount": 100, "expirationDate": "2025-06-30T23:59:59.000Z", "accessDays": 90, "accessExpirationDate": null, "uploaded": false, "gift": false, "client": null, "courses": [ { "id": "8f2a1c34-5b6d-4e7f-9a01-2b3c4d5e6f70", "title": "Onboarding Essentials" } ], "learningPaths": [], "bundles": [], "licenses": [] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Group ID. | | `label` | `string` | Group label. | | `redeemedCodesCount` | `integer` | Number of codes redeemed. | | `totalCodesCount` | `integer` | Total codes in group. | | `expirationDate` | `ISO 8601 date` | When codes expire. | | `accessDays` | `integer` | Days of access after redemption. | | `accessExpirationDate` | `ISO 8601 date` | Absolute access expiry. | | `uploaded` | `boolean` | True if codes were imported (vs generated). | | `gift` | `boolean` | Whether group is gift-type. | | `client` | `object` | { id, name } or null. | | `courses` | `object[]` | Array of { id, title }. | | `learningPaths` | `object[]` | Array of { id, name }. | | `bundles` | `object[]` | Array of { id, name }. | | `licenses` | `object[]` | Array of { id, name }. | --- ### List Redemption Codes List the individual codes within a redemption code group, sorted by `createdAt` ascending. ### GET /v2/redemptionCodes/:id/codes #### Example request ```bash curl "$BASE_URL/redemptionCodes//codes" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | Redemption code group UUID. | Returns `404` with `{ "errors": ["Redemption code not found"] }` if the group does not exist or does not belong to the requesting company. #### Example response ```json { "codes": [ { "code": "SPRING-AB12CD34", "redeemed": true, "redeemedAt": "Apr 12, 2025", "createdAt": "Mar 01, 2025", "firstName": "Jordan", "lastName": "Lee", "email": "jordan.lee@example.com" }, { "code": "SPRING-EF56GH78", "redeemed": false, "redeemedAt": null, "createdAt": "Mar 01, 2025", "firstName": "", "lastName": "", "email": "" }, { "code": "SPRING-IJ90KL12", "redeemed": true, "redeemedAt": "Apr 20, 2025", "createdAt": "Mar 01, 2025", "firstName": "Deleted", "lastName": "User", "email": "deleteduser@example.org" } ] } ``` The redeeming user's identity is exposed only through the `firstName`, `lastName`, and `email` fields — there is no `redeemedByUserId`. Anonymized or deleted users return `"Deleted"` / `"User"` / `deleteduser@example.org` rather than their original details. #### Response fields | Field | Type | Description | |-------|------|-------------| | `codes[].code` | `string` | The redemption code. | | `codes[].redeemed` | `boolean` | Whether the code has been redeemed. | | `codes[].redeemedAt` | `string` | Formatted date, or null if not redeemed. | | `codes[].createdAt` | `string` | Formatted date. | | `codes[].firstName` | `string` | Redeeming user's first name (empty if not redeemed, `"Deleted"` if anonymized). | | `codes[].lastName` | `string` | Redeeming user's last name (empty if not redeemed, `"User"` if anonymized). | | `codes[].email` | `string` | Redeeming user's email (empty if not redeemed, `deleteduser@example.org` if anonymized). | --- ### Create Redemption Code Group Create a redemption code group and generate its codes. ### POST /v2/redemptionCodes #### Example request ```bash curl -X POST "$BASE_URL/redemptionCodes" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "label": "Spring Promo", "codeLength": 8, "totalCodesCount": 100, "courses": [""] }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `label` | `string` | Yes | body | Group label. | | `codeLength` | `integer` | Yes | body | Length of each generated code. | | `totalCodesCount` | `integer` | Yes | body | Number of codes to generate (max 10,000). | | `client` | `uuid` | No | body | Client UUID. | | `expirationDate` | `ISO 8601 date` | No | body | When codes expire. | | `accessDays` | `integer` | No | body | Days of access after redemption. Mutually exclusive with `accessExpirationDate`. | | `accessExpirationDate` | `ISO 8601 date` | No | body | Absolute access expiry. Mutually exclusive with `accessDays`. | | `courses` | `string[]` | No | body | Course UUIDs. | | `bundles` | `string[]` | No | body | Bundle UUIDs. | | `licenses` | `string[]` | No | body | License UUIDs. | | `learningPaths` | `string[]` | No | body | Learning path UUIDs. | :::note **Constraints:** - At least one of `courses`, `bundles`, `licenses`, or `learningPaths` must be provided. - `accessDays` and `accessExpirationDate` are mutually exclusive — setting both is rejected. - `totalCodesCount` cannot exceed 10,000. ::: #### Example response ```json { "id": "rc100000-1111-2222-3333-444455556666", "label": "Spring Promo", "redeemedCodesCount": 0, "totalCodesCount": 100, "expirationDate": null, "accessDays": null, "accessExpirationDate": null, "client": null, "courses": [ { "id": "8f2a1c34-5b6d-4e7f-9a01-2b3c4d5e6f70", "title": "Onboarding Essentials" } ], "learningPaths": [], "bundles": [], "licenses": [] } ``` #### Validation errors Validation failures return `{ "errors": [message] }`: ```json { "errors": [ "Please choose at least one item to give access to, or select a license." ] } ``` Other validation messages include: - `"Generated Redemption Code Groups must have a value for code length and total codes."` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Created group ID. | | `label` | `string` | Group label. | | `redeemedCodesCount` | `integer` | Always 0 for new groups. | | `totalCodesCount` | `integer` | Number of codes generated. | | `expirationDate` | `ISO 8601 date` | When codes expire. | | `accessDays` | `integer` | Days of access after redemption. | | `accessExpirationDate` | `ISO 8601 date` | Absolute access expiry. | | `client` | `object` | `{ id, name }` or null. | | `courses` | `object[]` | Array of `{ id, title }`. | | `learningPaths` | `object[]` | Array of `{ id, name }`. | | `bundles` | `object[]` | Array of `{ id, name }`. | | `licenses` | `object[]` | Array of `{ id, name }`. | --- ### Coupons Returns a paginated list of coupons defined in your catalog. ### GET /v2/coupons #### Example request ```bash curl "$BASE_URL/coupons" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque pagination cursor from a previous response. | #### Example response ```json { "pageInfo": { "total": 42, "hasMore": true, "cursor": "eyJwYWdlIjoyfQ==" }, "coupons": [ { "id": "00000000-0000-0000-0000-000000000000", "active": true, "allowMultipleInCart": true, "appliesToUser": "existing", "code": "string", "timesRedeemed": 0, "maxRedemptions": 0, "redeemBy": "2024-01-01T00:00:00.000Z", "percentOff": 0, "amountOffInCents": 0, "multicurrencyAmountOff": {} } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `object` | Pagination metadata for the result set. | | `pageInfo.total` | `integer` | Total number of coupons across all pages. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `pageInfo.cursor` | `string` | Cursor for the next page. Present only when `hasMore` is `true`. | | `coupons` | `Coupon[]` | Array of coupon objects returned for the current page. | | `coupons.id` | `uuid` | Unique identifier for the coupon. | | `coupons.active` | `boolean` | Indicates whether the coupon is currently active and available for use. | | `coupons.allowMultipleInCart` | `boolean` | Indicates whether the coupon can be applied alongside other coupons in the same cart. | | `coupons.appliesToUser` | `AppliesToUser` | Specifies which users the coupon applies to (e.g., `existing` users only or new users). | | `coupons.code` | `string` | The redemption code learners enter to apply the coupon. | | `coupons.timesRedeemed` | `integer` | Number of times the coupon has been redeemed to date. | | `coupons.maxRedemptions` | `integer` | Maximum number of times the coupon can be redeemed in total. | | `coupons.redeemBy` | `ISO 8601 timestamp` | Expiration date and time after which the coupon can no longer be redeemed, in ISO 8601 format. | | `coupons.percentOff` | `number` | Percentage discount applied to the purchase price when the coupon is redeemed. | | `coupons.amountOffInCents` | `integer` | Fixed discount amount in cents applied to the purchase price when the coupon is redeemed. | | `coupons.multicurrencyAmountOff` | `object` | Fixed discount amounts keyed by currency code, used when multi-currency pricing is enabled. | --- ### Bulk Import Coupon Bulk-imports coupons from an uploaded asset file. Submit the asset path to validate each row and enqueue a background job that processes the import. ### POST /v2/coupons/bulkImport #### Example request ```bash curl -X POST "$BASE_URL/coupons/bulkImport" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "bulkImportAssetPath": "string" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `bulkImportAssetPath` | `string` | Yes | body | The storage path of the uploaded asset file containing the coupon rows to import. | If the instance already has too many pending background jobs, the response body is the string `ERROR_JOB_LIMIT_MSG` and no job is enqueued. #### Example response ```json { "backgroundJob": { "id": "00000000-0000-0000-0000-000000000000", "description": "Bulk import coupons", "errorMessage": null, "status": "pending" } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `backgroundJob` | `object` | The background job created to process the coupon import. | | `backgroundJob.id` | `uuid` | Unique identifier of the background job. | | `backgroundJob.description` | `string` | Human-readable description of the background job. | | `backgroundJob.errorMessage` | `string \| null` | Error message populated if the background job fails. | | `backgroundJob.status` | `BackgroundJobStatus` | Current status of the background job (for example, `pending`, `complete`). | --- --- ## Content ### Content Returns a paginated list of content items matching the specified types and optional filters. ### GET /v2/content #### Example request ```bash curl "$BASE_URL/content" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `types` | `ContentSearchType[]` | Yes | query | One or more content types to include in the search results. | | `cursor` | `string` | No | query | Opaque cursor string for fetching the next page of results. | | `perPage` | `integer` | No | query | Number of content items to return per page. | | `sort` | `string` | No | query | Field name to sort results by. | | `query` | `string` | No | query | Full-text search string to filter content items by title or description. | #### Example response ```json { "data": { "APIContentSearch": { "pageInfo": { "total": 0, "cursor": "string", "hasMore": true }, "contentItems": [ { "id": "00000000-0000-0000-0000-000000000000", "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "hasChildren": true, "courseStartDate": "2024-01-01T00:00:00.000Z", "courseEndDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "kind": "courseGroup", "language": "string", "contentTypeLabel": "string", "asset": "string", "assetAltText": "string", "title": "string", "slug": "example-slug", "description": "string", "metaTitle": "string", "metaDescription": "string", "sku": "string", "customFields": {}, "authorsAndInstructors": [ "string" ], "seatsLimit": 0, "enrollmentCount": 0, "status": "draft", "source": "string", "freeWithRegistration": true, "priceInCents": 0, "suggestedRetailPriceInCents": 0, "waitlistingEnabled": true, "waitlistingTriggered": true, "waitlistCount": 0, "url": "https://example.com/path", "tags": [ { "id": "00000000-0000-0000-0000-000000000000", "label": "string" } ], "prices": [ { "currencyCode": "string", "unitAmount": 0, "suggestedRetailUnitAmount": 0, "instructorAccessUnitAmount": 0, "annualUnitAmount": 0, "bulkPurchasingEnabled": true, "bulkPurchaseTiers": { "seatTiers": [ { "seats": 0, "unitAmount": 0 } ], "seatPackages": [ { "seats": 0 } ], "lastTierPriceUnitAmount": 0 }, "seatTiers": [ { "seats": 0, "unitAmount": 0 } ], "seatPackages": [ { "seats": 0 } ], "locale": "string", "isDefault": true } ] } ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.total` | `integer` | Total number of content items matching the query. | | `pageInfo.cursor` | `string` | Opaque cursor to pass in the next request to retrieve the following page. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `contentItems` | `Content[]` | Array of content items matching the search criteria. | | `contentItems.id` | `uuid` | Unique identifier of the content item. | | `contentItems.createdAt` | `ISO 8601 timestamp` | Date and time the content item was created. | | `contentItems.updatedAt` | `ISO 8601 timestamp` | Date and time the content item was last modified. | | `contentItems.hasChildren` | `boolean` | Indicates whether the content item contains child content items. | | `contentItems.courseStartDate` | `ISO 8601 timestamp` | Date and time the course officially begins. | | `contentItems.courseEndDate` | `ISO 8601 timestamp` | Date and time the course officially ends. | | `contentItems.enrollmentStartDate` | `ISO 8601 timestamp` | Date and time enrollment opens for the content item. | | `contentItems.enrollmentEndDate` | `ISO 8601 timestamp` | Date and time enrollment closes for the content item. | | `contentItems.kind` | `ContentKind` | Content type category, such as `courseGroup` or another supported kind. | | `contentItems.language` | `string` | BCP 47 language code indicating the content item's primary language. | | `contentItems.contentTypeLabel` | `string` | Human-readable label for the content item's type, shown in the UI. | | `contentItems.asset` | `string` | URL or identifier of the content item's primary thumbnail or cover image asset. | | `contentItems.assetAltText` | `string` | Accessible alt text for the content item's primary image asset. | | `contentItems.title` | `string` | Display title of the content item. | | `contentItems.slug` | `string` | URL-friendly identifier for the content item. | | `contentItems.description` | `string` | Full text description of the content item. | | `contentItems.metaTitle` | `string` | SEO meta title for the content item's page. | | `contentItems.metaDescription` | `string` | SEO meta description for the content item's page. | | `contentItems.sku` | `string` | Stock-keeping unit identifier used for catalog or integration purposes. | | `contentItems.customFields` | `object` | Key-value map of custom metadata fields defined for the content item. | | `contentItems.authorsAndInstructors` | `string[]` | Combined list of author and instructor names associated with the content item. | | `contentItems.seatsLimit` | `integer` | Maximum number of enrollments allowed for the content item. | | `contentItems.enrollmentCount` | `integer` | Total number of active enrollments for the content item. | | `contentItems.status` | `Status` | Publication status of the content item, such as `draft` or `published`. | | `contentItems.source` | `string` | Origin or integration source identifier for the content item. | | `contentItems.freeWithRegistration` | `boolean` | Indicates whether the content item is available at no cost upon registration. | | `contentItems.priceInCents` | `integer` | List price of the content item in cents (e.g., `1000` = $10.00). | | `contentItems.suggestedRetailPriceInCents` | `integer` | Suggested retail price of the content item in cents. | | `contentItems.waitlistingEnabled` | `boolean` | Indicates whether waitlisting is enabled for the content item. | | `contentItems.waitlistingTriggered` | `boolean` | Indicates whether the waitlist has been activated due to the enrollment limit being reached. | | `contentItems.waitlistCount` | `integer` | Number of learners currently on the waitlist for the content item. | | `contentItems.url` | `string` | Absolute URL to the content item's detail page. | | `contentItems.tags` | `Tag[]` | Array of tags associated with the content item for categorization and filtering. | | `contentItems.tags.id` | `uuid` | Unique identifier of the tag. | | `contentItems.tags.label` | `string` | Display name of the tag. | | `contentItems.prices` | `ItemPrice[]` | Array of pricing objects available for the content item, including currency and tier details. | | `contentItems.prices.currencyCode` | `string` | ISO currency code for this price. | | `contentItems.prices.unitAmount` | `integer` | Unit price amount. | | `contentItems.prices.suggestedRetailUnitAmount` | `integer` | Suggested retail unit price amount. | | `contentItems.prices.instructorAccessUnitAmount` | `integer` | Unit price amount for instructor access. | | `contentItems.prices.annualUnitAmount` | `integer` | Annual unit price amount. | | `contentItems.prices.bulkPurchasingEnabled` | `boolean` | Indicates whether bulk seat purchasing is enabled for this price. | | `contentItems.prices.bulkPurchaseTiers` | `object` | Bulk purchase tier configuration for this price. | | `contentItems.prices.seatTiers` | `object[]` | Seat-based pricing tiers. | | `contentItems.prices.seatPackages` | `object[]` | Seat package pricing options. | | `contentItems.prices.locale` | `string` | Locale this price applies to. | | `contentItems.prices.isDefault` | `boolean` | Indicates whether this is the default price for the content item. | --- ### Courses Returns a single course by its unique identifier. ### GET /v2/content/courses/:id :::note If `id` ends in `.xlf` (e.g. `GET /v2/content/courses/:id.xlf`), this endpoint instead returns the course's XLIFF translation export as `application/xml` rather than the JSON body below. ::: #### Example request ```bash curl "$BASE_URL/content/courses/:id" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | Unique identifier of the course to retrieve. | #### Example response ```json { "data": { "APIContentBody": { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "slug": "example-slug", "body": "string" } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Unique identifier of the course. | | `title` | `string` | Title of the course. | | `slug` | `string` | URL-friendly identifier for the course. | | `body` | `string` | Full HTML body content of the course. | --- ### LearningPaths Returns a single learning path by its unique identifier. ### GET /v2/content/learningPaths/:id :::note If `id` ends in `.xlf` (e.g. `GET /v2/content/learningPaths/:id.xlf`), this endpoint instead returns the learning path's XLIFF translation export as `application/xml` rather than the JSON body below. ::: #### Example request ```bash curl "$BASE_URL/content/learningPaths/:id" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | Unique identifier of the learning path to retrieve. | #### Example response ```json { "data": { "APIContentBody": { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "slug": "example-slug", "body": "string" } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Unique identifier of the learning path. | | `title` | `string` | Title of the learning path. | | `slug` | `string` | URL-friendly identifier for the learning path. | | `body` | `string` | Full HTML body content of the learning path. | --- ### Get Full Course Content Returns the full structure of a course as a flattened object with dot-path keys covering sections, lessons, and topics. ### GET /v2/fullContent/courses/:id #### Example request ```bash curl "$BASE_URL/fullContent/courses/:id" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | Course UUID. | #### Example response ```json { "id": "123e4567-e89b-12d3-a456-426614174000", "title": "Workplace Safety Fundamentals", "description": "An introduction to staying safe on the job.", "section.0.id": "1a2b3c4d-0000-0000-0000-000000000001", "section.0.title": "Getting Started", "section.0.lesson.0.id": "1a2b3c4d-0000-0000-0000-000000000002", "section.0.lesson.0.title": "Welcome", "section.0.lesson.0.topic.0.id": "1a2b3c4d-0000-0000-0000-000000000003", "section.0.lesson.0.topic.0.title": "Course Overview", "section.0.lesson.0.topic.0.body": "What you will learn in this course." } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Course ID. | | `title` | `string` | Course title. | | `description` | `string` | Course description (HTML). | | `section.N.id` | `uuid` | Section ID. | | `section.N.title` | `string` | Section title. | | `section.N.lesson.N.id` | `uuid` | Lesson ID. | | `section.N.lesson.N.title` | `string` | Lesson title. | | `section.N.lesson.N.topic.N.id` | `uuid` | Topic ID. | | `section.N.lesson.N.topic.N.title` | `string` | Topic title. | | `section.N.lesson.N.topic.N.body` | `string` | Topic body (HTML). | --- ### Get Full Learning Path Content Returns the full structure of a learning path, including its milestones and each milestone's associated courses. ### GET /v2/fullContent/learningPaths/:id #### Example request ```bash curl "$BASE_URL/fullContent/learningPaths/:id" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | Learning path UUID. | #### Example response ```json { "id": "1d9e8f70-4a3b-4c2d-8e1f-0a9b8c7d6e5f", "name": "Manager Fundamentals", "shortDescription": "A guided path covering the basics of people management.", "milestone.0.name": "Foundations", "milestone.0.milestoneCourses.0.id": "8f2a1c34-5b6d-4e7f-9a01-2b3c4d5e6f70", "milestone.0.milestoneCourses.0.title": "Onboarding Essentials", "milestone.0.milestoneCourses.0.description": "A short introduction to the platform for new hires." } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Learning path ID. | | `name` | `string` | Learning path name. | | `shortDescription` | `string` | Short description. | | `milestone.N.name` | `string` | Milestone name. | | `milestone.N.milestoneCourses.N.id` | `uuid` | Course ID. | | `milestone.N.milestoneCourses.N.title` | `string` | Course title. | | `milestone.N.milestoneCourses.N.description` | `string` | Course description. | --- ### Show Lesson Returns a single lesson by its unique identifier. ### GET /v2/lessons/:id #### Example request ```bash curl "$BASE_URL/lessons/:id" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The unique identifier of the lesson to retrieve. | #### Example response ```json { "data": { "APILessonById": { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "slug": "example-slug", "accessLevel": "studentsOnly", "sectionId": "00000000-0000-0000-0000-000000000000", "sectionTitle": "string", "topicCount": 0 } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Unique identifier of the lesson. | | `title` | `string` | Display title of the lesson. | | `slug` | `string` | URL-friendly identifier for the lesson. | | `accessLevel` | `AccessLevel` | Defines who can access the lesson (e.g., `studentsOnly`). | | `sectionId` | `uuid` | Unique identifier of the section that contains this lesson. | | `sectionTitle` | `string` | Display title of the parent section. | | `topicCount` | `integer` | Total number of topics within the lesson. | --- ### Show Section Returns the details of a single section by its ID. ### GET /v2/sections/:id #### Example request ```bash curl "$BASE_URL/sections/:id" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The unique identifier of the section to retrieve. | #### Example response ```json { "data": { "APISectionById": { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "slug": "example-slug", "status": "draft", "releaseAt": "2024-01-01T00:00:00.000Z", "parentSectionId": "00000000-0000-0000-0000-000000000000", "lessonCount": 0 } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Unique identifier of the section. | | `title` | `string` | Display title of the section. | | `slug` | `string` | URL-friendly identifier for the section. | | `status` | `Status` | Publication status of the section (e.g. `draft` or `published`). | | `releaseAt` | `ISO 8601 timestamp` | Date and time when the section becomes available to learners, in ISO 8601 format. | | `parentSectionId` | `uuid` | Unique identifier of the parent section, if this section is nested. | | `lessonCount` | `integer` | Total number of lessons contained in the section. | --- ### Section Lessons Returns a paginated list of lessons belonging to the specified section. ### GET /v2/sections/:sectionId/lessons #### Example request ```bash curl "$BASE_URL/sections/:sectionId/lessons" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `sectionId` | `uuid` | Yes | path | Unique identifier of the section whose lessons to list. | | `cursor` | `string` | No | query | Opaque pagination cursor returned by a previous response; use to fetch the next page of results. | | `perPage` | `integer` | No | query | The number of lessons to return per page. | #### Example response ```json { "data": { "APISectionLessons": { "pageInfo": { "total": 0, "cursor": "string", "hasMore": true }, "lessons": [ { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "slug": "example-slug", "accessLevel": "studentsOnly", "sectionTitle": "string", "topicCount": 0 } ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.total` | `integer` | The total number of lessons across all pages. | | `pageInfo.cursor` | `string` | Opaque cursor to pass in the next request to retrieve the following page. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `lessons` | `APILessonInfo[]` | The list of lessons belonging to the section. | | `lessons.id` | `uuid` | The unique identifier of the lesson. | | `lessons.title` | `string` | The display title of the lesson. | | `lessons.slug` | `string` | The URL-friendly identifier for the lesson. | | `lessons.accessLevel` | `AccessLevel` | The access restriction applied to the lesson, such as `studentsOnly`. | | `lessons.sectionTitle` | `string` | The display title of the section this lesson belongs to. | | `lessons.topicCount` | `integer` | The number of topics contained within the lesson. | --- ### Tags List tags ### GET /v2/tags Returns a paginated list of tags, optionally filtered by the provided criteria. #### Example request ```bash curl "$BASE_URL/tags" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `filters` | `TagsFiltersInput` | No | query | Filters to narrow the returned tags, such as by label or folder status. | #### Example response ```json { "data": { "Tags": { "pageInfo": { "perPage": 0, "currentPage": 0, "total": 0, "hasMore": true }, "tags": [ { "id": "00000000-0000-0000-0000-000000000000", "label": "string", "isFolder": true, "updatedAt": "2024-01-01T00:00:00.000Z", "description": "string" } ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `PageInfo` | Pagination metadata for the result set. | | `pageInfo.perPage` | `integer` | Number of tags returned per page. | | `pageInfo.currentPage` | `integer` | Index of the current page in the result set. | | `pageInfo.total` | `integer` | Total number of tags matching the request. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `tags` | `Tag[]` | Array of tag objects matching the request. | | `tags.id` | `uuid` | Unique identifier for the tag. | | `tags.label` | `string` | Display name of the tag. | | `tags.isFolder` | `boolean` | Indicates whether the tag acts as a folder grouping other tags. | | `tags.updatedAt` | `ISO 8601 timestamp` | Date and time the tag was last modified, in ISO 8601 format. | | `tags.description` | `string` | Optional text describing the purpose or contents of the tag. | --- ### Content List content ### GET /v2/tags/content Returns content items associated with the specified tags. #### Example request ```bash curl "$BASE_URL/tags/content?tagIds[]=00000000-0000-0000-0000-000000000000" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `tagIds[]` | `uuid[]` | Yes | query | One or more tag UUIDs whose associated content to retrieve (e.g. `?tagIds[]=&tagIds[]=`). | #### Example response ```json { "data": { "ContentByTagIds": [ { "id": "00000000-0000-0000-0000-000000000000", "kind": "courses" } ] } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `[].id` | `uuid` | Unique identifier of the content item. | | `[].kind` | `TagContentKind` | The content type, such as `courses`. | --- ### Course Topics Returns a paginated list of topics belonging to the specified course. ### GET /v2/topics/course/:courseId #### Example request ```bash curl "$BASE_URL/topics/course/:courseId" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `courseId` | `uuid` | Yes | path | Unique identifier of the course whose topics to list. | | `cursor` | `string` | No | query | Opaque pagination cursor returned by a previous response; use to fetch the next page of results. | | `perPage` | `integer` | No | query | Number of topics to return per page. | #### Example response ```json { "data": { "APICourseTopics": { "pageInfo": { "total": 0, "cursor": "string", "hasMore": true }, "topics": [ { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "type": "ad", "preTextBlock": "string", "postTextBlock": "string", "updatedAt": "2024-01-01T00:00:00.000Z" } ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.total` | `integer` | Total number of topics across all pages. | | `pageInfo.cursor` | `string` | Opaque cursor to pass in the next request to retrieve the following page. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results exist. | | `topics` | `Topic[]` | Array of topic objects belonging to the course. | | `topics.id` | `uuid` | Unique identifier for the topic. | | `topics.title` | `string` | Display title of the topic. | | `topics.type` | `TopicType` | Content type of the topic (e.g., `ad`, quiz, video). | | `topics.preTextBlock` | `string` | HTML or plain-text content displayed before the main topic content. | | `topics.postTextBlock` | `string` | HTML or plain-text content displayed after the main topic content. | | `topics.updatedAt` | `ISO 8601 timestamp` | Date and time the topic was last updated, in ISO 8601 format. | --- ### Topics Returns a single topic by its unique identifier. ### GET /v2/topics/:id #### Example request ```bash curl "$BASE_URL/topics/:id" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The unique identifier of the topic to retrieve. | #### Example response ```json { "data": { "APITopicById": { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "type": "ad", "preTextBlock": "string", "postTextBlock": "string", "updatedAt": "2024-01-01T00:00:00.000Z", "resultsMessage": "string", "passMessage": "string", "failMessage": "string", "navigationDisabled": true, "questionSkipEnabled": true } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Unique identifier of the topic. | | `title` | `string` | Display title of the topic. | | `type` | `TopicType` | The topic's content type (e.g., `ad`, `quiz`, `video`). | | `preTextBlock` | `string` | HTML or plain-text content displayed to the learner before the topic begins. | | `postTextBlock` | `string` | HTML or plain-text content displayed to the learner after the topic ends. | | `updatedAt` | `ISO 8601 timestamp` | Date and time the topic was last modified, in ISO 8601 format. | | `resultsMessage` | `string` | Message shown to the learner on the results screen after completing the topic. | | `passMessage` | `string` | Message shown to the learner when they pass the topic. | | `failMessage` | `string` | Message shown to the learner when they fail the topic. | | `navigationDisabled` | `boolean` | When `true`, learners cannot skip ahead or navigate freely within the topic. | | `questionSkipEnabled` | `boolean` | When `true`, learners may skip individual questions within the topic. | --- ### Lesson Topics Returns a paginated list of topics belonging to the specified lesson. ### GET /v2/topics/lesson/:lessonId #### Example request ```bash curl "$BASE_URL/topics/lesson/:lessonId" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `lessonId` | `uuid` | Yes | path | Unique identifier of the lesson whose topics to list. | | `cursor` | `string` | No | query | Opaque pagination cursor returned by a previous response; use to fetch the next page of results. | | `perPage` | `integer` | No | query | Number of topics to return per page. | #### Example response ```json { "data": { "APILessonTopics": { "pageInfo": { "total": 0, "cursor": "string", "hasMore": true }, "topics": [ { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "type": "ad", "preTextBlock": "string", "postTextBlock": "string", "updatedAt": "2024-01-01T00:00:00.000Z" } ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.total` | `integer` | Total number of topics across all pages. | | `pageInfo.cursor` | `string` | Opaque cursor to pass in the next request to retrieve the following page. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `topics` | `Topic[]` | Array of topic objects belonging to the lesson. | | `topics.id` | `uuid` | Unique identifier for the topic. | | `topics.title` | `string` | Display title of the topic. | | `topics.type` | `TopicType` | Content type of the topic (e.g., `ad`, quiz, video). | | `topics.preTextBlock` | `string` | HTML or plain-text content displayed before the topic's main content. | | `topics.postTextBlock` | `string` | HTML or plain-text content displayed after the topic's main content. | | `topics.updatedAt` | `ISO 8601 timestamp` | Date and time the topic was last updated, in ISO 8601 format. | --- ### Bulk Update Prices Updates prices in bulk for courses and learning paths. ### POST /v2/content/bulkUpdatePrices #### Example request ```bash curl -X POST "$BASE_URL/content/bulkUpdatePrices" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "courses": [], "learningPaths": [] }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `courses` | `BulkUpdatePricesContentInput[]` | Yes | body | Array of course price inputs to update. | | `learningPaths` | `BulkUpdatePricesContentInput[]` | Yes | body | Array of learning path price inputs to update. | #### Example response ```json { "data": { "APIBulkUpdatePrices": { "updatedRecordCount": 0, "errors": [ { "id": "00000000-0000-0000-0000-000000000000", "identifier": "string", "error": "string", "record": "string" } ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `updatedRecordCount` | `integer` | Total number of price records successfully updated. | | `errors` | `BulkAssignmentUserError[]` | List of errors encountered during the bulk update operation. | | `errors.id` | `uuid` | Unique identifier of the record that failed to update. | | `errors.identifier` | `string` | Human-readable identifier (such as a slug or external ID) of the failed record. | | `errors.error` | `string` | Description of the error that prevented the price update. | | `errors.record` | `string` | Name or reference of the content record that failed to update. | --- ### Bulk Create Certificate Templates Creates multiple certificate templates in a single request, returning counts of created and updated records along with any errors or warnings. ### POST /v2/content/certificate-templates/bulk #### Example request ```bash curl -X POST "$BASE_URL/content/certificate-templates/bulk" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "templates": [] }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `templates` | `CertificateTemplateInput[]` | Yes | body | Array of certificate template objects to create or update in bulk. | #### Example response ```json { "data": { "APIBulkCreateCertificateTemplates": { "createdCount": 0, "updatedCount": 0, "createdIds": [ "00000000-0000-0000-0000-000000000000" ], "errors": [ { "resourceId": "00000000-0000-0000-0000-000000000000", "title": "string", "error": "string", "record": "string" } ], "warnings": [ "string" ], "backgroundJob": { "id": "00000000-0000-0000-0000-000000000000", "type": "string", "description": "string", "status": "complete" } } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `createdCount` | `integer` | Number of certificate templates successfully created by this request. | | `updatedCount` | `integer` | Number of certificate templates successfully updated by this request. | | `createdIds` | `uuid[]` | UUIDs of the newly created certificate templates. | | `errors` | `BulkCertificateTemplateError[]` | List of errors encountered for individual templates during the bulk operation. | | `errors.resourceId` | `uuid` | UUID of the template that caused the error. | | `errors.title` | `string` | Human-readable title identifying the template that caused the error. | | `errors.error` | `string` | Description of the error that occurred for this template. | | `errors.record` | `string` | Raw record data associated with the failed template. | | `warnings` | `string[]` | Non-fatal warning messages generated during the bulk operation. | | `backgroundJob` | `BackgroundJob` | Background job spawned to process the bulk operation, if applicable. | | `backgroundJob.id` | `uuid` | Unique identifier of the background job. | | `backgroundJob.type` | `string` | Classification of the background job. | | `backgroundJob.description` | `string` | Human-readable description of what the background job is doing. | | `backgroundJob.status` | `BackgroundJobStatus` | Current status of the background job (e.g., `complete`). | --- ### Bulk Update Certificate Templates Bulk-updates one or more certificate templates in a single request. ### POST /v2/content/certificate-templates/bulk/update #### Example request ```bash curl -X POST "$BASE_URL/content/certificate-templates/bulk/update" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "templates": [] }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `templates` | `UpdateCertificateTemplateInput[]` | Yes | body | Array of certificate template objects to update, each containing the template ID and fields to modify. | #### Example response ```json { "data": { "APIBulkUpdateCertificateTemplates": { "createdCount": 0, "updatedCount": 0, "errors": [ { "resourceId": "00000000-0000-0000-0000-000000000000", "title": "string", "error": "string", "record": "string" } ], "warnings": [ "string" ], "backgroundJob": { "id": "00000000-0000-0000-0000-000000000000", "type": "string", "description": "string", "status": "complete" } } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `createdCount` | `integer` | Number of certificate templates created during the bulk operation. | | `updatedCount` | `integer` | Number of certificate templates successfully updated during the bulk operation. | | `errors` | `BulkCertificateTemplateError[]` | List of errors encountered for individual templates during the bulk operation. | | `errors.resourceId` | `uuid` | UUID of the certificate template that caused the error. | | `errors.title` | `string` | Human-readable title identifying the failed certificate template. | | `errors.error` | `string` | Description of the error that occurred for this template. | | `errors.record` | `string` | Serialized representation of the record that failed to process. | | `warnings` | `string[]` | Non-fatal warning messages generated during the bulk operation. | | `backgroundJob` | `BackgroundJob` | Background job created to process the bulk update asynchronously, if applicable. | | `backgroundJob.id` | `uuid` | Unique identifier of the background job. | | `backgroundJob.type` | `string` | Classification of the background job task. | | `backgroundJob.description` | `string` | Human-readable description of what the background job is processing. | | `backgroundJob.status` | `BackgroundJobStatus` | Current status of the background job (e.g., `complete`). | --- ### Bulk Create Completion Criteria Creates or updates completion criteria for multiple courses in a single request. ### POST /v2/content/completion-criteria/bulk #### Example request ```bash curl -X POST "$BASE_URL/content/completion-criteria/bulk" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "criteria": [] }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `criteria` | `CourseCompletionCriteriaInput[]` | Yes | body | Array of completion criteria objects to create or update, each associated with a specific course. | #### Example response ```json { "data": { "APIBulkCreateCompletionCriteria": { "createdCount": 0, "updatedCount": 0, "errors": [ { "courseId": "00000000-0000-0000-0000-000000000000", "type": "coursePercentViewed", "error": "string", "record": "string" } ], "warnings": [ "string" ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `createdCount` | `integer` | Number of completion criteria records successfully created. | | `updatedCount` | `integer` | Number of completion criteria records successfully updated. | | `errors` | `BulkCompletionCriteriaError[]` | Array of errors encountered during processing, one entry per failed criteria record. | | `errors.courseId` | `uuid` | ID of the course associated with the failed criteria record. | | `errors.type` | `CourseCompletionCriteriaType` | Completion criteria type that failed, such as `coursePercentViewed`. | | `errors.error` | `string` | Human-readable message describing why the criteria record failed. | | `errors.record` | `string` | String representation of the criteria record that caused the error. | | `warnings` | `string[]` | Non-fatal warnings generated during processing that did not prevent record creation or update. | --- ### Bulk Update Completion Criteria Bulk-creates or updates completion criteria for one or more courses in a single request. ### POST /v2/content/completion-criteria/bulk/update #### Example request ```bash curl -X POST "$BASE_URL/content/completion-criteria/bulk/update" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "criteria": [] }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `criteria` | `UpdateCourseCompletionCriteria[]` | Yes | body | Array of completion criteria objects to create or update, each scoped to a specific course. | #### Example response ```json { "data": { "APIBulkUpdateCompletionCriteria": { "createdCount": 0, "updatedCount": 0, "errors": [ { "courseId": "00000000-0000-0000-0000-000000000000", "type": "coursePercentViewed", "error": "string", "record": "string" } ], "warnings": [ "string" ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `createdCount` | `integer` | Number of completion criteria records created during this operation. | | `updatedCount` | `integer` | Number of completion criteria records updated during this operation. | | `errors` | `BulkCompletionCriteriaError[]` | Array of errors encountered while processing individual criteria records. | | `errors.courseId` | `uuid` | ID of the course associated with the failed criteria record. | | `errors.type` | `CourseCompletionCriteriaType` | Completion criteria type that failed to be created or updated (e.g., `coursePercentViewed`). | | `errors.error` | `string` | Human-readable message describing why the criteria record failed. | | `errors.record` | `string` | Serialized representation of the criteria record that caused the error. | | `warnings` | `string[]` | Non-fatal warnings generated during the bulk update, such as skipped or deprecated fields. | --- ### Create Courses Creates one or more courses (up to 100 courses per request, with a maximum of 25 children per course). Children include sections, lessons, and topics. The endpoint automatically creates the full course hierarchy including the course group, course (session), sections, lessons, and topics based on the `kind` specified. #### Authentication & Permissions Authenticate with `Authorization: Bearer APIKEY`. This endpoint requires the `courses.new` permission. This route is **not** rate limited. #### Course Attributes | Property | Type | Required? | Description | | ------------------------- | ------- | --------- | -------------------------------------------------------------------------------------------- | | `title` | string | yes | Title of the course | | `kind` | string | yes | Course type: `courseGroup`, `microCourse`, `article`, `video`, `shareableContentObject` (5 kinds). `xApiObject` is **not** a valid course kind — it is only a topic type | | `sku` | string | no | Unique SKU identifier | | `isbn` | string | no | ISBN number | | `discussionsEnabled` | boolean | no | Enable course discussions | | `availableToPublic` | boolean | no | Make course available without login | | `enrollmentLimit` | integer | no | Maximum number of enrollments (enables seat limits) | | `enrollmentStartDate` | date | no | When enrollment opens (ISO 8601) | | `enrollmentEndDate` | date | no | When enrollment closes (ISO 8601) | | `courseStartDate` | date | no | When the course starts (ISO 8601) | | `courseEndDate` | date | no | When the course ends (ISO 8601) | | `gracePeriodEndDate` | date | no | Grace period end date (ISO 8601) | | `sections` | array | no | Array of section objects (for `courseGroup` kind) | | `topics` | array | no | Array of topic objects (for `microCourse` kind) | | `articleVariant` | object | no | Article content (for `article` kind) | | `width` | integer | no | Display width for SCORM/xAPI (pixels) | | `height` | integer | no | Display height for SCORM/xAPI (pixels) | | `embeddedEnabled` | boolean | no | Enable embedded display for SCORM/xAPI | | `resetSessionAfterComplete` | boolean | no | Reset SCORM session after completion | | `scormUrl` | URL | no | URL to SCORM/xAPI package (for standalone SCORM courses) | | `videoUrl` | URL | no | External video URL for `video` kind — downloaded and uploaded to Wistia by a background job. Set at the course (top) level, not in `topics` | | `videoAsset` | ID | no | Existing Wistia media ID or Synthesia UUID for `video` kind. Mutually exclusive with `videoUrl`. Course-level | | `assetType` | string | no | Video provider for `video` kind: `wistia` (default) or `synthesia`. Required for Synthesia. Course-level | | `preAsset` | ID | no | Wistia media ID for pre-roll video (`video` kind). Course-level | | `postAsset` | ID | no | Wistia media ID for post-roll video (`video` kind). Course-level | | `posterImageAsset` | URL | no | Poster image URL shown before the video plays (`video` kind). Course-level | --- **Catalog & Session Attributes** (live in production): **CourseGroup-Level (Catalog) Attributes:** | Property | Type | Description | | ----------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------- | | `description` | string | Course description for catalog and detail pages (max 5,000 characters) | | `tagIds` | array | Array of tag UUIDs for categorization (max 10). Tags must already exist on the company. Use `GET /tags` to list available IDs | | `customFields` | JSON | Custom field values as `{"slug": "value" or ["v1", "v2"]}`. Replaces the entire object on update | | `asset` | URL | Catalog thumbnail image URL (recommended: 800x385px, PNG/JPEG/GIF). Downloaded and uploaded to S3 | | `assetAltText` | string | Alt text for catalog image. Defaults to empty string when `asset` is provided | | `detailAsset` | URL | Detail page image URL (recommended: 800x450px, PNG/JPEG/GIF). Downloaded and uploaded to S3 | | `detailAssetAltText` | string | Alt text for detail image. Defaults to empty string when `detailAsset` is provided | | `ribbon` | string | Ribbon badge slug (e.g., `new`, `popular`). Must exist in the company's ribbon configuration | | `metaTitle` | string | SEO meta title (max 200 characters, HTML stripped) | | `metaDescription` | string | SEO meta description (max 500 characters, HTML stripped) | | `contentType` | string | Content type label (e.g., "Training Module"). Auto-derived from `kind` if omitted; must exist in the company's configuration | | `relatedCourseGroupIds` | array | Related catalog course group UUIDs. Must exist on the company | **Course-Level (Session) Attributes:** | Property | Type | Description | | ----------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------- | | `priceInCents` | integer | Price in cents (e.g., `9900` = $99.00). Max: 99,999,999. Cannot be negative | | `freeWithRegistration` | boolean | Free access for all registered users (default: `false`) | | `purchasable` | boolean | Enable purchase functionality (default: `false`) | | `status` | string | `draft` or `published`; omit to default to `draft`. With `futurePublishDate`, status becomes `pending` for scheduled release | | `futurePublishDate` | date | Auto-publish the session to the catalog at this date. Implies `pending` unless `status` is `published` | | `publishDate` | date | Course group catalog date used for search/display ordering | | `sessionCustomFields` | JSON | Session-level custom field values. Replaces the entire object on update | | `forceLinearProgress` | boolean | Force learners to complete pages in order (default: `false`) | | `showProgress` | boolean | Show progress indicator to learners (default: `true`) | | `prerequisiteCourseIds` | array | Prerequisite course UUIDs. Must exist on the company | | `prerequisiteLearningPathIds` | array | Prerequisite learning path UUIDs. Must exist on the company | #### Section Object | Property | Type | Required? | Description | | ------------- | ------ | --------- | ------------------------ | | `title` | string | yes | Section title | | `releaseDate` | date | no | When section is released | | `lessons` | array | yes | Array of lesson objects | #### Lesson Object | Property | Type | Required? | Description | | ---------- | ------ | --------- | ----------------------------------------------------- | | `title` | string | yes | Lesson title | | `openType` | string | yes | Access level: `studentsOnly`, `open`, `emailCaptureOpen` | | `topics` | array | yes | Array of topic objects | #### Topic Object | Property | Type | Required? | Description | | ------------------------- | ------- | --------- | ------------------------------------------------------------------- | | `title` | string | yes | Topic title | | `type` | string | yes | Topic type (see Supported Topic Types below) | | `body` | string | no | HTML content (for text/article topics) | | `languages` | array | no | Multi-language content variants | | `preTextBlock` | string | no | Text displayed before topic content | | `postTextBlock` | string | no | Text displayed after topic content | | `width` | integer | no | Display width (pixels) | | `height` | integer | no | Display height (pixels) | | `embeddedEnabled` | boolean | no | Enable embedded display | | `fullscreenEmbed` | boolean | no | Enable fullscreen embed | | `preventProgression` | boolean | no | Prevent learner from advancing until complete | | `resetSessionAfterComplete` | boolean | no | Reset session after completion (SCORM) | | `caption` | string | no | Caption text | | `searchDisabled` | boolean | no | Exclude from search | | `printDisabled` | boolean | no | Disable printing | | `fileDownloadDisabled` | boolean | no | Disable file download | | `scormUrl` | URL | no | URL to SCORM/xAPI package (for `shareableContentObject`/`xApiObject`) | | `scoTitle` | string | no | SCORM title | | `objectType` | string | no | SCORM standard type | | `courseCompletionCriteria`| object | no | Completion criteria for the topic | | `pdfUrl` | URL | no | URL to PDF file (for `pdfViewer` type) | | `videoUrl` | URL | no | External URL to a video file (for `video` type) — uploaded to Wistia by a background job | | `videoAsset` | ID | no | Existing Wistia media ID or Synthesia UUID (for `video` type). Mutually exclusive with `videoUrl` | | `assetType` | string | no | Video provider: `wistia` (default) or `synthesia`. Required when `videoAsset` is a Synthesia ID | | `preAsset` | ID | no | Wistia media ID for pre-roll video | | `postAsset` | ID | no | Wistia media ID for post-roll video | | `posterImageAsset` | URL | no | Poster image URL displayed before the video plays | | `audioUrl` | URL | no | External URL to an audio file (for `audio` type) — downloaded and stored by a background job. Supported formats: MP3, WAV, OGG (max 200 MB) | #### Supported Topic Types **Fully Supported** (content can be created via API): | Type | Description | | ----------------------- | ---------------------------------------------------------------------------- | | `text` | Rich text content (use `body` field) | | `article` | Article page (use `body` or `languages` field) | | `video` | Video page (use `videoUrl` to upload, or `videoAsset` to reference existing) | | `audio` | Audio page (use `audioUrl` field) | | `shareableContentObject`| SCORM content (use `scormUrl` field) | | `xApiObject` | xAPI content (use `scormUrl` field) | | `pdfViewer` | PDF document (use `pdfUrl` field) | | `embed` | Embedded content (use `body` with iframe) | | `discussionBoard` | Discussion forum | **Shell Only** (creates placeholder, content must be added via UI): | Type | Description | | ----------------------- | ------------------------------------------------ | | `quiz` | Quiz - questions must be added via UI | | `test` | Assessment - questions must be added via UI | | `survey` | Survey - questions must be added via UI | | `assignment` | Assignment - configuration required via UI | | `image` | Image gallery - configuration via UI | | `slideshow` | Slideshow - configuration via UI | | `presentation` | Presentation - configuration via UI | | `meetingInfo` | Meeting information - linked to calendar event | ```endpoint POST $BASE_URL/content/course/create ``` #### Example request - Standard Course ```curl curl -X "POST" "$BASE_URL/content/course/create" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "courseAttributes": [ { "title": "Introduction to Marketing", "kind": "courseGroup", "sku": "MKT-101", "discussionsEnabled": true, "sections": [ { "title": "Getting Started", "lessons": [ { "title": "Welcome", "openType": "studentsOnly", "topics": [ { "title": "Course Overview", "type": "text", "body": "Welcome to the course!" }, { "title": "Embedded Media", "type": "embed", "body": "" } ] } ] }, { "title": "Core Concepts", "lessons": [ { "title": "Marketing Fundamentals", "openType": "studentsOnly", "topics": [ { "title": "The 4 Ps of Marketing", "type": "article", "body": "Product, Price, Place, Promotion..." }, { "title": "Knowledge Check", "type": "quiz" } ] } ] } ] } ] }' ``` #### Example response - Standard Course The response is always an object containing the created course and course group IDs: ```json { "courseIds": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"], "courseGroupIds": ["b2c3d4e5-f6a7-8901-bcde-f23456789012"] } ``` #### Example request - Micro Course ```curl curl -X "POST" "$BASE_URL/content/course/create" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "courseAttributes": [ { "title": "Quick Tips: Email Etiquette", "kind": "microCourse", "topics": [ { "title": "Subject Lines", "type": "text", "body": "Keep subject lines clear and concise." }, { "title": "Professional Tone", "type": "text", "body": "Maintain a professional tone in all communications." }, { "title": "Quiz", "type": "quiz" } ] } ] }' ``` #### Example request - Standalone SCORM Course ```curl curl -X "POST" "$BASE_URL/content/course/create" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "courseAttributes": [ { "title": "Compliance Training Module", "kind": "shareableContentObject", "sku": "COMP-2024", "width": 1024, "height": 768, "embeddedEnabled": true, "scormUrl": "https://example.com/scorm-packages/compliance-2024.zip" } ] }' ``` #### Example response - SCORM Course with Background Job When uploading SCORM content, the response includes background job information: ```json { "courseIds": ["b2c3d4e5-f6a7-8901-bcde-f23456789012"], "courseGroupIds": ["c3d4e5f6-a7b8-9012-cdef-345678901234"], "backgroundJob": { "id": "job-uuid-here", "type": "contentBulkUpload", "status": "queued", "contentUploads": [ { "topicId": "topic-uuid-here", "courseId": "b2c3d4e5-f6a7-8901-bcde-f23456789012", "contentType": "scorm" } ] } } ``` #### Example request - Article ```curl curl -X "POST" "$BASE_URL/content/course/create" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "courseAttributes": [ { "title": "Best Practices for Remote Work", "kind": "article", "articleVariant": { "body": "Working from HomeHere are the best practices..." } } ] }' ``` #### Example request - Standalone Video Course (upload from URL) For `kind: "video"`, the video fields (`videoUrl`, `videoAsset`, `assetType`, `preAsset`, `postAsset`, `posterImageAsset`) are set at the course (top) level — not inside a `topics` array. `videoUrl` and `videoAsset` are mutually exclusive, and `assetType` cannot be combined with `videoUrl`; violations return a 400 error. The platform downloads the file at `videoUrl` and uploads it to Wistia in a background job. Poll `/v2/jobs/{id}` to track the upload. ```curl curl -X "POST" "$BASE_URL/content/course/create" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "courseAttributes": [ { "title": "Welcome Video", "kind": "video", "sku": "VID-001", "videoUrl": "https://example.com/videos/welcome.mp4", "posterImageAsset": "https://example.com/images/welcome-poster.jpg" } ] }' ``` #### Example response - Video Course with Background Job ```json { "courseIds": ["c3d4e5f6-a7b8-9012-cdef-345678901234"], "courseGroupIds": ["d4e5f6a7-b8c9-0123-defa-456789012345"], "backgroundJob": { "id": "job-uuid-here", "type": "contentBulkUpload", "status": "queued", "contentUploads": [ { "topicId": "topic-uuid-here", "courseId": "c3d4e5f6-a7b8-9012-cdef-345678901234", "contentType": "video" } ] } } ``` #### Example request - Standalone Video Course (existing Wistia media) To reuse a video already uploaded to Wistia, pass `videoAsset` (the Wistia media ID) instead of `videoUrl`. No background job is queued. ```curl curl -X "POST" "$BASE_URL/content/course/create" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "courseAttributes": [ { "title": "Product Demo", "kind": "video", "videoAsset": "abc123wistia", "preAsset": "intro456wistia", "postAsset": "outro789wistia" } ] }' ``` #### Example request - Audio Topic (upload from URL) An `audio` topic attaches an audio file supplied as an external URL via `audioUrl`. The topic's `type` must be `audio`; using `audioUrl` with any other type returns a `400` error. Unlike video (which uploads to Wistia), the platform downloads the file at `audioUrl` and stores it in a background job. Supported formats are MP3, WAV, and OGG (max 200 MB); the URL must be HTTP/HTTPS. Poll `/v2/jobs/{id}` to track the upload. Audio topics live inside the `sections` → `lessons` → `topics` hierarchy (or the top-level `topics` array of a `microCourse`), the same as other topic types. ```curl curl -X "POST" "$BASE_URL/content/course/create" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "courseAttributes": [ { "title": "Language Lessons", "kind": "courseGroup", "sections": [ { "title": "Pronunciation", "lessons": [ { "title": "Vowel Sounds", "openType": "studentsOnly", "topics": [ { "title": "Listen: Vowel Sounds", "type": "audio", "audioUrl": "https://example.com/audio/vowel-sounds.mp3" } ] } ] } ] } ] }' ``` #### Example response - Audio Topic with Background Job ```json { "courseIds": ["e5f6a7b8-c9d0-1234-efab-567890123456"], "courseGroupIds": ["f6a7b8c9-d0e1-2345-fabc-678901234567"], "backgroundJob": { "id": "job-uuid-here", "type": "contentBulkUpload", "status": "queued", "contentUploads": [ { "topicId": "topic-uuid-here", "courseId": "e5f6a7b8-c9d0-1234-efab-567890123456", "contentType": "audio" } ] } } ``` #### Errors Validation failures return HTTP `400`, but the response **body shape depends on which layer rejected the request**. Handle both, and do not assume the body parses as JSON. **Plain text.** The controller's own pre-GraphQL checks return a bare string as the whole body — calling `JSON.parse` on one throws: | Trigger | Response body (plain text) | | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | Empty `courseAttributes` array, or no `courseAttributes` key | `No items provided` | | More than 100 courses in one request | `Cannot create more than 100 courses at a time` | | More than 25 children on a course | `Course at index 0 exceeds maximum of 25 children (sections + lessons + topics). Found 26 children.` | | `audioUrl` on a topic whose `type` is not `audio` | `Cannot use 'audioUrl' with topic type 'text' for topic at course[0].sections[0].lessons[0].topics[0]. This field is only valid for topics with type 'audio'.` | **JSON with an `errors` array of strings.** Every other failure — the pre-GraphQL `status` enum check and everything surfaced by the GraphQL layer — returns this shape: ```json { "errors": [ "Invalid course kinds: [object Object]. Supported kinds are: article, video, courseGroup, microCourse, shareableContentObject" ] } ``` `[object Object]` is not a placeholder for this page. The message interpolates the rejected course **objects** rather than their `kind` values, so that is the literal text a caller receives (one `[object Object]`, comma-separated, per rejected course). **Match on the `Invalid course kinds:` prefix** — never try to parse the offending kind back out of the message. An invalid `status` value is reported as `Invalid status 'zzz'. Valid values are: draft, authoring, published, loginRestriction, archived, pending, deleted`, and when the request carries more than one course that message is prefixed with `Course at index N:`. --- --- ### Bulk Import Course Group In Person Event Bulk-imports in-person events for course groups from a pre-uploaded asset file. Submit the asset path to validate and process the import, optionally as a dry run. ### POST /v2/content/courseGroup/inPersonEvent/bulk #### Example request ```bash curl -X POST "$BASE_URL/content/courseGroup/inPersonEvent/bulk" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "bulkImportAssetPath": "string" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `bulkImportAssetPath` | `string` | Yes | body | The file path of the pre-uploaded asset containing the in-person event records to import. | #### Example response ```json { "data": { "APIBulkCourseGroupInPersonEventImport": { "dryRun": false, "backgroundJob": { "id": "00000000-0000-0000-0000-000000000000", "description": "string", "errorMessage": "string", "status": "complete" } } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `dryRun` | `boolean` | Whether the import ran as a dry run. This endpoint always processes the import for real, so this is always `false`. | | `backgroundJob` | `BackgroundJob` | The background job created to process the bulk import. | | `backgroundJob.id` | `uuid` | Unique identifier of the background job. | | `backgroundJob.description` | `string` | Human-readable description of the background job. | | `backgroundJob.errorMessage` | `string` | Error message returned if the background job failed. | | `backgroundJob.status` | `BackgroundJobStatus` | Current status of the background job (e.g., `complete`). | --- ### Bulk Import Course Group Webinar Bulk imports course group webinar content from a specified asset path. Submit a dry run to validate rows before committing, or run a full import to create records in bulk. ### POST /v2/content/courseGroup/webinar/bulk #### Example request ```bash curl -X POST "$BASE_URL/content/courseGroup/webinar/bulk" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "bulkImportAssetPath": "string" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `bulkImportAssetPath` | `string` | Yes | body | The storage path to the asset file containing the bulk webinar import data. | #### Example response ```json { "data": { "APIBulkCourseGroupWebinarImport": { "dryRun": false, "backgroundJob": { "id": "00000000-0000-0000-0000-000000000000", "description": "string", "errorMessage": "string", "status": "complete" } } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `dryRun` | `boolean` | Whether the import ran as a dry run. This endpoint always processes the import for real, so this is always `false`. | | `backgroundJob` | `BackgroundJob` | The background job created to process the bulk import. | | `backgroundJob.id` | `uuid` | Unique identifier of the background job. | | `backgroundJob.description` | `string` | Human-readable description of what the background job performs. | | `backgroundJob.errorMessage` | `string` | Error message populated if the background job fails. | | `backgroundJob.status` | `BackgroundJobStatus` | Current status of the background job (e.g., `complete`). | --- ### Bulk Import In Person Event Bulk-imports in-person event sessions from an asset file. Submit the path to a pre-uploaded asset, and the API validates each row, returning valid and invalid entries along with a background job that tracks processing. ### POST /v2/content/session/inPersonEvent/bulk #### Example request ```bash curl -X POST "$BASE_URL/content/session/inPersonEvent/bulk" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "bulkImportAssetPath": "string" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `bulkImportAssetPath` | `string` | Yes | body | The file path of the pre-uploaded asset containing the in-person event rows to import. | #### Example response ```json { "data": { "APIBulkInPersonEventImport": { "dryRun": false, "backgroundJob": { "id": "00000000-0000-0000-0000-000000000000", "description": "string", "errorMessage": "string", "status": "complete" } } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `dryRun` | `boolean` | Whether the import ran as a dry run. This endpoint always processes the import for real, so this is always `false`. | | `backgroundJob` | `BackgroundJob` | The background job created to process the import asynchronously. | | `backgroundJob.id` | `uuid` | Unique identifier for the background job. | | `backgroundJob.description` | `string` | Human-readable description of the background job's purpose. | | `backgroundJob.errorMessage` | `string` | Error message populated if the background job fails during processing. | | `backgroundJob.status` | `BackgroundJobStatus` | Current execution status of the background job (e.g., `complete`). | --- ### Bulk Import Webinar Bulk-imports webinar sessions from an asset file. Submit the path to a structured import asset, and the API validates each row, returning valid and invalid entries along with a background job that tracks the import's progress. ### POST /v2/content/session/webinar/bulk #### Example request ```bash curl -X POST "$BASE_URL/content/session/webinar/bulk" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "bulkImportAssetPath": "string" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `bulkImportAssetPath` | `string` | Yes | body | The storage path to the asset file containing the webinar session rows to import. | #### Example response ```json { "data": { "APIBulkWebinarImport": { "dryRun": false, "backgroundJob": { "id": "00000000-0000-0000-0000-000000000000", "description": "string", "errorMessage": "string", "status": "complete" } } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `dryRun` | `boolean` | Whether the import ran as a dry run. This endpoint always processes the import for real, so this is always `false`. | | `backgroundJob` | `BackgroundJob` | The background job created to process the import asynchronously. | | `backgroundJob.id` | `uuid` | Unique identifier of the background job. | | `backgroundJob.description` | `string` | Human-readable description of the background job. | | `backgroundJob.errorMessage` | `string` | Error message populated if the background job fails. | | `backgroundJob.status` | `BackgroundJobStatus` | Current status of the background job (e.g., `complete`). | --- ### Update Courses Updates existing course content. Supports partial updates - only provided fields are updated. Can also create new sections, lessons, topics, or course groups by omitting the `id` field. The update endpoint accepts up to 100 total items across all entity types combined. #### Authentication & Permissions Authenticate with `Authorization: Bearer APIKEY`. This endpoint requires the `courses.edit` permission. This route is **not** rate limited. #### Update Course Attributes | Property | Type | Required? | Description | | -------------------------- | ----- | --------- | ------------------------------------------ | | `sections` | array | no | Array of section updates | | `courseGroups` | array | no | Array of course group updates | | `courses` | array | no | Array of course updates | | `lessons` | array | no | Array of lesson updates | | `topics` | array | no | Array of topic updates | #### Update Section Object | Property | Type | Required? | Description | | -------------- | ------- | ------------- | ------------------------------------------------- | | `id` | UUID | no | Section ID (omit to create new section) | | `courseId` | UUID | if creating | Required when creating new section | | `title` | string | no | Section title | | `releaseDate` | date | no | When section is released | | `displayOrder` | integer | no | Display order in course | #### Update Course Group Object | Property | Type | Required? | Description | | ----------------------- | ------ | ----------- | ------------------------------------------------- | | `id` | UUID | no | Course group ID (omit to create new) | | `title` | string | no | Course group title | | `slug` | string | no | URL slug | | `description` | string | no | Course description (max 5,000 characters). Pass `null` to clear; omit to leave unchanged | | `tagIds` | array | no | Tag UUIDs (max 10). Replaces the entire array; pass `null` or `[]` to clear | | `customFields` | JSON | no | Custom field values. Replaces the entire object; pass `null` or `{}` to clear | | `asset` | URL | no | Catalog image URL. Pass `null` to clear | | `assetAltText` | string | no | Catalog image alt text | | `detailAsset` | URL | no | Detail page image URL. Pass `null` to clear | | `detailAssetAltText` | string | no | Detail image alt text | | `ribbon` | string | no | Ribbon badge slug. Pass `null` to clear | | `metaTitle` | string | no | SEO meta title (max 200 characters) | | `metaDescription` | string | no | SEO meta description (max 500 characters) | | `archived` | boolean| no | Archive status (update only — cannot be set on create) | | `relatedCourseGroupIds` | array | no | Related course group UUIDs. Replaces the entire array | #### Update Course Object | Property | Type | Required? | Description | | ----------------------------- | ------- | ----------- | ------------------------------------------------- | | `id` | UUID | no | Course ID (omit to create new course) | | `courseGroupId` | UUID | if creating | Required when creating new course | | `title` | string | no | Course title | | `kind` | string | no | Course kind | | `priceInCents` | integer | no | Price in cents (max 99,999,999) | | `freeWithRegistration` | boolean | no | Free for registered users | | `purchasable` | boolean | no | Enable purchasing | | `status` | string | no | Course status: `draft`, `published`, `archived` | | `customFields` | JSON | no | Session custom fields. Replaces the entire object | | `forceLinearProgress` | boolean | no | Require sequential page completion | | `showProgress` | boolean | no | Show progress indicator | | `prerequisiteCourseIds` | array | no | Prerequisite course UUIDs | | `prerequisiteLearningPathIds` | array | no | Prerequisite learning path UUIDs | #### Update Lesson Object | Property | Type | Required? | Description | | ---------- | ------ | ----------- | ------------------------------------------------- | | `id` | UUID | no | Lesson ID (omit to create new lesson) | | `sectionId`| UUID | if creating | Required when creating new lesson | | `title` | string | no | Lesson title | | `openType` | string | no | Access level: `studentsOnly`, `open`, `emailCaptureOpen` | #### Update Topic Object | Property | Type | Required? | Description | | --------------------------- | ------- | ----------- | ------------------------------------------------------------------- | | `id` | UUID | yes/no | Topic ID (required for update, omit for create) | | `lessonId` | UUID | if creating | Required when creating new topic | | `title` | string | no | Topic title | | `type` | string | if creating | Topic type (required when creating) | | `body` | string | no | HTML content | | `languages` | array | no | Multi-language variants | | `preTextBlock` | string | no | Pre-content text | | `postTextBlock` | string | no | Post-content text | | `width` | integer | no | Display width (pixels) | | `height` | integer | no | Display height (pixels) | | `embeddedEnabled` | boolean | no | Enable embedded display | | `fullscreenEmbed` | boolean | no | Enable fullscreen embed | | `preventProgression` | boolean | no | Prevent progression until complete | | `resetSessionAfterComplete` | boolean | no | Reset session after completion | | `caption` | string | no | Caption text | | `searchDisabled` | boolean | no | Exclude from search | | `printDisabled` | boolean | no | Disable printing | | `fileDownloadDisabled` | boolean | no | Disable file download | | `scormUrl` | URL | no | URL to SCORM/xAPI package | | `scoTitle` | string | no | SCORM title | | `objectType` | string | no | SCORM standard type | | `restartProgress` | boolean | no | Restart user progress when replacing SCORM | | `pdfUrl` | URL | no | URL to PDF file | | `videoUrl` | URL | no | External URL to a video file (for `video` type) — uploaded to Wistia by a background job | | `videoAsset` | ID | no | Existing Wistia media ID or Synthesia UUID. Mutually exclusive with `videoUrl` | | `assetType` | string | no | Video provider: `wistia` (default) or `synthesia` | | `preAsset` | ID | no | Wistia media ID for pre-roll video | | `postAsset` | ID | no | Wistia media ID for post-roll video | | `posterImageAsset` | URL | no | Poster image URL displayed before the video plays | | `audioUrl` | URL | no | External URL to an audio file (for `audio` type) — downloaded and stored by a background job, replacing any existing audio on the topic. Supported formats: MP3, WAV, OGG (max 200 MB) | ```endpoint PUT $BASE_URL/content/course/update ``` #### Example request - Update Course Group Title ```curl curl -X "PUT" "$BASE_URL/content/course/update" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "courseAttributes": { "courseGroups": [ { "id": "1e7a0ab5-9e13-44d5-90bf-e7c61e7448f8", "title": "Updated Course Title" } ] } }' ``` #### Example response ```json true ``` #### Example request - Update Topic Content ```curl curl -X "PUT" "$BASE_URL/content/course/update" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "courseAttributes": { "topics": [ { "id": "1366501c-3740-4a30-9464-1a2f111499ee", "body": "Updated content here" } ] } }' ``` #### Example request - Add New Section to Existing Course ```curl curl -X "PUT" "$BASE_URL/content/course/update" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "courseAttributes": { "sections": [ { "courseId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "title": "New Section", "displayOrder": 3 } ] } }' ``` #### Example request - Upload SCORM to Existing Topic ```curl curl -X "PUT" "$BASE_URL/content/course/update" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "courseAttributes": { "topics": [ { "id": "fdbec9af-e134-4c42-81c4-05a056dd40ca", "scormUrl": "https://example.com/scorm-packages/updated-module.zip", "restartProgress": true } ] } }' ``` #### Example response - SCORM Update with Background Job ```json { "success": true, "backgroundJob": { "id": "job-uuid-here", "type": "contentBulkUpload", "status": "queued", "contentUploads": [ { "topicId": "fdbec9af-e134-4c42-81c4-05a056dd40ca", "contentType": "scorm" } ] } } ``` #### Example request - Replace Audio on an Existing Topic To replace the audio file on an existing `audio` topic, send its `id` with a new `audioUrl`. The platform downloads the new file and overwrites the topic's existing audio in a background job. Supported formats are MP3, WAV, and OGG (max 200 MB). ```curl curl -X "PUT" "$BASE_URL/content/course/update" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "courseAttributes": { "topics": [ { "id": "1366501c-3740-4a30-9464-1a2f111499ee", "audioUrl": "https://example.com/audio/vowel-sounds-v2.mp3" } ] } }' ``` #### Example response - Audio Update with Background Job ```json { "success": true, "backgroundJob": { "id": "job-uuid-here", "type": "contentBulkUpload", "status": "queued", "contentUploads": [ { "topicId": "1366501c-3740-4a30-9464-1a2f111499ee", "contentType": "audio" } ] } } ``` #### Example request - Add PDF Topic to Existing Lesson ```curl curl -X "PUT" "$BASE_URL/content/course/update" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "courseAttributes": { "topics": [ { "lessonId": "93e6076b-ba84-4012-84ff-73c0367006e0", "title": "Course Materials PDF", "type": "pdfViewer", "pdfUrl": "https://example.com/materials/course-materials.pdf" } ] } }' ``` #### Errors Validation failures return HTTP `400` with an `errors` array. If a topic `id` does not belong to your company, the response reports `Topics not found: `. ```json { "errors": [ "Topics not found: 1366501c-3740-4a30-9464-1a2f111499ee" ] } ``` --- --- ### Upload Xliff Uploads an XLIFF file to update the translatable content of a course. ### PUT /v2/content/courses/:id.xlf :::caution The request body must be the raw XLIFF XML document itself (not JSON), sent with a `Content-Type` of `text/xml` or `application/xml`. Any other content type returns `415` with `invalid content type, must be text/xml`. A missing or empty body returns `400` with `invalid xml body`. ::: #### Example request ```bash curl -X PUT "$BASE_URL/content/courses/:id.xlf" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: text/xml' \ --data-binary @translated-course.xlf ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | Unique identifier of the course to update. | | (body) | `string` | Yes | body | Raw XLIFF XML document, up to 1MB, sent as the entire request body with a `text/xml` or `application/xml` content type. | #### Example response ```json { "data": { "ContentUploadXliff": true } } ``` --- ### Release or Unrelease Content Release (publish) or unrelease (unpublish) a content item. ### PUT /v2/content/:id/releaseContent :::note **Permission:** `courses.edit.settings` · **Not rate-limited** ::: #### Example request ```bash curl -X PUT "$BASE_URL/content//releaseContent" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "kind": "course", "action": "release" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | Content UUID. | | `kind` | `enum` | Yes | body | `course` or `learningPath`. | | `action` | `enum` | Yes | body | `release` or `unrelease`. | An invalid `kind` value returns an `"Invalid content kind"` error. #### Example response (`action: "release"`) ```json { "id": "123e4567-e89b-12d3-a456-426614174000", "status": "published" } ``` #### Example response (`action: "unrelease"`) ```json { "id": "123e4567-e89b-12d3-a456-426614174000", "status": "draft" } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Content ID. | | `status` | `string` | `published` for release, `draft` for unrelease. | --- ### Upload Xliff Uploads an XLIFF file to update the translations for the specified learning path. ### PUT /v2/content/learningPaths/:id.xlf :::caution The request body must be the raw XLIFF XML document itself (not JSON), sent with a `Content-Type` of `text/xml` or `application/xml`. Any other content type returns `415` with `invalid content type, must be text/xml`. A missing or empty body returns `400` with `invalid xml body`. ::: #### Example request ```bash curl -X PUT "$BASE_URL/content/learningPaths/:id.xlf" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: text/xml' \ --data-binary @translated-learning-path.xlf ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | Unique identifier of the learning path to update. | | (body) | `string` | Yes | body | Raw XLIFF XML document, up to 1MB, sent as the entire request body with a `text/xml` or `application/xml` content type. | #### Example response ```json { "data": { "ContentUploadXliff": true } } ``` --- --- ## Course Groups ### Course Groups Returns a paginated list of course groups. Filter results by kind, template status, or archived state. ### GET /v2/courseGroups #### Example request ```bash curl "$BASE_URL/courseGroups" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `CourseGroupKind` | No | query | Filters results to course groups of the specified kind (e.g., `microCourse`). | | `isTemplate` | `boolean` | No | query | When `true`, returns only course groups marked as templates. | | `archived` | `boolean` | No | query | When `true`, returns only archived course groups. | | `cursor` | `string` | No | query | Opaque cursor for the next page of course groups, taken from a previous response's `pageInfo.cursor`. Omit to fetch the first page. | | `perPage` | `integer` | No | query | Number of course groups to return per page. | #### Example response ```json { "pageInfo": { "perPage": 0, "cursor": "MTA", "total": 0, "hasMore": true }, "courseGroups": [ { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "slug": "example-slug", "kind": "microCourse", "status": "draft", "isTemplate": true, "language": "string", "contentType": "string", "authors": [ "string" ], "description": "string", "displayCourseId": "00000000-0000-0000-0000-000000000000", "courseCount": 0 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `PageInfo` | Pagination metadata for the result set. | | `pageInfo.perPage` | `integer` | Number of course groups returned per page. | | `pageInfo.cursor` | `string` | Opaque cursor for fetching the next page. Present when `hasMore` is `true`; pass it back as the `cursor` query parameter to continue. | | `pageInfo.total` | `integer` | Total number of course groups matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `courseGroups` | `APICourseGroupInfo[]` | Array of course group objects matching the query. | | `courseGroups.id` | `uuid` | Unique identifier for the course group. | | `courseGroups.title` | `string` | Display title of the course group. | | `courseGroups.slug` | `string` | URL-friendly identifier for the course group. | | `courseGroups.kind` | `CourseGroupKind` | The category or type of the course group (e.g., `microCourse`). | | `courseGroups.status` | `Status` | Publication status of the course group (e.g., `draft`). | | `courseGroups.isTemplate` | `boolean` | Indicates whether the course group is marked as a template. | | `courseGroups.language` | `string` | Language code of the course group's content. | | `courseGroups.contentType` | `string` | The content type associated with the course group. | | `courseGroups.authors` | `string[]` | List of author names or identifiers credited to the course group. | | `courseGroups.description` | `string` | Short description of the course group. | | `courseGroups.displayCourseId` | `uuid` | Unique identifier of the course used as the display representative for this group. | | `courseGroups.courseCount` | `integer` | Total number of courses contained in the course group. | --- ### Course Group By Slug Returns a single course group matching the provided URL slug. ### GET /v2/courseGroups/slug/:slug #### Example request ```bash curl "$BASE_URL/courseGroups/slug/:slug" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `slug` | `string` | Yes | path | The slug identifier. | #### Example response ```json { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "slug": "example-slug", "kind": "microCourse", "status": "draft", "isTemplate": true, "language": "string", "contentType": "string", "authors": [ "string" ], "description": "string", "displayCourseId": "00000000-0000-0000-0000-000000000000", "courseCount": 0, "asset": "string", "assetAltText": "string", "detailAsset": "string", "detailAssetAltText": "string", "metaTitle": "string", "metaDescription": "string", "customFields": {}, "isbn": "string", "source": "string", "externalPurchaseUrl": "string", "externalDetailUrl": "string" } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Unique identifier of the course group. | | `title` | `string` | Display title of the course group. | | `slug` | `string` | URL-friendly slug used to identify the course group. | | `kind` | `CourseGroupKind` | Category type of the course group (e.g., `microCourse`). | | `status` | `Status` | Publication status of the course group (e.g., `draft`). | | `isTemplate` | `boolean` | Indicates whether the course group is a template. | | `language` | `string` | Language of the course group content. | | `contentType` | `string` | Content type classification for the course group. | | `authors` | `string[]` | List of author names or identifiers associated with the course group. | | `description` | `string` | Short descriptive text for the course group. | | `displayCourseId` | `uuid` | Identifier of the course used for display or marketing purposes within the group. | | `courseCount` | `integer` | Total number of courses contained in the course group. | | `asset` | `string` | URL or reference to the primary thumbnail or listing image for the course group. | | `assetAltText` | `string` | Accessibility alt text for the primary asset image. | | `detailAsset` | `string` | URL or reference to the detail page image for the course group. | | `detailAssetAltText` | `string` | Accessibility alt text for the detail asset image. | | `metaTitle` | `string` | SEO meta title for the course group detail page. | | `metaDescription` | `string` | SEO meta description for the course group detail page. | | `customFields` | `object` | Key-value pairs of custom metadata fields defined for the course group. | | `isbn` | `string` | ISBN assigned to the course group, if applicable. | | `source` | `string` | Origin or source system from which the course group was created or imported. | | `externalPurchaseUrl` | `string` | External URL where learners can purchase access to the course group. | | `externalDetailUrl` | `string` | External URL pointing to additional detail or marketing information for the course group. | --- ### Course Group By Id Returns a single course group by its unique identifier. ### GET /v2/courseGroups/:id #### Example request ```bash curl "$BASE_URL/courseGroups/:id" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The unique identifier of the course group to retrieve. | #### Example response ```json { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "slug": "example-slug", "kind": "microCourse", "status": "draft", "isTemplate": true, "language": "string", "contentType": "string", "authors": [ "string" ], "description": "string", "displayCourseId": "00000000-0000-0000-0000-000000000000", "courseCount": 0, "asset": "string", "assetAltText": "string", "detailAsset": "string", "detailAssetAltText": "string", "metaTitle": "string", "metaDescription": "string", "customFields": {}, "isbn": "string", "source": "string", "externalPurchaseUrl": "string", "externalDetailUrl": "string" } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Unique identifier of the course group. | | `title` | `string` | Display title of the course group. | | `slug` | `string` | URL-friendly identifier used in the course group's path. | | `kind` | `CourseGroupKind` | Category type of the course group (e.g., `microCourse`). | | `status` | `Status` | Publication status of the course group (e.g., `draft`). | | `isTemplate` | `boolean` | Indicates whether the course group is a template. | | `language` | `string` | Language of the course group content. | | `contentType` | `string` | Content type classification assigned to the course group. | | `authors` | `string[]` | List of authors associated with the course group. | | `description` | `string` | Long-form description of the course group. | | `displayCourseId` | `uuid` | Identifier of the course used to represent the group on listing pages. | | `courseCount` | `integer` | Total number of courses within the course group. | | `asset` | `string` | URL of the thumbnail or card image for the course group. | | `assetAltText` | `string` | Accessibility alt text for the thumbnail image. | | `detailAsset` | `string` | URL of the hero or detail-page image for the course group. | | `detailAssetAltText` | `string` | Accessibility alt text for the detail-page image. | | `metaTitle` | `string` | SEO meta title for the course group page. | | `metaDescription` | `string` | SEO meta description for the course group page. | | `customFields` | `object` | Key-value pairs of custom metadata fields defined for the course group. | | `isbn` | `string` | ISBN assigned to the course group, if applicable. | | `source` | `string` | Origin or provider source of the course group content. | | `externalPurchaseUrl` | `string` | URL to an external page where the course group can be purchased. | | `externalDetailUrl` | `string` | URL to an external detail or marketing page for the course group. | --- ### Course Group Courses Returns a paginated list of courses belonging to the specified course group. ### GET /v2/courseGroups/:id/courses #### Example request ```bash curl "$BASE_URL/courseGroups/:id/courses" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The id identifier. | | `activeOnly` | `boolean` | No | query | When `true`, returns only courses that are currently active. | | `cursor` | `string` | No | query | Opaque cursor for the next page of courses, taken from a previous response's `pageInfo.cursor`. Omit to fetch the first page. | | `perPage` | `integer` | No | query | The number of courses to return per page. | #### Example response ```json { "pageInfo": { "perPage": 0, "cursor": "MTA", "total": 0, "hasMore": true }, "courses": [ { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "slug": "example-slug", "status": "draft", "courseStartDate": "2024-01-01T00:00:00.000Z", "courseEndDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "seatsLimit": 0, "seatsUsed": 0, "isDisplayCourse": true } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `PageInfo` | Pagination metadata for the result set. | | `pageInfo.perPage` | `integer` | Number of courses returned per page. | | `pageInfo.cursor` | `string` | Opaque cursor for fetching the next page. Present when `hasMore` is `true`; pass it back as the `cursor` query parameter to continue. | | `pageInfo.total` | `integer` | Total number of courses across all pages. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `courses` | `APICourseInfo[]` | Array of courses belonging to the course group. | | `courses.id` | `uuid` | Unique identifier for the course. | | `courses.title` | `string` | Display title of the course. | | `courses.slug` | `string` | URL-friendly identifier for the course. | | `courses.status` | `Status` | Publication status of the course (e.g., `draft`, `published`). | | `courses.courseStartDate` | `ISO 8601 timestamp` | Date and time when the course becomes available to learners, in ISO 8601 format. | | `courses.courseEndDate` | `ISO 8601 timestamp` | Date and time when the course is no longer available to learners, in ISO 8601 format. | | `courses.enrollmentStartDate` | `ISO 8601 timestamp` | Date and time when enrollment opens for the course, in ISO 8601 format. | | `courses.enrollmentEndDate` | `ISO 8601 timestamp` | Date and time when enrollment closes for the course, in ISO 8601 format. | | `courses.seatsLimit` | `integer` | Maximum number of learners that can enroll in the course. A value of `0` indicates no limit. | | `courses.seatsUsed` | `integer` | Number of seats currently occupied by enrolled learners. | | `courses.isDisplayCourse` | `boolean` | Indicates whether this course is designated as the display course for the course group. | --- ### Course Group Display Course Returns the display course associated with the specified course group. ### GET /v2/courseGroups/:id/displayCourse #### Example request ```bash curl "$BASE_URL/courseGroups/:id/displayCourse" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The unique identifier of the course group. | #### Example response ```json { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "slug": "example-slug", "status": "draft", "courseStartDate": "2024-01-01T00:00:00.000Z", "courseEndDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "seatsLimit": 0, "seatsUsed": 0 } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Unique identifier of the display course. | | `title` | `string` | Display name of the course. | | `slug` | `string` | URL-friendly identifier for the course. | | `status` | `Status` | Publication status of the course (e.g., `draft`, `published`). | | `courseStartDate` | `ISO 8601 timestamp` | Date and time when the course becomes available to learners. | | `courseEndDate` | `ISO 8601 timestamp` | Date and time when the course is no longer available to learners. | | `enrollmentStartDate` | `ISO 8601 timestamp` | Date and time when enrollment opens for the course. | | `enrollmentEndDate` | `ISO 8601 timestamp` | Date and time when enrollment closes for the course. | | `seatsLimit` | `integer` | Maximum number of learners who can enroll in the course. | | `seatsUsed` | `integer` | Number of seats currently occupied by enrolled learners. | --- --- ## Courses ### Course Sections Returns a paginated list of sections belonging to the specified course. ### GET /v2/courses/:courseId/sections #### Example request ```bash curl "$BASE_URL/courses/:courseId/sections" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `courseId` | `uuid` | Yes | path | The courseId identifier. | | `cursor` | `string` | No | query | Opaque cursor for the next page of sections, taken from a previous response's `pageInfo.cursor`. Omit to fetch the first page. | | `perPage` | `integer` | No | query | Number of sections to return per page. | #### Example response ```json { "pageInfo": { "perPage": 0, "cursor": "MTA", "total": 0, "hasMore": true }, "sections": [ { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "slug": "example-slug", "status": "draft", "releaseAt": "2024-01-01T00:00:00.000Z", "parentSectionId": "00000000-0000-0000-0000-000000000000", "lessonCount": 0 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `PageInfo` | Pagination metadata for the result set. | | `pageInfo.perPage` | `integer` | Number of sections returned per page. | | `pageInfo.cursor` | `string` | Opaque cursor for fetching the next page. Present when `hasMore` is `true`; pass it back as the `cursor` query parameter to continue. | | `pageInfo.total` | `integer` | Total number of sections across all pages. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `sections` | `APISectionInfo[]` | Array of section objects belonging to the course. | | `sections.id` | `uuid` | Unique identifier for the section. | | `sections.title` | `string` | Display title of the section. | | `sections.slug` | `string` | URL-friendly identifier for the section. | | `sections.status` | `Status` | Publication status of the section (e.g., `draft` or `published`). | | `sections.releaseAt` | `ISO 8601 timestamp` | Date and time when the section becomes available to learners, in ISO 8601 format. | | `sections.parentSectionId` | `uuid` | Unique identifier of the parent section, if this section is nested. | | `sections.lessonCount` | `integer` | Total number of lessons within the section. | --- ### Course Lessons Returns a paginated list of lessons belonging to the specified course. ### GET /v2/courses/:courseId/lessons #### Example request ```bash curl "$BASE_URL/courses/:courseId/lessons" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `courseId` | `uuid` | Yes | path | The courseId identifier. | | `cursor` | `string` | No | query | Opaque cursor for the next page of lessons, taken from a previous response's `pageInfo.cursor`. Omit to fetch the first page. | | `perPage` | `integer` | No | query | The number of lessons to return per page. | #### Example response ```json { "pageInfo": { "perPage": 0, "cursor": "MTA", "total": 0, "hasMore": true }, "lessons": [ { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "slug": "example-slug", "accessLevel": "studentsOnly", "sectionId": "00000000-0000-0000-0000-000000000000", "sectionTitle": "string", "topicCount": 0 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `PageInfo` | Pagination metadata for the response. | | `pageInfo.perPage` | `integer` | The number of lessons returned per page. | | `pageInfo.cursor` | `string` | Opaque cursor for fetching the next page. Present when `hasMore` is `true`; pass it back as the `cursor` query parameter to continue. | | `pageInfo.total` | `integer` | The total number of lessons across all pages. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `lessons` | `APILessonInfo[]` | The list of lessons belonging to the course. | | `lessons.id` | `uuid` | The unique identifier of the lesson. | | `lessons.title` | `string` | The display title of the lesson. | | `lessons.slug` | `string` | The URL-friendly identifier for the lesson. | | `lessons.accessLevel` | `AccessLevel` | The access restriction applied to the lesson (e.g., `studentsOnly`). | | `lessons.sectionId` | `uuid` | The unique identifier of the section that contains this lesson. | | `lessons.sectionTitle` | `string` | The display title of the section that contains this lesson. | | `lessons.topicCount` | `integer` | The number of topics within the lesson. | --- ### Course Completion Criteria Returns the completion criteria configured for a course. Each criterion defines a condition a learner must satisfy for the course to be marked complete. ### GET /v2/courses/:courseId/completionCriteria #### Example request ```bash curl "$BASE_URL/courses/:courseId/completionCriteria" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `courseId` | `uuid` | Yes | path | The courseId identifier. | #### Example response ```json [ { "id": "00000000-0000-0000-0000-000000000000", "type": "coursePercentViewed", "coursePercentViewed": 0, "courseAssignmentComplete": "00000000-0000-0000-0000-000000000000", "bongoAssignmentCompleted": "00000000-0000-0000-0000-000000000000", "courseAssessmentPassed": "00000000-0000-0000-0000-000000000000", "courseTopicViewed": "00000000-0000-0000-0000-000000000000", "courseMeetingAttended": "00000000-0000-0000-0000-000000000000", "scormComplete": "00000000-0000-0000-0000-000000000000", "surveyGizmoComplete": "00000000-0000-0000-0000-000000000000", "xApiComplete": "00000000-0000-0000-0000-000000000000", "articlePercentViewed": 0, "articleTimeViewedInSeconds": 0, "videoPercentViewed": 0 } ] ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `[].id` | `uuid` | Unique identifier for this completion criterion. | | `[].type` | `CourseCompletionCriteriaType` | Discriminator that identifies which criterion type applies (e.g. `coursePercentViewed`). | | `[].coursePercentViewed` | `integer` | Minimum percentage of the course a learner must view to satisfy this criterion, expressed as a whole number. | | `[].courseAssignmentComplete` | `uuid` | ID of the course assignment that must be completed to satisfy this criterion. | | `[].bongoAssignmentCompleted` | `uuid` | ID of the Bongo assignment that must be completed to satisfy this criterion. | | `[].courseAssessmentPassed` | `uuid` | ID of the course assessment that must be passed to satisfy this criterion. | | `[].courseTopicViewed` | `uuid` | ID of the course topic that must be viewed to satisfy this criterion. | | `[].courseMeetingAttended` | `uuid` | ID of the course meeting that must be attended to satisfy this criterion. | | `[].scormComplete` | `uuid` | ID of the SCORM topic that must be completed to satisfy this criterion. | | `[].surveyGizmoComplete` | `uuid` | ID of the SurveyGizmo survey that must be completed to satisfy this criterion. | | `[].xApiComplete` | `uuid` | ID of the xAPI activity that must be completed to satisfy this criterion. | | `[].articlePercentViewed` | `integer` | Minimum percentage of a specific article a learner must view, expressed as a whole number. | | `[].articleTimeViewedInSeconds` | `integer` | Minimum time a learner must spend viewing an article to satisfy this criterion, in seconds. | | `[].videoPercentViewed` | `integer` | Minimum percentage of a specific video a learner must watch, expressed as a whole number. | --- ### Course Structure Returns the full hierarchical structure of a course, including its sections, lessons, and completion criteria. ### GET /v2/courses/:courseId/structure #### Example request ```bash curl "$BASE_URL/courses/:courseId/structure" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `courseId` | `uuid` | Yes | path | The courseId identifier. | #### Example response ```json { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "status": "draft", "sections": [ { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "releaseAt": "2024-01-01T00:00:00.000Z", "lessons": [ { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "accessLevel": "studentsOnly", "topics": [ { "...": "..." } ] } ] } ], "completionCriteria": [ { "id": "00000000-0000-0000-0000-000000000000", "type": "coursePercentViewed", "coursePercentViewed": 0, "courseAssignmentComplete": "00000000-0000-0000-0000-000000000000", "bongoAssignmentCompleted": "00000000-0000-0000-0000-000000000000", "courseAssessmentPassed": "00000000-0000-0000-0000-000000000000", "courseTopicViewed": "00000000-0000-0000-0000-000000000000", "courseMeetingAttended": "00000000-0000-0000-0000-000000000000", "scormComplete": "00000000-0000-0000-0000-000000000000", "surveyGizmoComplete": "00000000-0000-0000-0000-000000000000", "xApiComplete": "00000000-0000-0000-0000-000000000000", "articlePercentViewed": 0, "articleTimeViewedInSeconds": 0, "videoPercentViewed": 0 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Unique identifier for the course. | | `title` | `string` | Display title of the course. | | `status` | `Status` | Publication status of the course (e.g., `draft`, `published`). | | `sections` | `APISectionWithLessons[]` | Ordered list of sections belonging to the course, each containing its lessons. | | `sections.id` | `uuid` | Unique identifier for the section. | | `sections.title` | `string` | Display title of the section. | | `sections.releaseAt` | `ISO 8601 timestamp` | Date and time when the section becomes available to learners, as an ISO 8601 timestamp. | | `sections.lessons` | `APILessonWithTopics[]` | Ordered list of lessons within the section, each containing its topics. | | `completionCriteria` | `CourseCompletionCriteria[]` | List of criteria that must be satisfied for a learner to be marked as having completed the course. | | `completionCriteria.id` | `uuid` | Unique identifier for the completion criterion. | | `completionCriteria.type` | `CourseCompletionCriteriaType` | Category of the completion criterion (e.g., `coursePercentViewed`). | | `completionCriteria.coursePercentViewed` | `integer` | Minimum percentage of course content a learner must view to satisfy this criterion, expressed as a whole number from 0 to 100. | | `completionCriteria.courseAssignmentComplete` | `uuid` | Identifier of the course assignment that must be completed to satisfy this criterion. | | `completionCriteria.bongoAssignmentCompleted` | `uuid` | Identifier of the Bongo assignment that must be completed to satisfy this criterion. | | `completionCriteria.courseAssessmentPassed` | `uuid` | Identifier of the course assessment that must be passed to satisfy this criterion. | | `completionCriteria.courseTopicViewed` | `uuid` | Identifier of the topic that must be viewed to satisfy this criterion. | | `completionCriteria.courseMeetingAttended` | `uuid` | Identifier of the meeting that must be attended to satisfy this criterion. | | `completionCriteria.scormComplete` | `uuid` | Identifier of the SCORM topic that must be completed to satisfy this criterion. | | `completionCriteria.surveyGizmoComplete` | `uuid` | Identifier of the SurveyGizmo survey that must be completed to satisfy this criterion. | | `completionCriteria.xApiComplete` | `uuid` | Identifier of the xAPI topic that must be completed to satisfy this criterion. | | `completionCriteria.articlePercentViewed` | `integer` | Minimum percentage of an article a learner must view to satisfy this criterion, expressed as a whole number from 0 to 100. | | `completionCriteria.articleTimeViewedInSeconds` | `integer` | Minimum time a learner must spend viewing an article to satisfy this criterion, in seconds. | | `completionCriteria.videoPercentViewed` | `integer` | Minimum percentage of a video a learner must watch to satisfy this criterion, expressed as a whole number from 0 to 100. | --- --- ## REST API Reference Browse the available REST API endpoints by category. | Category | Endpoints | |----------|-----------| | [Assessments](/docs/rest/endpoints/assessments) | 3 | | [Assignments & External Activity](/docs/rest/endpoints/assignments-external-activity) | 4 | | [Certifications](/docs/rest/endpoints/certifications) | 3 | | [Commerce](/docs/rest/endpoints/commerce) | 14 | | [Content](/docs/rest/endpoints/content) | 27 | | [Course Groups](/docs/rest/endpoints/course-groups) | 5 | | [Courses](/docs/rest/endpoints/courses) | 4 | | [Logs](/docs/rest/endpoints/logs) | 6 | | [Meetings (ILT & VILT)](/docs/rest/endpoints/meetings-ilt-vilt) | 3 | | [Notifications (Learner Actions)](/docs/rest/endpoints/notifications-learner-actions) | 35 | | [Panorama](/docs/rest/endpoints/panorama) | 13 | | [System](/docs/rest/endpoints/system) | 3 | | [Users](/docs/rest/endpoints/users) | 20 | --- ## Logs ### Avalara Returns a paginated list of Avalara API cache logs. ### GET /v2/logs/avalara :::note Other valid `/v2/logs/:type` values not documented as separate pages here include `email`, `rusticiPostback`, `campaign`, `bulkImportCoupon`, and `zoom`. ::: #### Example request ```bash curl "$BASE_URL/logs/avalara" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque pagination cursor returned by a previous response; use to fetch the next page of results. | | `perPage` | `integer` | No | query | Number of log entries to return per page. Defaults to `50`; must be greater than `2`. | #### Example response ```json { "data": { "APICacheLogs": { "data": [ {} ], "pageInfo": { "cursor": "string", "hasMore": true } } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `data` | `object[]` | Array of Avalara API cache log entries. | | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.cursor` | `string` | Opaque cursor value to pass in the next request to retrieve the following page of results. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | --- ### BulkImportCourseGroupInPersonEvent Returns a paginated list of bulk import course group in-person event logs. ### GET /v2/logs/bulkImportCourseGroupInPersonEvent :::note Other valid `/v2/logs/:type` values not documented as separate pages here include `email`, `rusticiPostback`, `campaign`, `bulkImportCoupon`, and `zoom`. ::: #### Example request ```bash curl "$BASE_URL/logs/bulkImportCourseGroupInPersonEvent" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque pagination cursor returned by a previous response; use to fetch the next page of results. | | `perPage` | `integer` | No | query | Number of log entries to return per page. Defaults to `50`; must be greater than `2`. | #### Example response ```json { "data": { "APICacheLogs": { "data": [ {} ], "pageInfo": { "cursor": "string", "hasMore": true } } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `data` | `object[]` | Array of bulk import course group in-person event log entries. | | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.cursor` | `string` | Opaque cursor value to pass in the next request to retrieve the following page of results. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | --- ### BulkImportCourseGroupWebinar Returns a paginated list of bulk import logs for course group webinars. ### GET /v2/logs/bulkImportCourseGroupWebinar :::note Other valid `/v2/logs/:type` values not documented as separate pages here include `email`, `rusticiPostback`, `campaign`, `bulkImportCoupon`, and `zoom`. ::: #### Example request ```bash curl "$BASE_URL/logs/bulkImportCourseGroupWebinar" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque pagination cursor returned by a previous response; use to fetch the next page of results. | | `perPage` | `integer` | No | query | Number of log entries to return per page. Defaults to `50`; must be greater than `2`. | #### Example response ```json { "data": { "APICacheLogs": { "data": [ {} ], "pageInfo": { "cursor": "string", "hasMore": true } } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `data` | `object[]` | Array of bulk import log entry objects. | | `pageInfo` | `CursorPageInfo` | Pagination metadata for the result set. | | `pageInfo.cursor` | `string` | Opaque cursor value to use in subsequent requests to retrieve the next page of results. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | --- ### BulkImportCourseInPersonEvent Returns a paginated list of bulk import course in-person event logs. ### GET /v2/logs/bulkImportCourseInPersonEvent :::note Other valid `/v2/logs/:type` values not documented as separate pages here include `email`, `rusticiPostback`, `campaign`, `bulkImportCoupon`, and `zoom`. ::: #### Example request ```bash curl "$BASE_URL/logs/bulkImportCourseInPersonEvent" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque pagination cursor returned by a previous response; use to fetch the next page of results. | | `perPage` | `integer` | No | query | Number of log entries to return per page. Defaults to `50`; must be greater than `2`. | #### Example response ```json { "data": { "APICacheLogs": { "data": [ {} ], "pageInfo": { "cursor": "string", "hasMore": true } } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `data` | `object[]` | Array of bulk import course in-person event log entries. | | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.cursor` | `string` | Opaque cursor value to pass in the next request to retrieve the following page of results. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | --- ### BulkImportCourseWebinar Returns a paginated list of bulk import course webinar logs. ### GET /v2/logs/bulkImportCourseWebinar :::note Other valid `/v2/logs/:type` values not documented as separate pages here include `email`, `rusticiPostback`, `campaign`, `bulkImportCoupon`, and `zoom`. ::: #### Example request ```bash curl "$BASE_URL/logs/bulkImportCourseWebinar" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque pagination cursor returned by a previous response; use to fetch the next page of results. | | `perPage` | `integer` | No | query | Number of log entries to return per page. Defaults to `50`; must be greater than `2`. | #### Example response ```json { "data": { "APICacheLogs": { "data": [ {} ], "pageInfo": { "cursor": "string", "hasMore": true } } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `data` | `object[]` | Array of bulk import course webinar log entries. | | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.cursor` | `string` | Opaque cursor value to pass in the next request to retrieve the following page of results. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | --- ### Sso Returns a paginated list of SSO log entries. ### GET /v2/logs/sso :::note Other valid `/v2/logs/:type` values not documented as separate pages here include `email`, `rusticiPostback`, `campaign`, `bulkImportCoupon`, and `zoom`. ::: #### Example request ```bash curl "$BASE_URL/logs/sso" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque pagination cursor returned by a previous response; use to fetch the next page of results. | | `perPage` | `integer` | No | query | Number of log entries to return per page. Defaults to `50`; must be greater than `2`. | #### Example response ```json { "data": { "APICacheLogs": { "data": [ {} ], "pageInfo": { "cursor": "string", "hasMore": true } } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `data` | `object[]` | Array of SSO log entry objects matching the requested type. | | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.cursor` | `string` | Opaque cursor value to pass in the next request to retrieve the subsequent page. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | --- --- ## Meetings (ILT & VILT) ### Locations Returns a paginated list of physical locations configured in the platform. ### GET /v2/locations #### Example request ```bash curl "$BASE_URL/locations" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque pagination cursor returned by a previous response; use to fetch the next page of results. | #### Example response ```json { "data": { "APILocations": { "pageInfo": { "total": 0, "cursor": "string", "hasMore": true }, "locations": [ { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "room": "string", "address1": "string", "address2": "string", "city": "string", "state": "string", "zipCode": "string", "country": "string", "timeZone": "string" } ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.total` | `integer` | Total number of locations across all pages. | | `pageInfo.cursor` | `string` | Opaque cursor to pass in the next request to retrieve the following page. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `locations` | `Location[]` | Array of location objects returned for the current page. | | `locations.id` | `uuid` | Unique identifier for the location. | | `locations.name` | `string` | Display name of the location. | | `locations.room` | `string` | Room or suite designation within the location. | | `locations.address1` | `string` | Primary street address line. | | `locations.address2` | `string` | Secondary address line (e.g., suite or floor number). | | `locations.city` | `string` | City where the location resides. | | `locations.state` | `string` | State or province where the location resides. | | `locations.zipCode` | `string` | Postal or ZIP code for the location. | | `locations.country` | `string` | Country where the location resides. | | `locations.timeZone` | `string` | IANA time zone identifier for the location (e.g., `America/New_York`). | --- ### Meetings Returns a paginated list of meetings. Filter by course using the `courseId` parameter. ### GET /v2/meetings #### Example request ```bash curl "$BASE_URL/meetings" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque pagination cursor returned by a previous response; use to fetch the next page of results. | | `courseId` | `uuid` | No | query | Filters results to meetings belonging to the specified course. | #### Example response ```json { "data": { "Meetings": { "pageInfo": { "total": 0, "cursor": "string", "hasMore": true }, "meetings": [ { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "course": { "id": "00000000-0000-0000-0000-000000000000", "title": "string" }, "location": { "id": "00000000-0000-0000-0000-000000000000", "name": "string" }, "instructors": [ "string" ], "joinUrl": "string", "attendeeInfo": "string", "startDate": "2024-01-01T00:00:00.000Z", "endDate": "2024-01-01T00:00:00.000Z" } ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.total` | `integer` | Total number of meetings across all pages. | | `pageInfo.cursor` | `string` | Opaque cursor to pass in the next request to retrieve the following page. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results exist. | | `meetings` | `Meeting[]` | Array of meeting objects returned for the current page. | | `meetings.id` | `uuid` | Unique identifier for the meeting. | | `meetings.title` | `string` | Display title of the meeting. | | `meetings.course` | `Course` | Course this meeting belongs to. | | `meetings.course.id` | `uuid` | Unique identifier of the course. | | `meetings.course.title` | `string` | Title of the course. | | `meetings.location` | `Location` | Physical location of the meeting. | | `meetings.location.id` | `uuid` | Unique identifier of the location. | | `meetings.location.name` | `string` | Name of the location. | | `meetings.instructors` | `string[]` | List of instructor names assigned to the meeting. | | `meetings.joinUrl` | `string` | URL attendees use to join the meeting. | | `meetings.attendeeInfo` | `string` | Additional instructions or notes displayed to meeting attendees. | | `meetings.startDate` | `ISO 8601 timestamp` | Date and time the meeting begins, in ISO 8601 format. | | `meetings.endDate` | `ISO 8601 timestamp` | Date and time the meeting ends, in ISO 8601 format. | --- ### Meetings Returns a single meeting by its unique identifier, including the associated course, location, instructors, and attendee details. ### GET /v2/meetings/:id #### Example request ```bash curl "$BASE_URL/meetings/:id" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | Unique identifier of the meeting to retrieve. | #### Example response ```json { "data": { "MeetingById": { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "course": { "id": "00000000-0000-0000-0000-000000000000", "title": "string" }, "location": { "id": "00000000-0000-0000-0000-000000000000", "name": "string" }, "instructors": [ "string" ], "joinUrl": "string", "attendeeInfo": "string", "startDate": "2024-01-01T00:00:00.000Z", "endDate": "2024-01-01T00:00:00.000Z", "attendees": [ { "id": "00000000-0000-0000-0000-000000000000", "email": "string", "firstName": "string", "lastName": "string", "externalCustomerId": "00000000-0000-0000-0000-000000000000" } ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Unique identifier for the meeting. | | `title` | `string` | Display title of the meeting. | | `course` | `Course` | Course this meeting belongs to. | | `course.id` | `uuid` | Unique identifier of the course. | | `course.title` | `string` | Title of the course. | | `location` | `Location` | Physical location of the meeting. | | `location.id` | `uuid` | Unique identifier of the location. | | `location.name` | `string` | Name of the location. | | `instructors` | `string[]` | List of instructor names assigned to the meeting. | | `joinUrl` | `string` | URL attendees use to join the meeting. | | `attendeeInfo` | `string` | Additional instructions or notes displayed to meeting attendees. | | `startDate` | `ISO 8601 timestamp` | Date and time the meeting begins, in ISO 8601 format. | | `endDate` | `ISO 8601 timestamp` | Date and time the meeting ends, in ISO 8601 format. | | `attendees` | `User[]` | List of users registered to attend the meeting. | | `attendees.id` | `uuid` | Unique identifier of the attendee. | | `attendees.email` | `string` | Email address of the attendee. | | `attendees.firstName` | `string` | First name of the attendee. | | `attendees.lastName` | `string` | Last name of the attendee. | | `attendees.externalCustomerId` | `uuid` | External customer identifier associated with the attendee. | --- --- ## Notifications (Learner Actions) ## Conventions for every endpoint on this page Every endpoint below routes to the same controller and shares the same behavior: - `kind` is the URL path segment (for example, `/v2/events/courseAction`), not a query parameter — do not pass `kind` in the query string. - Pagination is cursor-only. Pass `cursor` (opaque) from the previous response; there is no `page` or `perPage`. - `startDate` and `endDate` are **epoch milliseconds** (integers), not ISO 8601 strings. - `notifiableId` and `userId` are honored **only** for `courseAction` and `learningPathAction`; the controller passes them to the underlying query for those two kinds only. On the other 33 kinds they are silently ignored. - Responses are unwrapped: `{ pageInfo: { cursor, hasMore }, events: [...] }`. There is no `data.APIEvents` envelope, no `perPage`, and no `total` field on `pageInfo`. ### Extract List courseAction ### GET /v2/events/courseAction Returns a paginated list of `courseAction` events, which fire when a learner takes an action on a course (such as starting, completing, or progressing through it). #### Example request ```bash curl "$BASE_URL/events/courseAction" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `integer` | No | query | Returns only events at or after this timestamp. Epoch milliseconds. | | `endDate` | `integer` | No | query | Returns only events at or before this timestamp. Epoch milliseconds. | | `notifiableId` | `uuid` | No | query | Filters results to events associated with a specific course, identified by its UUID. | | `userId` | `uuid` | No | query | Filters results to events triggered by a specific learner, identified by their UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `object` | Cursor-only pagination metadata. | | `pageInfo.cursor` | `string` | Opaque cursor value to pass on the next request. Present only when `hasMore` is `true`. | | `pageInfo.hasMore` | `boolean` | Whether additional pages of results are available. | | `events` | `object[]` | Array of courseAction event objects matching the query parameters. | --- ### Extract List courseEnrollment ### GET /v2/events/courseEnrollment Returns a paginated list of course enrollment events. Filter results by date range, user, or notifiable resource using the available query parameters. #### Example request ```bash curl "$BASE_URL/events/courseEnrollment" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque cursor string for fetching the next page of results. | | `startDate` | `integer` | No | query | Returns only events at or after this timestamp. Epoch milliseconds. | | `endDate` | `integer` | No | query | Returns only events at or before this timestamp. Epoch milliseconds. | | `notifiableId` | `uuid` | No | query | Ignored for this kind. Applies only to `courseAction` and `learningPathAction`. | | `userId` | `uuid` | No | query | Ignored for this kind. Applies only to `courseAction` and `learningPathAction`. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `object` | Cursor-only pagination metadata. | | `pageInfo.cursor` | `string` | Opaque cursor value to pass on the next request. Present only when `hasMore` is `true`. | | `pageInfo.hasMore` | `boolean` | Whether additional pages of results are available. | | `events` | `object[]` | Array of course enrollment event objects matching the query. | --- ### Extract List courseCompletion ### GET /v2/events/courseCompletion Returns a paginated list of course completion events. Filter results by date range, user, or notifiable resource. #### Example request ```bash curl "$BASE_URL/events/courseCompletion" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `ISO 8601 timestamp` | No | query | Returns only events that occurred on or after this ISO 8601 timestamp. | | `endDate` | `ISO 8601 timestamp` | No | query | Returns only events that occurred on or before this ISO 8601 timestamp. | | `notifiableId` | `uuid` | No | query | Filters events to those associated with the specified course or notifiable resource UUID. | | `userId` | `uuid` | No | query | Filters events to those associated with the specified learner UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Cursor value to pass in the next request to retrieve the following page. | | `pageInfo.total` | `integer` | Total number of matching events across all pages. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of course completion event objects matching the query filters. | --- ### Extract List courseCertificate ### GET /v2/events/courseCertificate Returns a paginated list of `courseCertificate` events. Emitted when a learner earns a certificate upon completing a course. #### Example request ```bash curl "$BASE_URL/events/courseCertificate" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters results to a specific event kind. | | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `ISO 8601 timestamp` | No | query | Returns only events that occurred on or after this ISO 8601 timestamp. | | `endDate` | `ISO 8601 timestamp` | No | query | Returns only events that occurred on or before this ISO 8601 timestamp. | | `notifiableId` | `uuid` | No | query | Filters results to events associated with the specified notifiable resource UUID. | | `userId` | `uuid` | No | query | Filters results to events associated with the specified learner UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Maximum number of events returned per page. | | `pageInfo.cursor` | `string` | Opaque cursor to pass as the `cursor` parameter to retrieve the next page. | | `pageInfo.total` | `integer` | Total number of matching events across all pages. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of `courseCertificate` event objects matching the query. | --- ### Extract List activeCourseCertificate ### GET /v2/events/activeCourseCertificate Returns a paginated list of `activeCourseCertificate` events. Emitted when a course certificate becomes active for a learner. #### Example request ```bash curl "$BASE_URL/events/activeCourseCertificate" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters results to a specific event kind. | | `cursor` | `string` | No | query | Opaque pagination cursor returned by a previous response; pass this value to retrieve the next page of results. | | `startDate` | `integer` | No | query | Returns only events at or after this timestamp. Epoch milliseconds. | | `endDate` | `integer` | No | query | Returns only events at or before this timestamp. Epoch milliseconds. | | `notifiableId` | `uuid` | No | query | Filters results to events associated with the specified notifiable resource UUID. | | `userId` | `uuid` | No | query | Filters results to events associated with the specified learner's UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Opaque cursor to pass as the `cursor` parameter to retrieve the next page of results. | | `pageInfo.total` | `integer` | Total number of events matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of `activeCourseCertificate` event objects matching the query. | --- ### Extract List courseView ### GET /v2/events/courseView Returns a paginated list of `courseView` events, emitted when a learner views a course. #### Example request ```bash curl "$BASE_URL/events/courseView" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters results to a specific event kind. | | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `integer` | No | query | Returns only events at or after this timestamp. Epoch milliseconds. | | `endDate` | `integer` | No | query | Returns only events at or before this timestamp. Epoch milliseconds. | | `notifiableId` | `uuid` | No | query | Filters results to events associated with the specified course UUID. | | `userId` | `uuid` | No | query | Filters results to events generated by the specified user UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Opaque cursor to pass in the next request to retrieve the following page. | | `pageInfo.total` | `integer` | Total number of events matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of courseView event objects matching the query. | --- ### Extract List courseComment ### GET /v2/events/courseComment Returns a paginated list of `courseComment` events, representing learner comments posted in a course context. #### Example request ```bash curl "$BASE_URL/events/courseComment" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters events by a specific subtype or kind of course comment event. | | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `integer` | No | query | Returns only events at or after this timestamp. Epoch milliseconds. | | `endDate` | `integer` | No | query | Returns only events at or before this timestamp. Epoch milliseconds. | | `notifiableId` | `uuid` | No | query | Filters events to those associated with the specified notifiable resource (e.g. a course), identified by UUID. | | `userId` | `uuid` | No | query | Filters events to those generated by the specified user, identified by UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Maximum number of events returned per page. | | `pageInfo.cursor` | `string` | Cursor value to pass in the next request to retrieve the following page of results. | | `pageInfo.total` | `integer` | Total number of events matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of courseComment event objects matching the query. | --- ### Extract List courseInstructorMessage ### GET /v2/events/courseInstructorMessage #### Example request ```bash curl "$BASE_URL/events/courseInstructorMessage" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters results to a specific event kind. | | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `integer` | No | query | Returns only events at or after this timestamp. Epoch milliseconds. | | `endDate` | `integer` | No | query | Returns only events at or before this timestamp. Epoch milliseconds. | | `notifiableId` | `uuid` | No | query | Filters results to events associated with the specified notifiable resource. | | `userId` | `uuid` | No | query | Filters results to events associated with the specified user. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Opaque cursor to pass in the next request to retrieve the following page. | | `pageInfo.total` | `integer` | Total number of events matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of courseInstructorMessage event objects matching the query. | --- ### Extract List award ### GET /v2/events/award Returns a paginated list of award events, optionally filtered by user, notifiable object, award kind, or date range. #### Example request ```bash curl "$BASE_URL/events/award" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters results to a specific award type. | | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `ISO 8601 timestamp` | No | query | Returns only events on or after this ISO 8601 timestamp. | | `endDate` | `ISO 8601 timestamp` | No | query | Returns only events on or before this ISO 8601 timestamp. | | `notifiableId` | `uuid` | No | query | Filters results to events associated with the specified notifiable object. | | `userId` | `uuid` | No | query | Filters results to events belonging to the specified user. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Cursor value to pass in the next request to retrieve the following page. | | `pageInfo.total` | `integer` | Total number of award events matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of award event objects matching the query. | --- ### Extract List learningPathAction ### GET /v2/events/learningPathAction Returns a paginated list of `learningPathAction` events. Each event captures a learner's interaction with a learning path, such as starting or completing it. #### Example request ```bash curl "$BASE_URL/events/learningPathAction" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `integer` | No | query | Returns only events at or after this timestamp. Epoch milliseconds. | | `endDate` | `integer` | No | query | Returns only events at or before this timestamp. Epoch milliseconds. | | `notifiableId` | `uuid` | No | query | Filters results to events associated with the specified learning path UUID. | | `userId` | `uuid` | No | query | Filters results to events generated by the specified learner UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Maximum number of events returned per page. | | `pageInfo.cursor` | `string` | Cursor value to pass in the next request to retrieve the following page. | | `pageInfo.total` | `integer` | Total number of events matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of `learningPathAction` event objects matching the query. | --- ### Extract List learningPathEnrollment ### GET /v2/events/learningPathEnrollment #### Example request ```bash curl "$BASE_URL/events/learningPathEnrollment" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters events by kind classification. | | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `ISO 8601 timestamp` | No | query | Returns only events occurring at or after this ISO 8601 timestamp. | | `endDate` | `ISO 8601 timestamp` | No | query | Returns only events occurring at or before this ISO 8601 timestamp. | | `notifiableId` | `uuid` | No | query | Filters events to those associated with the specified learning path UUID. | | `userId` | `uuid` | No | query | Filters events to those associated with the specified user UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Opaque cursor value to pass in the next request to retrieve the following page. | | `pageInfo.total` | `integer` | Total number of matching events across all pages. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of learning path enrollment event objects matching the query. | --- ### Extract List quizAttempt ### GET /v2/events/quizAttempt Returns a paginated list of quiz attempt events. Filter results by date range, user, or notifiable resource using the available query parameters. #### Example request ```bash curl "$BASE_URL/events/quizAttempt" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `integer` | No | query | Returns only events at or after this timestamp. Epoch milliseconds. | | `endDate` | `integer` | No | query | Returns only events at or before this timestamp. Epoch milliseconds. | | `notifiableId` | `uuid` | No | query | Filters events to those associated with the specified notifiable resource UUID. | | `userId` | `uuid` | No | query | Filters events to those belonging to the specified user UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Cursor value to pass in the next request to retrieve the following page. | | `pageInfo.total` | `integer` | Total number of events matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of quiz attempt event objects matching the query. | --- ### Extract List assignmentSubmission ### GET /v2/events/assignmentSubmission Returns a paginated list of assignment submission events. Filter results by date range, user, or notifiable resource using the available query parameters. #### Example request ```bash curl "$BASE_URL/events/assignmentSubmission" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `ISO 8601 timestamp` | No | query | Returns only events that occurred on or after this ISO 8601 timestamp. | | `endDate` | `ISO 8601 timestamp` | No | query | Returns only events that occurred on or before this ISO 8601 timestamp. | | `notifiableId` | `uuid` | No | query | Filters events to those associated with the specified notifiable resource UUID. | | `userId` | `uuid` | No | query | Filters events to those associated with the specified user UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Cursor value to pass in the next request to retrieve the following page. | | `pageInfo.total` | `integer` | Total number of events matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of assignment submission event objects matching the query. | --- ### Extract List scormAttempt ### GET /v2/events/scormAttempt Returns a paginated list of SCORM attempt events. Filter results by user, notifiable resource, or date range. #### Example request ```bash curl "$BASE_URL/events/scormAttempt" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Pagination cursor returned from a previous response; pass this value to retrieve the next page of results. | | `startDate` | `ISO 8601 timestamp` | No | query | Returns only events that occurred on or after this ISO 8601 timestamp. | | `endDate` | `ISO 8601 timestamp` | No | query | Returns only events that occurred on or before this ISO 8601 timestamp. | | `notifiableId` | `uuid` | No | query | Filters events to those associated with the specified notifiable resource UUID. | | `userId` | `uuid` | No | query | Filters events to those belonging to the specified user UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Opaque cursor value to pass as the `cursor` parameter to retrieve the next page. | | `pageInfo.total` | `integer` | Total number of events matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of SCORM attempt event objects matching the query. | --- ### Extract List coursePurchase ### GET /v2/events/coursePurchase Returns a paginated list of `coursePurchase` events, each emitted when a learner purchases a course. Filter results by date range, user, or notifiable resource. #### Example request ```bash curl "$BASE_URL/events/coursePurchase" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters events by kind, scoping results to a specific event subtype. | | `cursor` | `string` | No | query | Opaque pagination cursor returned by a previous response; pass it to retrieve the next page of results. | | `startDate` | `integer` | No | query | Returns only events at or after this timestamp. Epoch milliseconds. | | `endDate` | `integer` | No | query | Returns only events at or before this timestamp. Epoch milliseconds. | | `notifiableId` | `uuid` | No | query | Filters events to those associated with the specified course or resource UUID. | | `userId` | `uuid` | No | query | Filters events to those generated by the specified learner UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Opaque cursor to pass as the `cursor` parameter to retrieve the next page. | | `pageInfo.total` | `integer` | Total number of events matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of `coursePurchase` event objects matching the query. | --- ### Extract List productPurchase ### GET /v2/events/productPurchase Returns a paginated list of `productPurchase` events. Emitted when a learner purchases a product. #### Example request ```bash curl "$BASE_URL/events/productPurchase" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters results to a specific event kind. | | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `ISO 8601 timestamp` | No | query | Returns only events occurring at or after this ISO 8601 timestamp. | | `endDate` | `ISO 8601 timestamp` | No | query | Returns only events occurring at or before this ISO 8601 timestamp. | | `notifiableId` | `uuid` | No | query | Filters results to events associated with the specified notifiable resource UUID. | | `userId` | `uuid` | No | query | Filters results to events associated with the specified user UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Opaque cursor to pass in the next request to retrieve the following page. | | `pageInfo.total` | `integer` | Total number of events matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of `productPurchase` event objects matching the query. | --- ### Extract List bundlePurchase ### GET /v2/events/bundlePurchase Retrieves a paginated list of `bundlePurchase` events. Each event is emitted when a learner purchases a bundle. Filter results by date range, user, or the purchased bundle. #### Example request ```bash curl "$BASE_URL/events/bundlePurchase" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque cursor string returned by a previous response; pass it to fetch the next page of results. | | `startDate` | `ISO 8601 timestamp` | No | query | Returns only events that occurred on or after this ISO 8601 timestamp. | | `endDate` | `ISO 8601 timestamp` | No | query | Returns only events that occurred on or before this ISO 8601 timestamp. | | `notifiableId` | `uuid` | No | query | Filters events to those associated with the specified bundle UUID. | | `userId` | `uuid` | No | query | Filters events to those generated by the specified user UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Cursor value to pass in the next request to retrieve the following page. | | `pageInfo.total` | `integer` | Total number of events matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of `bundlePurchase` event objects matching the query. | --- ### Extract List discountGroupPurchase ### GET /v2/events/discountGroupPurchase Returns a paginated list of `discountGroupPurchase` events. Each event is emitted when a learner completes a purchase using a discount group. #### Example request ```bash curl "$BASE_URL/events/discountGroupPurchase" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters results to a specific event kind. | | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `ISO 8601 timestamp` | No | query | Returns only events on or after this ISO 8601 timestamp. | | `endDate` | `ISO 8601 timestamp` | No | query | Returns only events on or before this ISO 8601 timestamp. | | `notifiableId` | `uuid` | No | query | Filters results to events associated with the specified notifiable resource UUID. | | `userId` | `uuid` | No | query | Filters results to events associated with the specified user UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Opaque cursor value to pass in the next request to retrieve the following page. | | `pageInfo.total` | `integer` | Total number of events matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of `discountGroupPurchase` event objects matching the query. | --- ### Extract List pickableGroupPurchase ### GET /v2/events/pickableGroupPurchase Retrieves a paginated list of `pickableGroupPurchase` events. Filter results by date range, user, or notifiable resource using the available query parameters. #### Example request ```bash curl "$BASE_URL/events/pickableGroupPurchase" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Pagination cursor returned from a previous response; use to fetch the next page of results. | | `startDate` | `integer` | No | query | Returns only events at or after this timestamp. Epoch milliseconds. | | `endDate` | `integer` | No | query | Returns only events at or before this timestamp. Epoch milliseconds. | | `notifiableId` | `uuid` | No | query | Filters events to those associated with the specified notifiable resource UUID. | | `userId` | `uuid` | No | query | Filters events to those associated with the specified user UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `object` | Cursor-only pagination metadata. | | `pageInfo.cursor` | `string` | Opaque cursor value to pass on the next request. Present only when `hasMore` is `true`. | | `pageInfo.hasMore` | `boolean` | Whether additional pages of results are available. | | `events` | `object[]` | Array of `pickableGroupPurchase` event objects matching the query. | --- ### Extract List bulkPurchase ### GET /v2/events/bulkPurchase Returns a paginated list of bulk purchase events. Use the cursor and date parameters to filter or page through results. #### Example request ```bash curl "$BASE_URL/events/bulkPurchase" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `ISO 8601 timestamp` | No | query | Returns only events that occurred on or after this ISO 8601 timestamp. | | `endDate` | `ISO 8601 timestamp` | No | query | Returns only events that occurred on or before this ISO 8601 timestamp. | | `notifiableId` | `uuid` | No | query | Filters events to those associated with the specified notifiable resource UUID. | | `userId` | `uuid` | No | query | Filters events to those associated with the specified user UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Cursor value to pass in the next request to retrieve the following page. | | `pageInfo.total` | `integer` | Total number of events matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of bulk purchase event objects matching the query. | --- ### Extract List failedPurchase ### GET /v2/events/failedPurchase Returns a paginated list of failed purchase events. Filter results by date range, user, or notifiable resource. #### Example request ```bash curl "$BASE_URL/events/failedPurchase" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Pagination cursor returned from a previous response; pass this value to retrieve the next page of results. | | `startDate` | `integer` | No | query | Returns only events at or after this timestamp. Epoch milliseconds. | | `endDate` | `integer` | No | query | Returns only events at or before this timestamp. Epoch milliseconds. | | `notifiableId` | `uuid` | No | query | Filters events by the UUID of the associated notifiable resource (e.g., a course or bundle). | | `userId` | `uuid` | No | query | Filters events to those associated with the specified user UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Opaque cursor string to pass as the `cursor` parameter to retrieve the next page. | | `pageInfo.total` | `integer` | Total number of matching events across all pages. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of failed purchase event objects matching the query criteria. | --- ### Extract List failedSubscription ### GET /v2/events/failedSubscription Retrieves a paginated list of `failedSubscription` events. Each event represents a subscription payment failure. Filter results by date range, user, or notifiable resource. #### Example request ```bash curl "$BASE_URL/events/failedSubscription" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters events by a specific kind or subtype. | | `cursor` | `string` | No | query | Pagination cursor returned from a previous response; fetches the next page of results. | | `startDate` | `ISO 8601 timestamp` | No | query | Returns only events that occurred on or after this ISO 8601 timestamp. | | `endDate` | `ISO 8601 timestamp` | No | query | Returns only events that occurred on or before this ISO 8601 timestamp. | | `notifiableId` | `uuid` | No | query | Filters events to those associated with the specified notifiable resource UUID. | | `userId` | `uuid` | No | query | Filters events to those associated with the specified user UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Opaque cursor value used to retrieve the next page of results. | | `pageInfo.total` | `integer` | Total number of events matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of `failedSubscription` event objects matching the query. | --- ### Extract List subscriptionCancel ### GET /v2/events/subscriptionCancel Retrieves a paginated list of subscription cancellation events. Emitted when a learner cancels a subscription. Use `cursor` and date range filters to page through or narrow results. #### Example request ```bash curl "$BASE_URL/events/subscriptionCancel" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque cursor string returned by a previous response; use to fetch the next page of results. | | `startDate` | `integer` | No | query | Returns only events at or after this timestamp. Epoch milliseconds. | | `endDate` | `integer` | No | query | Returns only events at or before this timestamp. Epoch milliseconds. | | `notifiableId` | `uuid` | No | query | Filters events to those associated with the specified notifiable resource UUID. | | `userId` | `uuid` | No | query | Filters events to those triggered by the specified user UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Opaque cursor to pass as the `cursor` parameter to retrieve the next page. | | `pageInfo.total` | `integer` | Total number of matching events across all pages. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of subscription cancellation event objects for the current page. | --- ### Extract List subscriptionReactivation ### GET /v2/events/subscriptionReactivation Retrieves a paginated list of subscription reactivation events. Each event is emitted when a learner's subscription is reactivated after a lapse or cancellation. Use `cursor` and date range parameters to filter and page through results. #### Example request ```bash curl "$BASE_URL/events/subscriptionReactivation" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters results to a specific event kind. | | `cursor` | `string` | No | query | Opaque cursor value used to fetch the next page of results. | | `startDate` | `integer` | No | query | Returns only events at or after this timestamp. Epoch milliseconds. | | `endDate` | `integer` | No | query | Returns only events at or before this timestamp. Epoch milliseconds. | | `notifiableId` | `uuid` | No | query | Filters results to events associated with a specific notifiable resource, identified by UUID. | | `userId` | `uuid` | No | query | Filters results to events associated with a specific user, identified by UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Cursor value to pass in the next request to retrieve the following page of results. | | `pageInfo.total` | `integer` | Total number of matching events across all pages. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of subscription reactivation event objects matching the query. | --- ### Extract Returns a paginated list of login events. ### GET /v2/events/login #### Example request ```bash curl "$BASE_URL/events/login" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters results by event kind. | | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `integer` | No | query | Returns only events at or after this timestamp. Epoch milliseconds. | | `endDate` | `integer` | No | query | Returns only events at or before this timestamp. Epoch milliseconds. | | `notifiableId` | `uuid` | No | query | Filters results to events associated with this notifiable object's UUID. | | `userId` | `uuid` | No | query | Filters results to events associated with this user's UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Cursor value to pass in the next request to retrieve the following page. | | `pageInfo.total` | `integer` | Total number of login events matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of login event objects matching the query parameters. | --- ### Extract Returns a paginated list of visit events. ### GET /v2/events/visit #### Example request ```bash curl "$BASE_URL/events/visit" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters results to a specific visit event kind. | | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `integer` | No | query | Returns only events at or after this timestamp. Epoch milliseconds. | | `endDate` | `integer` | No | query | Returns only events at or before this timestamp. Epoch milliseconds. | | `notifiableId` | `uuid` | No | query | Filters results to events associated with the specified notifiable resource. | | `userId` | `uuid` | No | query | Filters results to events belonging to the specified user. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Opaque cursor to pass in the next request to retrieve the following page. | | `pageInfo.total` | `integer` | Total number of events matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of visit event objects matching the query filters. | --- ### Extract List registration ### GET /v2/events/registration Returns a paginated list of registration events. Filter results by event kind, date range, notifiable resource, or user. #### Example request ```bash curl "$BASE_URL/events/registration" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters results to a specific registration event type. | | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `integer` | No | query | Returns only events at or after this timestamp. Epoch milliseconds. | | `endDate` | `integer` | No | query | Returns only events at or before this timestamp. Epoch milliseconds. | | `notifiableId` | `uuid` | No | query | Filters results to events associated with a specific notifiable resource, identified by its UUID. | | `userId` | `uuid` | No | query | Filters results to events associated with a specific user, identified by their UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `object` | Cursor-only pagination metadata. | | `pageInfo.cursor` | `string` | Opaque cursor value to pass on the next request. Present only when `hasMore` is `true`. | | `pageInfo.hasMore` | `boolean` | Whether additional pages of results are available. | | `events` | `object[]` | Array of registration event objects matching the query. | --- ### Extract List emailCapture ### GET /v2/events/emailCapture Returns a paginated list of email capture events, optionally filtered by date range, user, or notifiable resource. #### Example request ```bash curl "$BASE_URL/events/emailCapture" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters results to a specific email capture event kind. | | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `integer` | No | query | Returns only events at or after this timestamp. Epoch milliseconds. | | `endDate` | `integer` | No | query | Returns only events at or before this timestamp. Epoch milliseconds. | | `notifiableId` | `uuid` | No | query | Filters results to events associated with the specified notifiable resource. | | `userId` | `uuid` | No | query | Filters results to events associated with the specified user. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Opaque cursor value used to retrieve the next page of results. | | `pageInfo.total` | `integer` | Total number of email capture events matching the request filters. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of email capture event objects matching the request filters. | --- ### Extract List newsletterSignup ### GET /v2/events/newsletterSignup Retrieves a paginated list of `newsletterSignup` events. Emitted when a user signs up for a newsletter. Use the cursor and date parameters to filter or page through results. #### Example request ```bash curl "$BASE_URL/events/newsletterSignup" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters results to a specific event kind. | | `cursor` | `string` | No | query | Opaque cursor string returned by a previous response; used to fetch the next page of results. | | `startDate` | `integer` | No | query | Returns only events at or after this timestamp. Epoch milliseconds. | | `endDate` | `integer` | No | query | Returns only events at or before this timestamp. Epoch milliseconds. | | `notifiableId` | `uuid` | No | query | Filters results to events associated with the specified notifiable object UUID. | | `userId` | `uuid` | No | query | Filters results to events associated with the specified user UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Opaque cursor to pass as the `cursor` parameter to retrieve the next page of results. | | `pageInfo.total` | `integer` | Total number of events matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of `newsletterSignup` event objects matching the query. | --- ### Extract List onboardingSurveyResponse ### GET /v2/events/onboardingSurveyResponse Retrieves a paginated list of onboarding survey response events. Filter results by date range, user, or notifiable resource using the available query parameters. #### Example request ```bash curl "$BASE_URL/events/onboardingSurveyResponse" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `ISO 8601 timestamp` | No | query | Returns only events occurring on or after this ISO 8601 timestamp. | | `endDate` | `ISO 8601 timestamp` | No | query | Returns only events occurring on or before this ISO 8601 timestamp. | | `notifiableId` | `uuid` | No | query | Filters events by the UUID of the associated notifiable resource (e.g., a course or bundle). | | `userId` | `uuid` | No | query | Filters events to those belonging to the specified user UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Cursor value to pass in the next request to retrieve the following page. | | `pageInfo.total` | `integer` | Total number of matching events across all pages. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of onboarding survey response event objects matching the query filters. | --- ### Extract List redemptionCodeRedeem ### GET /v2/events/redemptionCodeRedeem Returns a paginated list of events emitted when a learner redeems a redemption code. #### Example request ```bash curl "$BASE_URL/events/redemptionCodeRedeem" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters results to a specific event kind. | | `cursor` | `string` | No | query | Pagination cursor returned from a previous response; use to fetch the next page of results. | | `startDate` | `ISO 8601 timestamp` | No | query | Returns only events that occurred on or after this ISO 8601 timestamp. | | `endDate` | `ISO 8601 timestamp` | No | query | Returns only events that occurred on or before this ISO 8601 timestamp. | | `notifiableId` | `uuid` | No | query | Filters results to events associated with the specified notifiable resource UUID. | | `userId` | `uuid` | No | query | Filters results to events associated with the specified user UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `object` | Cursor-only pagination metadata. | | `pageInfo.cursor` | `string` | Opaque cursor value to pass on the next request. Present only when `hasMore` is `true`. | | `pageInfo.hasMore` | `boolean` | Whether additional pages of results are available. | | `events` | `object[]` | Array of redemption code redeem event objects. | --- ### Extract List userLastActive7 ### GET /v2/events/userLastActive7 Retrieves a paginated list of `userLastActive7` events, which fire when a user has been inactive for 7 days. Filter results by date range, user, or notifiable resource using the available query parameters. #### Example request ```bash curl "$BASE_URL/events/userLastActive7" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters events by kind identifier. | | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `ISO 8601 timestamp` | No | query | Returns only events occurring on or after this ISO 8601 timestamp. | | `endDate` | `ISO 8601 timestamp` | No | query | Returns only events occurring on or before this ISO 8601 timestamp. | | `notifiableId` | `uuid` | No | query | Filters events by the UUID of the associated notifiable resource. | | `userId` | `uuid` | No | query | Filters events to those belonging to the specified user UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Cursor value to pass in the next request to retrieve the following page. | | `pageInfo.total` | `integer` | Total number of events matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of `userLastActive7` event objects matching the query. | --- ### Extract List userLastActive14 ### GET /v2/events/userLastActive14 Retrieves a paginated list of `userLastActive14` events, each representing a user who has been inactive for 14 days. Filter results by date range, user, or notifiable resource. #### Example request ```bash curl "$BASE_URL/events/userLastActive14" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters events by kind classification. | | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `integer` | No | query | Returns only events at or after this timestamp. Epoch milliseconds. | | `endDate` | `integer` | No | query | Returns only events at or before this timestamp. Epoch milliseconds. | | `notifiableId` | `uuid` | No | query | Filters events by the UUID of the associated notifiable resource. | | `userId` | `uuid` | No | query | Filters events to those belonging to the specified user UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo` | Pagination metadata for the current result set. | | `pageInfo.perPage` | `integer` | Number of events returned per page. | | `pageInfo.cursor` | `string` | Opaque cursor to pass in the next request to retrieve the following page. | | `pageInfo.total` | `integer` | Total number of events matching the query. | | `pageInfo.hasMore` | `boolean` | Indicates whether additional pages of results are available. | | `events` | `object[]` | Array of `userLastActive14` event objects matching the query. | --- ### Extract List userLastActive31 ### GET /v2/events/userLastActive31 Retrieves a paginated list of `userLastActive31` events — emitted when a user has been inactive for 31 days. Filter results by date range, user, or notifiable resource using the available query parameters. #### Example request ```bash curl "$BASE_URL/events/userLastActive31" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters events by kind, narrowing results to a specific event sub-type. | | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results in a paginated response. | | `startDate` | `ISO 8601 timestamp` | No | query | Returns only events occurring on or after this ISO 8601 timestamp. | | `endDate` | `ISO 8601 timestamp` | No | query | Returns only events occurring on or before this ISO 8601 timestamp. | | `notifiableId` | `uuid` | No | query | Filters events to those associated with the specified notifiable resource UUID. | | `userId` | `uuid` | No | query | Filters events to those belonging to the specified user UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `object` | Cursor-only pagination metadata. | | `pageInfo.cursor` | `string` | Opaque cursor value to pass on the next request. Present only when `hasMore` is `true`. | | `pageInfo.hasMore` | `boolean` | Whether additional pages of results are available. | | `events` | `object[]` | Array of `userLastActive31` event objects matching the query. | --- ### Extract List meetingAttendanceUpdate ### GET /v2/events/meetingAttendanceUpdate Returns a paginated list of `meetingAttendanceUpdate` events. Emitted when a learner's attendance record for a meeting is created or updated. #### Example request ```bash curl "$BASE_URL/events/meetingAttendanceUpdate" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `kind` | `string` | No | query | Filters results to a specific event kind. | | `cursor` | `string` | No | query | Opaque cursor string used to fetch the next page of results. | | `startDate` | `ISO 8601 timestamp` | No | query | Returns only events that occurred on or after this ISO 8601 timestamp. | | `endDate` | `ISO 8601 timestamp` | No | query | Returns only events that occurred on or before this ISO 8601 timestamp. | | `notifiableId` | `uuid` | No | query | Filters results to events associated with the specified notifiable resource UUID. | | `userId` | `uuid` | No | query | Filters results to events associated with the specified user UUID. | #### Example response ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ { "id": "...", "userId": "...", "notifiableId": "...", "action": "...", "createdAt": 1739620320000 } ] } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `object` | Cursor-only pagination metadata. | | `pageInfo.cursor` | `string` | Opaque cursor value to pass on the next request. Present only when `hasMore` is `true`. | | `pageInfo.hasMore` | `boolean` | Whether additional pages of results are available. | | `events` | `object[]` | Array of `meetingAttendanceUpdate` event objects matching the query. | --- --- ## Panorama ## Conventions used on this page - **Pagination** uses `cursor` on list endpoints. `page` is not accepted. Responses expose `pageInfo { total, hasMore, cursor }`; `perPage` and `currentPage` are not returned. - **`DELETE /v2/clients/:id`** and **`DELETE /v2/licenses/:id`** return `{ id, deleted: true }`, not the deleted object or a bare identifier. - **Fabricated parameters** — the following appeared in earlier drafts but are not accepted by the controllers: - `enableGlobalLinks` — hardcoded to `false` by the create and update mutations; cannot be set from the request body. - `slug` on client update — the client slug is derived, not settable. - `enableCreditPurchasing` and `enableCustomEmailSettings` on client update — not part of `UpdateClientSettings`. - Query params `clientId` and `count` on the client-list endpoints — not read by the controller. - **Required-array flags** — array body parameters (`courseIds`, `learningPathIds`, `licenseIds`, `courseTagIds`) are optional unless a section explicitly documents otherwise. ### Clients Returns a paginated list of clients, including each client's courses, learning paths, licenses, appearance settings, and feature flags. ### GET /v2/clients #### Example request ```bash curl "$BASE_URL/clients" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `query` | `string` | No | query | Filters clients by a search string matched against client name or other indexed fields. | | `sort` | `string` | No | query | Field by which to sort the results, such as `name` or `createdAt`. | | `page` | `integer` | No | query | Page number to retrieve for paginated results. Defaults to the first page. | | `count` | `integer` | No | query | Number of clients to return per page. | #### Example response ```json { "data": { "Clients": { "pageInfo": { "perPage": 0, "currentPage": 0, "total": 0, "hasMore": true }, "aggregations": [ { "key": "string", "label": "string", "buckets": [ { "query": "string", "value": "string", "label": "string", "description": "string", "count": 0 } ] } ], "clients": [ { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "schoolName": "string", "courses": [ { "id": "00000000-0000-0000-0000-000000000000", "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "slug": "example-slug", "status": "draft", "title": "string", "sessionIsTitled": true, "sampleLesson": { "...": "..." }, "sampleLessonId": "00000000-0000-0000-0000-000000000000", "sections": [ { "...": "..." } ], "appearanceBlock": "00000000-0000-0000-0000-000000000000", "appearanceHash": "string", "paywallsBlock": "00000000-0000-0000-0000-000000000000", "termsBlock": "00000000-0000-0000-0000-000000000000", "confirmationBlock": "00000000-0000-0000-0000-000000000000", "courseStartDate": "2024-01-01T00:00:00.000Z", "courseEndDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "gracePeriodEndDate": "2024-01-01T00:00:00.000Z", "seatsLimit": 0, "waitlistCount": 0, "seatsLimitMet": true, "waitlistingEnabled": true, "waitlistingTriggered": true, "taxable": true, "purchasable": true, "taxCategory": "00000000-0000-0000-0000-000000000000", "fulfillmentCenter": "00000000-0000-0000-0000-000000000000", "courseGroup": { "...": "..." }, "syllabusTitlesEnabled": true, "galleryEnabled": true, "workbookEnabled": true, "linkedWorkbookEnabled": true, "linkedWorkbookSkipEnabled": true, "superquizEnabled": true, "discussionsEnabled": true, "resourcesEnabled": true, "meetingsOverviewEnabled": true, "recurrenceRule": "string", "recurrence": {}, "readableRecurrenceString": "string", "instructorAccessEnabled": true, "instructorEmails": [ "string" ], "freeWithRegistration": true, "priceInCents": 0, "instructorAccessPriceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "showProgress": true, "annotationsEnabled": true, "forceLinearProgress": true, "completionTimePerPage": 0, "askLearnerPassword": true, "roster": true, "topicGroupId": "00000000-0000-0000-0000-000000000000", "sku": "string", "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "...": "..." } ], "parentCourseId": "00000000-0000-0000-0000-000000000000", "isChild": true, "hasMultipleCurrencies": true, "prices": [ { "...": "..." } ], "enrollmentActive": true, "waitlistActive": true, "webinarDates": [ { "...": "..." } ], "webinarTimeZone": "string", "meetings": [ { "...": "..." } ], "inPersonEventInfo": { "...": "..." }, "currentUserHasAccess": true, "location": { "...": "..." }, "displayMap": true, "publishDate": "2024-01-01T00:00:00.000Z", "isActive": true, "futurePublishDate": "2024-01-01T00:00:00.000Z", "balanceRequirement": 0, "awardClaimingOptions": [ { "...": "..." } ], "availabilityStatus": "string", "embeddedEnabled": true, "displayResults": true, "bulkPurchasingEnabled": true, "seatTiers": [ { "...": "..." } ], "seatPackages": [ { "...": "..." } ], "lastTierPriceInCents": 0, "webinarId": "string", "hasChildren": true, "customFields": {}, "kind": "microCourse", "rosterSubmittedDate": "2024-01-01T00:00:00.000Z", "finalAssessment": "string", "scormCollectUserDetails": true, "seatsUsedCount": 0, "seatsAllocatedCount": 0, "webinarDetails": { "...": "..." }, "showSyllabusWhileUnenrolled": true, "topicGroup": { "...": "..." }, "courseGroupTitle": "string", "currentUserDueDate": "2024-01-01T00:00:00.000Z" } ], "learningPaths": [ { "id": "00000000-0000-0000-0000-000000000000", "layoutId": "00000000-0000-0000-0000-000000000000", "layout": { "...": "..." }, "learnerLayoutId": "00000000-0000-0000-0000-000000000000", "learnerLayout": { "...": "..." }, "name": "string", "sku": "string", "ribbon": { "...": "..." }, "slug": "example-slug", "client": { "...": "..." }, "createdAt": "2024-01-01T00:00:00.000Z", "metaTitle": "string", "metaDescription": "string", "asset": "https://example.com/path", "assetAltText": "string", "assetLibraryId": "00000000-0000-0000-0000-000000000000", "detailAsset": "https://example.com/path", "detailAssetAltText": "string", "detailAssetLibraryId": "00000000-0000-0000-0000-000000000000", "heroAsset": "https://example.com/path", "heroAssetAltText": "string", "priceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "shortDescription": "string", "longDescription": "string", "tags": [ { "...": "..." } ], "status": "draft", "freeWithRegistration": true, "hasMultipleCurrencies": true, "prices": [ { "...": "..." } ], "customFields": {}, "contentType": "string", "confirmationHtml": "string", "startDate": "2024-01-01T00:00:00.000Z", "endDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "milestones": [ { "...": "..." } ], "availabilityStatus": "string", "currentUserStartActionDate": "2024-01-01T00:00:00.000Z", "isPayAsYouGo": true, "language": "string", "futurePublishDate": "2024-01-01T00:00:00.000Z", "purchasable": true, "taxable": true, "fulfillmentCenter": { "...": "..." }, "taxCategory": "00000000-0000-0000-0000-000000000000", "currentUserPendingCertificate": true, "currentUserEarnedCertificate": true, "currentUserDueDate": "2024-01-01T00:00:00.000Z", "bulkPurchasingEnabled": true, "seatTiers": [ { "...": "..." } ], "seatPackages": [ { "...": "..." } ], "lastTierPriceInCents": 0, "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "...": "..." } ], "resourceType": "string", "seatsLimit": 0, "videoAsset": "00000000-0000-0000-0000-000000000000", "relatedQuery": "string", "relatedQuerySignature": "string", "source": "string", "authors": [ "string" ], "publishDate": "2024-01-01T00:00:00.000Z", "externalDetailUrl": "string", "externalConditionalRedirectUrl": "string", "kind": "string", "tabs": { "...": "..." }, "updatedAt": "2024-01-01T00:00:00.000Z", "dueDate": "2024-01-01T00:00:00.000Z", "dueDateDays": 0, "allowUnorderedMilestoneAccess": true, "authoringClient": "00000000-0000-0000-0000-000000000000", "isTemplate": true, "externalPurchaseUrl": "string" } ], "learningPathIds": [ "00000000-0000-0000-0000-000000000000" ], "courseIds": [ "00000000-0000-0000-0000-000000000000" ], "courseTags": [ { "id": "00000000-0000-0000-0000-000000000000", "label": "string", "isFolder": true, "updatedAt": "2024-01-01T00:00:00.000Z", "description": "string" } ], "courseTagIds": [ "00000000-0000-0000-0000-000000000000" ], "purchasableContentIds": [ "00000000-0000-0000-0000-000000000000" ], "purchasableTagIds": [ "00000000-0000-0000-0000-000000000000" ], "purchasableTags": [ { "id": "00000000-0000-0000-0000-000000000000", "label": "string", "isFolder": true, "updatedAt": "2024-01-01T00:00:00.000Z", "description": "string" } ], "licenseEndDate": "2024-01-01T00:00:00.000Z", "primaryLicense": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "label": "string", "sku": "string", "client": { "...": "..." }, "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "accessDays": 0, "expirationDate": "2024-01-01T00:00:00.000Z", "seatsLimit": 0, "dashboardLayoutId": "00000000-0000-0000-0000-000000000000", "enableBranding": true, "schoolName": "string", "isDefault": true, "learningPaths": [ { "...": "..." } ], "learningPathIds": [ "00000000-0000-0000-0000-000000000000" ], "appearance": { "...": "..." }, "courses": [ { "...": "..." } ], "courseIds": [ "00000000-0000-0000-0000-000000000000" ], "courseTags": [ { "...": "..." } ], "courseTagIds": [ "00000000-0000-0000-0000-000000000000" ], "parentLicense": { "...": "..." --- ### Show Licenses Returns a paginated list of licenses associated with a client. ### GET /v2/clients/:id/licenses #### Example request ```bash curl "$BASE_URL/clients/:id/licenses" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `string` | Yes | path | The id identifier. | | `clientId` | `uuid` | Yes | query | The UUID of the client whose licenses to retrieve. | | `page` | `integer` | No | query | The page number to return for paginated results. | #### Example response ```json { "data": { "APILicensesByClientId": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "sku": "string", "pageInfo": { "perPage": 0, "currentPage": 0, "total": 0, "hasMore": true }, "licenses": [ { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "label": "string", "sku": "string", "client": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "schoolName": "string", "courses": [ { "...": "..." } ], "learningPaths": [ { "...": "..." } ], "learningPathIds": [ "00000000-0000-0000-0000-000000000000" ], "courseIds": [ "00000000-0000-0000-0000-000000000000" ], "courseTags": [ { "...": "..." } ], "courseTagIds": [ "00000000-0000-0000-0000-000000000000" ], "purchasableContentIds": [ "00000000-0000-0000-0000-000000000000" ], "purchasableTagIds": [ "00000000-0000-0000-0000-000000000000" ], "purchasableTags": [ { "...": "..." } ], "licenseEndDate": "2024-01-01T00:00:00.000Z", "primaryLicense": { "...": "..." }, "dashboardLayoutId": "00000000-0000-0000-0000-000000000000", "panorama": true, "emailLayout": { "...": "..." }, "appearance": { "...": "..." }, "catalog": { "...": "..." }, "sku": "string", "slug": "example-slug", "customHost": "string", "sso": { "...": "..." }, "redemptionLayoutId": "00000000-0000-0000-0000-000000000000", "pendingJobs": [ { "...": "..." } ], "seatsAllocatedCount": 0, "seatsLimit": 0, "seatsUsedCount": 0, "tags": [ { "...": "..." } ], "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "licenses": [ { "...": "..." } ], "licenseTree": [ { "...": "..." } ], "currentUserManagerAllocatedLicenseTree": [ { "...": "..." } ], "notificationEmails": "string", "enableSegmentation": true, "enableDiscussions": true, "enableCommunitiesSegmentation": true, "enableBranding": true, "enableEcommerce": true, "enableOnboardingSurvey": true, "enableRecommendationAssessment": true, "enableGlobalLinks": true, "enableNavLinks": true, "enableContentDetailPage": true, "enableLicenseDashboards": true, "enableCustomEmailSettings": true, "disabled": true, "clientSubscriptionNeedsSetup": true, "defaultLanguage": "string", "languageSelectorEnabled": true, "languages": [ "string" ], "autoFilterForSelectedLanguage": true, "secretKey": "string", "enableCreditPurchasing": true, "currentBalance": 0, "startingBalance": 0, "allocatedCredits": 0, "clientAdminAllocatableLearningPaths": [ "00000000-0000-0000-0000-000000000000" ] }, "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "accessDays": 0, "expirationDate": "2024-01-01T00:00:00.000Z", "seatsLimit": 0, "dashboardLayoutId": "00000000-0000-0000-0000-000000000000", "enableBranding": true, "schoolName": "string", "isDefault": true, "learningPaths": [ { "id": "00000000-0000-0000-0000-000000000000", "layoutId": "00000000-0000-0000-0000-000000000000", "layout": { "...": "..." }, "learnerLayoutId": "00000000-0000-0000-0000-000000000000", "learnerLayout": { "...": "..." }, "name": "string", "sku": "string", "ribbon": { "...": "..." }, "slug": "example-slug", "client": { "...": "..." }, "createdAt": "2024-01-01T00:00:00.000Z", "metaTitle": "string", "metaDescription": "string", "asset": "https://example.com/path", "assetAltText": "string", "assetLibraryId": "00000000-0000-0000-0000-000000000000", "detailAsset": "https://example.com/path", "detailAssetAltText": "string", "detailAssetLibraryId": "00000000-0000-0000-0000-000000000000", "heroAsset": "https://example.com/path", "heroAssetAltText": "string", "priceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "shortDescription": "string", "longDescription": "string", "tags": [ { "...": "..." } ], "status": "draft", "freeWithRegistration": true, "hasMultipleCurrencies": true, "prices": [ { "...": "..." } ], "customFields": {}, "contentType": "string", "confirmationHtml": "string", "startDate": "2024-01-01T00:00:00.000Z", "endDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "milestones": [ { "...": "..." } ], "availabilityStatus": "string", "currentUserStartActionDate": "2024-01-01T00:00:00.000Z", "isPayAsYouGo": true, "language": "string", "futurePublishDate": "2024-01-01T00:00:00.000Z", "purchasable": true, "taxable": true, "fulfillmentCenter": { "...": "..." }, "taxCategory": "00000000-0000-0000-0000-000000000000", "currentUserPendingCertificate": true, "currentUserEarnedCertificate": true, "currentUserDueDate": "2024-01-01T00:00:00.000Z", "bulkPurchasingEnabled": true, "seatTiers": [ { "...": "..." } ], "seatPackages": [ { "...": "..." } ], "lastTierPriceInCents": 0, "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "...": "..." } ], "resourceType": "string", "seatsLimit": 0, "videoAsset": "00000000-0000-0000-0000-000000000000", "relatedQuery": "string", "relatedQuerySignature": "string", "source": "string", "authors": [ "string" ], "publishDate": "2024-01-01T00:00:00.000Z", "externalDetailUrl": "string", "externalConditionalRedirectUrl": "string", "kind": "string", "tabs": { "...": "..." }, "updatedAt": "2024-01-01T00:00:00.000Z", "dueDate": "2024-01-01T00:00:00.000Z", "dueDateDays": 0, "allowUnorderedMilestoneAccess": true, "authoringClient": "00000000-0000-0000-0000-000000000000", "isTemplate": true, "externalPurchaseUrl": "string" } ], "learningPathIds": [ "00000000-0000-0000-0000-000000000000" ], "appearance": { "id": "00000000-0000-0000-0000-000000000000", "company": { "...": "..." }, "backgroundAsset": "string", "backgroundAssetTiled": true, "logoAsset": "https://example.com/path", "retinaLogo": true, "altFont": "string", "font": "string", "accentColor": "#000000", "secondaryColor": "#000000", "linkColor": "#000000", "favicon": "https://example.com/path", "customCss": "string", "globalNavigationLinks": [ { "...": "..." } ] }, "courses": [ { "id": "00000000-0000-0000-0000-000000000000", "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "slug": "example-slug", "status": "draft", "title": "string", "sessionIsTitled": true, "sampleLesson": { "...": "..." }, "sampleLessonId": "00000000-0000-0000-0000-000000000000", "sections": [ { "...": "..." } ], "appearanceBlock": "00000000-0000-0000-0000-000000000000", "appearanceHash": "string", "paywallsBlock": "00000000-0000-0000-0000-000000000000", "termsBlock": "00000000-0000-0000-0000-000000000000", "confirmationBlock": "00000000-0000-0000-0000-000000000000", "courseStartDate": "2024-01-01T00:00:00.000Z", "courseEndDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "gracePeriodEndDate": "2024-01-01T00:00:00.000Z", "seatsLimit": 0, "waitlistCount": 0, "seatsLimitMet": true, "waitlistingEnabled": true, "waitlistingTriggered": true, "taxable": true, "purchasable": true, "taxCategory": "00000000-0000-0000-0000-000000000000", "fulfillmentCenter": "00000000-0000-0000-0000-000000000000", "courseGroup": { "...": "..." }, "syllabusTitlesEnabled": true, "galleryEnabled": true, "workbookEnabled": true, "linkedWorkbookEnabled": true, "linkedWorkbookSkipEnabled": true, "superquizEnabled": true, "discussionsEnabled": true, "resourcesEnabled": true, "meetingsOverviewEnabled": true, "recurrenceRule": "string", "recurrence": {}, "readableRecurrenceString": "string", "instructorAccessEnabled": true, "instructorEmails": [ "string" ], "freeWithRegistration": true, "priceInCents": 0, "instructorAccessPriceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "showProgress": true, "annotationsEnabled": true, "forceLinearProgress": true, "completionTimePerPage": 0, "askLearnerPassword": true, "roster": true, "topicGroupId": "00000000-0000-0000-0000-000000000000", "sku": "string", "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "...": "..." } ], "parentCourseId": "00000000-0000-0000-0000-000000000000", "isChild": true, "hasMultipleCurrencies": true, "prices": [ { "...": "..." } ], "enrollmentActive": true, "waitlistActive": true, "webinarDates": [ --- ### Clients Returns a single client record by ID, including its courses, learning paths, licenses, appearance settings, SSO configuration, and catalog configuration. ### GET /v2/clients/:id #### Example request ```bash curl "$BASE_URL/clients/:id" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The unique identifier of the client to retrieve. | #### Example response ```json { "data": { "ClientById": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "schoolName": "string", "courses": [ { "id": "00000000-0000-0000-0000-000000000000", "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "slug": "example-slug", "status": "draft", "title": "string", "sessionIsTitled": true, "sampleLesson": { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "slug": "example-slug", "accessLevel": "studentsOnly", "section": { "...": "..." }, "slugOrId": "string", "topics": [ null ], "topicOutline": [ { "...": "..." } ] }, "sampleLessonId": "00000000-0000-0000-0000-000000000000", "sections": [ { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "lessons": [ { "...": "..." } ], "slug": "example-slug", "status": "draft", "releaseAt": "2024-01-01T00:00:00.000Z", "parentSectionId": "00000000-0000-0000-0000-000000000000" } ], "appearanceBlock": "00000000-0000-0000-0000-000000000000", "appearanceHash": "string", "paywallsBlock": "00000000-0000-0000-0000-000000000000", "termsBlock": "00000000-0000-0000-0000-000000000000", "confirmationBlock": "00000000-0000-0000-0000-000000000000", "courseStartDate": "2024-01-01T00:00:00.000Z", "courseEndDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "gracePeriodEndDate": "2024-01-01T00:00:00.000Z", "seatsLimit": 0, "waitlistCount": 0, "seatsLimitMet": true, "waitlistingEnabled": true, "waitlistingTriggered": true, "taxable": true, "purchasable": true, "taxCategory": "00000000-0000-0000-0000-000000000000", "fulfillmentCenter": "00000000-0000-0000-0000-000000000000", "courseGroup": { "id": "00000000-0000-0000-0000-000000000000", "authoringClientId": "00000000-0000-0000-0000-000000000000", "createdAt": "2024-01-01T00:00:00.000Z", "displayCourse": { "...": "..." }, "displayBundle": { "...": "..." }, "courses": [ { "...": "..." } ], "asset": "string", "assetAltText": "string", "assetLibraryId": "00000000-0000-0000-0000-000000000000", "detailAsset": "string", "detailAssetAltText": "string", "detailAssetLibraryId": "00000000-0000-0000-0000-000000000000", "videoAsset": "string", "tabs": [ { "...": "..." } ], "description": "string", "title": "string", "archived": true, "metaTitle": "string", "metaDescription": "string", "language": "string", "slug": "example-slug", "rating": 0, "ratingsCount": 0, "relatedCourseGroups": [ { "...": "..." } ], "relatedQuery": "string", "relatedQuerySort": "string", "relatedQueryLimit": 0, "relatedQuerySignature": "string", "relatedShowImageAsset": true, "relatedShowDescription": true, "externalPurchaseUrl": "string", "externalDetailUrl": "string", "layout": { "...": "..." }, "hasParentCourseGroup": true, "tags": [ { "...": "..." } ], "customFields": {}, "authors": [ "string" ], "source": "string", "isbn": "string", "ribbon": "example-slug", "contentType": { "...": "..." }, "kind": "microCourse", "availabilityStatus": "string", "ownedByActiveScope": true, "allowRescheduling": true, "currentUserMayReschedule": true, "isTemplate": true }, "syllabusTitlesEnabled": true, "galleryEnabled": true, "workbookEnabled": true, "linkedWorkbookEnabled": true, "linkedWorkbookSkipEnabled": true, "superquizEnabled": true, "discussionsEnabled": true, "resourcesEnabled": true, "meetingsOverviewEnabled": true, "recurrenceRule": "string", "recurrence": {}, "readableRecurrenceString": "string", "instructorAccessEnabled": true, "instructorEmails": [ "string" ], "freeWithRegistration": true, "priceInCents": 0, "instructorAccessPriceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "showProgress": true, "annotationsEnabled": true, "forceLinearProgress": true, "completionTimePerPage": 0, "askLearnerPassword": true, "roster": true, "topicGroupId": "00000000-0000-0000-0000-000000000000", "sku": "string", "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "id": "00000000-0000-0000-0000-000000000000", "layoutId": "00000000-0000-0000-0000-000000000000", "layout": { "...": "..." }, "learnerLayoutId": "00000000-0000-0000-0000-000000000000", "learnerLayout": { "...": "..." }, "name": "string", "sku": "string", "ribbon": { "...": "..." }, "slug": "example-slug", "client": { "...": "..." }, "createdAt": "2024-01-01T00:00:00.000Z", "metaTitle": "string", "metaDescription": "string", "asset": "https://example.com/path", "assetAltText": "string", "assetLibraryId": "00000000-0000-0000-0000-000000000000", "detailAsset": "https://example.com/path", "detailAssetAltText": "string", "detailAssetLibraryId": "00000000-0000-0000-0000-000000000000", "heroAsset": "https://example.com/path", "heroAssetAltText": "string", "priceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "shortDescription": "string", "longDescription": "string", "tags": [ { "...": "..." } ], "status": "draft", "freeWithRegistration": true, "hasMultipleCurrencies": true, "prices": [ { "...": "..." } ], "customFields": {}, "contentType": "string", "confirmationHtml": "string", "startDate": "2024-01-01T00:00:00.000Z", "endDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "milestones": [ { "...": "..." } ], "availabilityStatus": "string", "currentUserStartActionDate": "2024-01-01T00:00:00.000Z", "isPayAsYouGo": true, "language": "string", "futurePublishDate": "2024-01-01T00:00:00.000Z", "purchasable": true, "taxable": true, "fulfillmentCenter": { "...": "..." }, "taxCategory": "00000000-0000-0000-0000-000000000000", "currentUserPendingCertificate": true, "currentUserEarnedCertificate": true, "currentUserDueDate": "2024-01-01T00:00:00.000Z", "bulkPurchasingEnabled": true, "seatTiers": [ { "...": "..." } ], "seatPackages": [ { "...": "..." } ], "lastTierPriceInCents": 0, "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "...": "..." } ], "resourceType": "string", "seatsLimit": 0, "videoAsset": "00000000-0000-0000-0000-000000000000", "relatedQuery": "string", "relatedQuerySignature": "string", "source": "string", "authors": [ "string" ], "publishDate": "2024-01-01T00:00:00.000Z", "externalDetailUrl": "string", "externalConditionalRedirectUrl": "string", "kind": "string", "tabs": { "...": "..." }, "updatedAt": "2024-01-01T00:00:00.000Z", "dueDate": "2024-01-01T00:00:00.000Z", "dueDateDays": 0, "allowUnorderedMilestoneAccess": true, "authoringClient": "00000000-0000-0000-0000-000000000000", "isTemplate": true, "externalPurchaseUrl": "string" } ], "parentCourseId": "00000000-0000-0000-0000-000000000000", "isChild": true, "hasMultipleCurrencies": true, "prices": [ { "id": "00000000-0000-0000-0000-000000000000", "currencyCode": "string", "unitAmount": 0, "suggestedRetailUnitAmount": 0, "annualUnitAmount": 0, "unitsAmount": [ 0 ], "instructorAccessUnitAmount": 0, "variations": [ { "...": "..." } ], "locale": "string", "isDefault": true, "alternativePricingRef": 0, "alternativePricingType": "membership", "bulkPurchasingEnabled": true, "seatTiers": [ { "...": "..." } ], "seatPackages": [ { "...": "..." } ], "lastTierPriceUnitAmount": 0, "bulkPurchaseTiers": [ { "...": "..." } ] } ], "enrollmentActive": true, "waitlistActive": true, "webinarDates": [ { "startDate": "2024-01-01T00:00:00.000Z", "endDate": "2024-01-01T00:00:00.000Z" } ], "webinarTimeZone": "string", "meetings": [ { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "courseId": "00000000-0000-0000-0000-000000000000", "course": { "...": "..." }, "locationId": "00000000-0000-0000-0000-000000000000", "location": { "...": "..." }, "joinUrl": "string", "attendeeInfo": "string", "instructors": [ "string" ], "startDate": "2024-01-01T00:00:00.000Z", "endDate": "2024-01-01T00:00:00.000Z", "timeZone": "string", "attendees": [ { "...": "..." } ] } ], "inPersonEventInfo": { "subtitle": "string", "heroAsset": "https://example.com/path", "heroAssetAltText": "string" }, "currentUserHasAccess": true, "location": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "room": "string", "address1": "string", "address2": "string --- ### Clients Creates a new client with the specified configuration, including feature flags, language settings, and branding options. Returns the full client object upon success. ### POST /v2/clients #### Example request ```bash curl -X POST "$BASE_URL/clients" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "name": "string", "slug": "string", "enableSegmentation": true, "enableDiscussions": true }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `name` | `string` | Yes | body | Display name for the client. | | `slug` | `string` | Yes | body | URL-safe identifier for the client. | | `enableSegmentation` | `boolean` | Yes | body | Enables learner segmentation for this client. | | `enableDiscussions` | `boolean` | No | body | Enables discussion forums across the client's courses. | | `enableCommunitiesSegmentation` | `boolean` | No | body | Enables segmentation within communities features. | | `enableEcommerce` | `boolean` | Yes | body | Enables e-commerce purchasing for this client. | | `enableRecommendationAssessment` | `boolean` | No | body | Enables the recommendation assessment feature for this client. | | `enableBranding` | `boolean` | Yes | body | Enables custom branding for this client. | | `enableOnboardingSurvey` | `boolean` | Yes | body | Enables the onboarding survey presented to new learners. | | `enableGlobalLinks` | `boolean` | Yes | body | Enables global navigation links in the client interface. | | `enableNavLinks` | `boolean` | Yes | body | Enables the navigation link bar in the client interface. | | `enableContentDetailPage` | `boolean` | No | body | Enables dedicated detail pages for content items. | | `enableLicenseDashboards` | `boolean` | No | body | Enables license-specific dashboard views. | | `enableCreditPurchasing` | `boolean` | No | body | Enables learners to purchase credits within this client. | | `defaultLanguage` | `string` | No | body | Default language for the client interface as a BCP 47 language tag (e.g., `en`). | | `languageSelectorEnabled` | `boolean` | No | body | Enables the language selector in the client interface. | | `autoFilterForSelectedLanguage` | `boolean` | No | body | Automatically filters catalog content to match the learner's selected language. | | `languages` | `string[]` | Yes | body | List of BCP 47 language tags supported by this client (e.g., `["en", "fr"]`). | #### Example response ```json { "data": { "CreateClient": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "schoolName": "string", "courses": [ { "id": "00000000-0000-0000-0000-000000000000", "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "slug": "example-slug", "status": "draft", "title": "string", "sessionIsTitled": true, "sampleLesson": { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "slug": "example-slug", "accessLevel": "studentsOnly", "section": { "...": "..." }, "slugOrId": "string", "topics": [ null ], "topicOutline": [ { "...": "..." } ] }, "sampleLessonId": "00000000-0000-0000-0000-000000000000", "sections": [ { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "lessons": [ { "...": "..." } ], "slug": "example-slug", "status": "draft", "releaseAt": "2024-01-01T00:00:00.000Z", "parentSectionId": "00000000-0000-0000-0000-000000000000" } ], "appearanceBlock": "00000000-0000-0000-0000-000000000000", "appearanceHash": "string", "paywallsBlock": "00000000-0000-0000-0000-000000000000", "termsBlock": "00000000-0000-0000-0000-000000000000", "confirmationBlock": "00000000-0000-0000-0000-000000000000", "courseStartDate": "2024-01-01T00:00:00.000Z", "courseEndDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "gracePeriodEndDate": "2024-01-01T00:00:00.000Z", "seatsLimit": 0, "waitlistCount": 0, "seatsLimitMet": true, "waitlistingEnabled": true, "waitlistingTriggered": true, "taxable": true, "purchasable": true, "taxCategory": "00000000-0000-0000-0000-000000000000", "fulfillmentCenter": "00000000-0000-0000-0000-000000000000", "courseGroup": { "id": "00000000-0000-0000-0000-000000000000", "authoringClientId": "00000000-0000-0000-0000-000000000000", "createdAt": "2024-01-01T00:00:00.000Z", "displayCourse": { "...": "..." }, "displayBundle": { "...": "..." }, "courses": [ { "...": "..." } ], "asset": "string", "assetAltText": "string", "assetLibraryId": "00000000-0000-0000-0000-000000000000", "detailAsset": "string", "detailAssetAltText": "string", "detailAssetLibraryId": "00000000-0000-0000-0000-000000000000", "videoAsset": "string", "tabs": [ { "...": "..." } ], "description": "string", "title": "string", "archived": true, "metaTitle": "string", "metaDescription": "string", "language": "string", "slug": "example-slug", "rating": 0, "ratingsCount": 0, "relatedCourseGroups": [ { "...": "..." } ], "relatedQuery": "string", "relatedQuerySort": "string", "relatedQueryLimit": 0, "relatedQuerySignature": "string", "relatedShowImageAsset": true, "relatedShowDescription": true, "externalPurchaseUrl": "string", "externalDetailUrl": "string", "layout": { "...": "..." }, "hasParentCourseGroup": true, "tags": [ { "...": "..." } ], "customFields": {}, "authors": [ "string" ], "source": "string", "isbn": "string", "ribbon": "example-slug", "contentType": { "...": "..." }, "kind": "microCourse", "availabilityStatus": "string", "ownedByActiveScope": true, "allowRescheduling": true, "currentUserMayReschedule": true, "isTemplate": true }, "syllabusTitlesEnabled": true, "galleryEnabled": true, "workbookEnabled": true, "linkedWorkbookEnabled": true, "linkedWorkbookSkipEnabled": true, "superquizEnabled": true, "discussionsEnabled": true, "resourcesEnabled": true, "meetingsOverviewEnabled": true, "recurrenceRule": "string", "recurrence": {}, "readableRecurrenceString": "string", "instructorAccessEnabled": true, "instructorEmails": [ "string" ], "freeWithRegistration": true, "priceInCents": 0, "instructorAccessPriceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "showProgress": true, "annotationsEnabled": true, "forceLinearProgress": true, "completionTimePerPage": 0, "askLearnerPassword": true, "roster": true, "topicGroupId": "00000000-0000-0000-0000-000000000000", "sku": "string", "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "id": "00000000-0000-0000-0000-000000000000", "layoutId": "00000000-0000-0000-0000-000000000000", "layout": { "...": "..." }, "learnerLayoutId": "00000000-0000-0000-0000-000000000000", "learnerLayout": { "...": "..." }, "name": "string", "sku": "string", "ribbon": { "...": "..." }, "slug": "example-slug", "client": { "...": "..." }, "createdAt": "2024-01-01T00:00:00.000Z", "metaTitle": "string", "metaDescription": "string", "asset": "https://example.com/path", "assetAltText": "string", "assetLibraryId": "00000000-0000-0000-0000-000000000000", "detailAsset": "https://example.com/path", "detailAssetAltText": "string", "detailAssetLibraryId": "00000000-0000-0000-0000-000000000000", "heroAsset": "https://example.com/path", "heroAssetAltText": "string", "priceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "shortDescription": "string", "longDescription": "string", "tags": [ { "...": "..." } ], "status": "draft", "freeWithRegistration": true, "hasMultipleCurrencies": true, "prices": [ { "...": "..." } ], "customFields": {}, "contentType": "string", "confirmationHtml": "string", "startDate": "2024-01-01T00:00:00.000Z", "endDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "milestones": [ { "...": "..." } ], "availabilityStatus": "string", "currentUserStartActionDate": "2024-01-01T00:00:00.000Z", "isPayAsYouGo": true, "language": "string", "futurePublishDate": "2024-01-01T00:00:00.000Z", "purchasable": true, "taxable": true, "fulfillmentCenter": { "...": "..." }, "taxCategory": "00000000-0000-0000-0000-000000000000", "currentUserPendingCertificate": true, "currentUserEarnedCertificate": true, "currentUserDueDate": "2024-01-01T00:00:00.000Z", "bulkPurchasingEnabled": true, "seatTiers": [ { "...": "..." } ], "seatPackages": [ { "...": "..." } ], "lastTierPriceInCents": 0, "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "...": "..." } ], "resourceType": "string", "seatsLimit": 0, "videoAsset": "00000000-0000-0000-0000-000000000000", "relatedQuery": "string", "relatedQuerySignature": "string", "source": "string", "authors": [ "string" ], "publishDate": "2024-01-01T00:00:00.000Z", "externalDetailUrl": "string", "externalConditionalRedirectUrl": "string", "kind": "string", "tabs": { "...": "..." }, "updatedAt": "2024-01-01T00:00:00.000Z", "dueDate": "2024-01-01T00:00:00.000Z", "dueDateDays": 0, "allowUnorderedMilestoneAccess": true, "authoringClient": "00000000-0000-0000-0000-000000000000", "isTemplate": true, "externalPurchaseUrl": "string" } ], "parentCourseId": "00000000-0000-0000-0000-000000000000", "isChild": true, "hasMultipleCurrencies": true, "prices": [ { "id": "00000000-0000-0000-0000-000000000000", "currencyCode": "string", "unitAmount": 0, "suggestedRetailUnitAmount": 0, "annualUnitAmount": 0, "unitsAmount": [ 0 ], "instructorAccessUnitAmount": 0, "variations": [ { "...": "..." } ], "locale": "string", "isDefault": true, "alternativePricingRef": 0, "alternativePricingType": "membership", "bulkP --- ### Update Settings Updates configuration settings for the specified client, including feature flags, localization options, seat limits, and licensing. ### PUT /v2/clients/:id/updateSettings #### Example request ```bash curl -X PUT "$BASE_URL/clients/:id/updateSettings" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "name": "string", "slug": "string", "seatsLimit": 0, "sku": "string" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The id identifier. | | `name` | `string` | Yes | body | Display name of the client. | | `slug` | `string` | No | body | URL-safe identifier for the client. | | `seatsLimit` | `integer` | No | body | Maximum number of seats available for the client; `0` indicates no limit. | | `sku` | `string` | No | body | Stock-keeping unit identifier for the client. | | `tagIds` | `uuid[]` | Yes | body | IDs of the tags to associate with the client. | | `notificationEmails` | `string` | No | body | Comma-separated email addresses that receive client notifications. | | `licenseEndDate` | `ISO 8601 timestamp` | No | body | Date and time when the client's license expires, as an ISO 8601 timestamp. | | `enableSegmentation` | `boolean` | Yes | body | When `true`, enables audience segmentation for the client. | | `enableDiscussions` | `boolean` | No | body | When `true`, enables discussion features across the client's content. | | `enableCommunitiesSegmentation` | `boolean` | No | body | When `true`, enables segmentation within communities. | | `enableRecommendationAssessment` | `boolean` | No | body | When `true`, enables the recommendation assessment feature. | | `enableEcommerce` | `boolean` | Yes | body | When `true`, enables e-commerce purchasing for the client. | | `enableBranding` | `boolean` | Yes | body | When `true`, enables custom branding for the client. | | `enableOnboardingSurvey` | `boolean` | Yes | body | When `true`, displays the onboarding survey to new learners. | | `enableGlobalLinks` | `boolean` | Yes | body | When `true`, enables global navigation links in the client interface. | | `enableNavLinks` | `boolean` | Yes | body | When `true`, enables navigation links in the client interface. | | `defaultLanguage` | `string` | No | body | BCP 47 language code for the client's default display language. | | `languages` | `string[]` | Yes | body | List of BCP 47 language codes supported by the client. | | `languageSelectorEnabled` | `boolean` | No | body | When `true`, shows a language selector to learners. | | `autoFilterForSelectedLanguage` | `boolean` | No | body | When `true`, automatically filters content to match the learner's selected language. | | `enableContentDetailPage` | `boolean` | No | body | When `true`, enables dedicated detail pages for content items. | | `enableLicenseDashboards` | `boolean` | No | body | When `true`, enables license-level dashboards for managers. | | `enableCreditPurchasing` | `boolean` | No | body | When `true`, allows learners to purchase credits within the client. | | `enableCustomEmailSettings` | `boolean` | No | body | When `true`, enables custom email sender settings for the client. | #### Example response ```json { "data": { "UpdateClientSettings": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "schoolName": "string", "courses": [ { "id": "00000000-0000-0000-0000-000000000000", "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "slug": "example-slug", "status": "draft", "title": "string", "sessionIsTitled": true, "sampleLesson": { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "slug": "example-slug", "accessLevel": "studentsOnly", "section": { "...": "..." }, "slugOrId": "string", "topics": [ null ], "topicOutline": [ { "...": "..." } ] }, "sampleLessonId": "00000000-0000-0000-0000-000000000000", "sections": [ { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "lessons": [ { "...": "..." } ], "slug": "example-slug", "status": "draft", "releaseAt": "2024-01-01T00:00:00.000Z", "parentSectionId": "00000000-0000-0000-0000-000000000000" } ], "appearanceBlock": "00000000-0000-0000-0000-000000000000", "appearanceHash": "string", "paywallsBlock": "00000000-0000-0000-0000-000000000000", "termsBlock": "00000000-0000-0000-0000-000000000000", "confirmationBlock": "00000000-0000-0000-0000-000000000000", "courseStartDate": "2024-01-01T00:00:00.000Z", "courseEndDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "gracePeriodEndDate": "2024-01-01T00:00:00.000Z", "seatsLimit": 0, "waitlistCount": 0, "seatsLimitMet": true, "waitlistingEnabled": true, "waitlistingTriggered": true, "taxable": true, "purchasable": true, "taxCategory": "00000000-0000-0000-0000-000000000000", "fulfillmentCenter": "00000000-0000-0000-0000-000000000000", "courseGroup": { "id": "00000000-0000-0000-0000-000000000000", "authoringClientId": "00000000-0000-0000-0000-000000000000", "createdAt": "2024-01-01T00:00:00.000Z", "displayCourse": { "...": "..." }, "displayBundle": { "...": "..." }, "courses": [ { "...": "..." } ], "asset": "string", "assetAltText": "string", "assetLibraryId": "00000000-0000-0000-0000-000000000000", "detailAsset": "string", "detailAssetAltText": "string", "detailAssetLibraryId": "00000000-0000-0000-0000-000000000000", "videoAsset": "string", "tabs": [ { "...": "..." } ], "description": "string", "title": "string", "archived": true, "metaTitle": "string", "metaDescription": "string", "language": "string", "slug": "example-slug", "rating": 0, "ratingsCount": 0, "relatedCourseGroups": [ { "...": "..." } ], "relatedQuery": "string", "relatedQuerySort": "string", "relatedQueryLimit": 0, "relatedQuerySignature": "string", "relatedShowImageAsset": true, "relatedShowDescription": true, "externalPurchaseUrl": "string", "externalDetailUrl": "string", "layout": { "...": "..." }, "hasParentCourseGroup": true, "tags": [ { "...": "..." } ], "customFields": {}, "authors": [ "string" ], "source": "string", "isbn": "string", "ribbon": "example-slug", "contentType": { "...": "..." }, "kind": "microCourse", "availabilityStatus": "string", "ownedByActiveScope": true, "allowRescheduling": true, "currentUserMayReschedule": true, "isTemplate": true }, "syllabusTitlesEnabled": true, "galleryEnabled": true, "workbookEnabled": true, "linkedWorkbookEnabled": true, "linkedWorkbookSkipEnabled": true, "superquizEnabled": true, "discussionsEnabled": true, "resourcesEnabled": true, "meetingsOverviewEnabled": true, "recurrenceRule": "string", "recurrence": {}, "readableRecurrenceString": "string", "instructorAccessEnabled": true, "instructorEmails": [ "string" ], "freeWithRegistration": true, "priceInCents": 0, "instructorAccessPriceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "showProgress": true, "annotationsEnabled": true, "forceLinearProgress": true, "completionTimePerPage": 0, "askLearnerPassword": true, "roster": true, "topicGroupId": "00000000-0000-0000-0000-000000000000", "sku": "string", "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "id": "00000000-0000-0000-0000-000000000000", "layoutId": "00000000-0000-0000-0000-000000000000", "layout": { "...": "..." }, "learnerLayoutId": "00000000-0000-0000-0000-000000000000", "learnerLayout": { "...": "..." }, "name": "string", "sku": "string", "ribbon": { "...": "..." }, "slug": "example-slug", "client": { "...": "..." }, "createdAt": "2024-01-01T00:00:00.000Z", "metaTitle": "string", "metaDescription": "string", "asset": "https://example.com/path", "assetAltText": "string", "assetLibraryId": "00000000-0000-0000-0000-000000000000", "detailAsset": "https://example.com/path", "detailAssetAltText": "string", "detailAssetLibraryId": "00000000-0000-0000-0000-000000000000", "heroAsset": "https://example.com/path", "heroAssetAltText": "string", "priceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "shortDescription": "string", "longDescription": "string", "tags": [ { "...": "..." } ], "status": "draft", "freeWithRegistration": true, "hasMultipleCurrencies": true, "prices": [ { "...": "..." } ], "customFields": {}, "contentType": "string", "confirmationHtml": "string", "startDate": "2024-01-01T00:00:00.000Z", "endDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "milestones": [ { "...": "..." } ], "availabilityStatus": "string", "currentUserStartActionDate": "2024-01-01T00:00:00.000Z", "isPayAsYouGo": true, "language": "string", "futurePublishDate": "2024-01-01T00:00:00.000Z", "purchasable": true, "taxable": true, "fulfillmentCenter": { "...": "..." }, "taxCategory": "00000000-0000-0000-0000-000000000000", "currentUserPendingCertificate": true, "currentUserEarnedCertificate": true, "currentUserDueDate": "2024-01-01T00:00:00.000Z", "bulkPurchasingEnabled": true, "seatTiers": [ { "...": "..." } ], "seatPackages": [ { "...": "..." } ], "lastTierPriceInCents": 0, "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "...": "..." } ], "resourceType": "string", "seatsLimit": 0, "videoAsset": "00000000-0000-0000-0000-000000000000", "relatedQuery": "string", "relatedQuerySignature": "string", "source": "string", "authors": [ "string" ], "publishDate": "2024-01-01T00:00:00.000Z", "externalDetailUrl": "string", "externalConditionalRedirectUrl": "string", "kind": "string", "tabs": { "...": "..." }, "updatedAt": "2024-01-01T00:00:00.000Z", "dueDate": "2024-01-01T00:00:00.000Z", "dueDateDays": 0, "allowUnorderedMilestoneAccess": true, "authoringClient": "00000000-0000-0000-0000-000000000000", "isTemplate": true, "externalPurchaseUrl": "string" } ] } } } } } ``` --- ### Update Access Updates the content access settings for a client, including the courses, learning paths, course tags, and purchasable content the client can access. Returns the updated client object. ### PUT /v2/clients/:id/updateAccess #### Example request ```bash curl -X PUT "$BASE_URL/clients/:id/updateAccess" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "courseIds": "00000000-0000-0000-0000-000000000000", "courseTagIds": "00000000-0000-0000-0000-000000000000", "learningPathIds": "00000000-0000-0000-0000-000000000000", "purchasableContentIds": "00000000-0000-0000-0000-000000000000" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The id identifier. | | `courseIds` | `uuid[]` | Yes | body | IDs of the courses to grant the client access to. | | `courseTagIds` | `uuid[]` | Yes | body | IDs of the course tags used to grant the client access to all courses with matching tags. | | `learningPathIds` | `uuid[]` | Yes | body | IDs of the learning paths to grant the client access to. | | `purchasableContentIds` | `uuid[]` | Yes | body | IDs of the purchasable content items to grant the client access to. | | `purchasableTagIds` | `uuid[]` | Yes | body | IDs of the purchasable content tags used to grant the client access to all matching purchasable items. | | `replaceCourseIds` | `boolean` | No | body | When `true`, replaces the client's existing course access list with the provided `courseIds` instead of appending to it. | | `replaceLearningPathIds` | `boolean` | No | body | When `true`, replaces the client's existing learning path access list with the provided `learningPathIds` instead of appending to it. | | `replaceCourseTagIds` | `boolean` | No | body | When `true`, replaces the client's existing course tag access list with the provided `courseTagIds` instead of appending to it. | | `replacePurchasableContentIds` | `boolean` | No | body | When `true`, replaces the client's existing purchasable content access list with the provided `purchasableContentIds` instead of appending to it. | | `replacePurchasableTagIds` | `boolean` | No | body | When `true`, replaces the client's existing purchasable tag access list with the provided `purchasableTagIds` instead of appending to it. | #### Example response ```json { "data": { "UpdateClientAccess": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "schoolName": "string", "courses": [ { "id": "00000000-0000-0000-0000-000000000000", "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "slug": "example-slug", "status": "draft", "title": "string", "sessionIsTitled": true, "sampleLesson": { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "slug": "example-slug", "accessLevel": "studentsOnly", "section": { "...": "..." }, "slugOrId": "string", "topics": [ null ], "topicOutline": [ { "...": "..." } ] }, "sampleLessonId": "00000000-0000-0000-0000-000000000000", "sections": [ { "id": "00000000-0000-0000-0000-000000000000", "title": "string", "lessons": [ { "...": "..." } ], "slug": "example-slug", "status": "draft", "releaseAt": "2024-01-01T00:00:00.000Z", "parentSectionId": "00000000-0000-0000-0000-000000000000" } ], "appearanceBlock": "00000000-0000-0000-0000-000000000000", "appearanceHash": "string", "paywallsBlock": "00000000-0000-0000-0000-000000000000", "termsBlock": "00000000-0000-0000-0000-000000000000", "confirmationBlock": "00000000-0000-0000-0000-000000000000", "courseStartDate": "2024-01-01T00:00:00.000Z", "courseEndDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "gracePeriodEndDate": "2024-01-01T00:00:00.000Z", "seatsLimit": 0, "waitlistCount": 0, "seatsLimitMet": true, "waitlistingEnabled": true, "waitlistingTriggered": true, "taxable": true, "purchasable": true, "taxCategory": "00000000-0000-0000-0000-000000000000", "fulfillmentCenter": "00000000-0000-0000-0000-000000000000", "courseGroup": { "id": "00000000-0000-0000-0000-000000000000", "authoringClientId": "00000000-0000-0000-0000-000000000000", "createdAt": "2024-01-01T00:00:00.000Z", "displayCourse": { "...": "..." }, "displayBundle": { "...": "..." }, "courses": [ { "...": "..." } ], "asset": "string", "assetAltText": "string", "assetLibraryId": "00000000-0000-0000-0000-000000000000", "detailAsset": "string", "detailAssetAltText": "string", "detailAssetLibraryId": "00000000-0000-0000-0000-000000000000", "videoAsset": "string", "tabs": [ { "...": "..." } ], "description": "string", "title": "string", "archived": true, "metaTitle": "string", "metaDescription": "string", "language": "string", "slug": "example-slug", "rating": 0, "ratingsCount": 0, "relatedCourseGroups": [ { "...": "..." } ], "relatedQuery": "string", "relatedQuerySort": "string", "relatedQueryLimit": 0, "relatedQuerySignature": "string", "relatedShowImageAsset": true, "relatedShowDescription": true, "externalPurchaseUrl": "string", "externalDetailUrl": "string", "layout": { "...": "..." }, "hasParentCourseGroup": true, "tags": [ { "...": "..." } ], "customFields": {}, "authors": [ "string" ], "source": "string", "isbn": "string", "ribbon": "example-slug", "contentType": { "...": "..." }, "kind": "microCourse", "availabilityStatus": "string", "ownedByActiveScope": true, "allowRescheduling": true, "currentUserMayReschedule": true, "isTemplate": true }, "syllabusTitlesEnabled": true, "galleryEnabled": true, "workbookEnabled": true, "linkedWorkbookEnabled": true, "linkedWorkbookSkipEnabled": true, "superquizEnabled": true, "discussionsEnabled": true, "resourcesEnabled": true, "meetingsOverviewEnabled": true, "recurrenceRule": "string", "recurrence": {}, "readableRecurrenceString": "string", "instructorAccessEnabled": true, "instructorEmails": [ "string" ], "freeWithRegistration": true, "priceInCents": 0, "instructorAccessPriceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "showProgress": true, "annotationsEnabled": true, "forceLinearProgress": true, "completionTimePerPage": 0, "askLearnerPassword": true, "roster": true, "topicGroupId": "00000000-0000-0000-0000-000000000000", "sku": "string", "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "id": "00000000-0000-0000-0000-000000000000", "layoutId": "00000000-0000-0000-0000-000000000000", "layout": { "...": "..." }, "learnerLayoutId": "00000000-0000-0000-0000-000000000000", "learnerLayout": { "...": "..." }, "name": "string", "sku": "string", "ribbon": { "...": "..." }, "slug": "example-slug", "client": { "...": "..." }, "createdAt": "2024-01-01T00:00:00.000Z", "metaTitle": "string", "metaDescription": "string", "asset": "https://example.com/path", "assetAltText": "string", "assetLibraryId": "00000000-0000-0000-0000-000000000000", "detailAsset": "https://example.com/path", "detailAssetAltText": "string", "detailAssetLibraryId": "00000000-0000-0000-0000-000000000000", "heroAsset": "https://example.com/path", "heroAssetAltText": "string", "priceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "shortDescription": "string", "longDescription": "string", "tags": [ { "...": "..." } ], "status": "draft", "freeWithRegistration": true, "hasMultipleCurrencies": true, "prices": [ { "...": "..." } ], "customFields": {}, "contentType": "string", "confirmationHtml": "string", "startDate": "2024-01-01T00:00:00.000Z", "endDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "milestones": [ { "...": "..." } ], "availabilityStatus": "string", "currentUserStartActionDate": "2024-01-01T00:00:00.000Z", "isPayAsYouGo": true, "language": "string", "futurePublishDate": "2024-01-01T00:00:00.000Z", "purchasable": true, "taxable": true, "fulfillmentCenter": { "...": "..." }, "taxCategory": "00000000-0000-0000-0000-000000000000", "currentUserPendingCertificate": true, "currentUserEarnedCertificate": true, "currentUserDueDate": "2024-01-01T00:00:00.000Z", "bulkPurchasingEnabled": true, "seatTiers": [ { "...": "..." } ], "seatPackages": [ { "...": "..." } ], "lastTierPriceInCents": 0, "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "...": "..." } ], "resourceType": "string", "seatsLimit": 0, "videoAsset": "00000000-0000-0000-0000-000000000000", "relatedQuery": "string", "relatedQuerySignature": "string", "source": "string", "authors": [ "string" ], "publishDate": "2024-01-01T00:00:00.000Z", "externalDetailUrl": "string", "externalConditionalRedirectUrl": "string", "kind": "string", "tabs": { "...": "..." }, "updatedAt": "2024-01-01T00:00:00.000Z", "dueDate": "2024-01-01T00:00:00.000Z", "dueDateDays": 0, "allowUnorderedMilestoneAccess": true, "authoringClient": "00000000-0000-0000-0000-000000000000", "isTemplate": true, "externalPurchaseUrl": "string" } ], "parentCourseId": "00000000-0000-0000-0000-000000000000", "isChild": true, "hasMultipleCurrencies": true, "prices": [ { "id": "00000000-0000-0000-0000-000000000000", "currencyCode": "string", "unitAmount": 0, "suggestedRetailUnitAmount": 0, "annualUnitAmount": 0, "unitsAmount": [ 0 ], "instructorAccessUnitAmount": 0, "variations": [ { "...": "..." } ], "locale": "string", "isDefault": true, "alternativePricingRef": 0, "alternativePricingType": "membership", " --- ### Update Client Status Sets a client's status to active or disabled. ### PUT /v2/clients/:id/updateStatus #### Example request ```bash curl -X PUT "$BASE_URL/clients/:id/updateStatus" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "status": "string" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | Client UUID. | | `status` | `string` | No | body | Required. 'active' or 'disabled'. A value of 'disabled' disables the client; any other value enables it. | #### Example response ```json { "id": "3f9a2b18-7c4d-4e2a-9b1f-0c5d6e7f8a90", "status": "disabled" } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Client ID. | | `status` | `string` | 'active' or 'disabled'. | --- ### Clients Deletes the specified client by ID. ### DELETE /v2/clients/:id #### Example request ```bash curl -X DELETE "$BASE_URL/clients/:id" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The unique identifier of the client to delete. | #### Example response ```json { "id": "00000000-0000-0000-0000-000000000000", "deleted": true } ``` If the underlying mutation returns no result, the raw GraphQL response is passed through instead. --- ### Licenses Returns a single license by its unique identifier, including full details about the associated client, courses, learning paths, appearance settings, and license hierarchy. ### GET /v2/licenses/:id #### Example request ```bash curl "$BASE_URL/licenses/:id" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | Unique identifier of the license to retrieve. | #### Example response ```json { "data": { "LicenseById": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "label": "string", "sku": "string", "client": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "schoolName": "string", "courses": [ { "id": "00000000-0000-0000-0000-000000000000", "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "slug": "example-slug", "status": "draft", "title": "string", "sessionIsTitled": true, "sampleLesson": { "...": "..." }, "sampleLessonId": "00000000-0000-0000-0000-000000000000", "sections": [ { "...": "..." } ], "appearanceBlock": "00000000-0000-0000-0000-000000000000", "appearanceHash": "string", "paywallsBlock": "00000000-0000-0000-0000-000000000000", "termsBlock": "00000000-0000-0000-0000-000000000000", "confirmationBlock": "00000000-0000-0000-0000-000000000000", "courseStartDate": "2024-01-01T00:00:00.000Z", "courseEndDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "gracePeriodEndDate": "2024-01-01T00:00:00.000Z", "seatsLimit": 0, "waitlistCount": 0, "seatsLimitMet": true, "waitlistingEnabled": true, "waitlistingTriggered": true, "taxable": true, "purchasable": true, "taxCategory": "00000000-0000-0000-0000-000000000000", "fulfillmentCenter": "00000000-0000-0000-0000-000000000000", "courseGroup": { "...": "..." }, "syllabusTitlesEnabled": true, "galleryEnabled": true, "workbookEnabled": true, "linkedWorkbookEnabled": true, "linkedWorkbookSkipEnabled": true, "superquizEnabled": true, "discussionsEnabled": true, "resourcesEnabled": true, "meetingsOverviewEnabled": true, "recurrenceRule": "string", "recurrence": {}, "readableRecurrenceString": "string", "instructorAccessEnabled": true, "instructorEmails": [ "string" ], "freeWithRegistration": true, "priceInCents": 0, "instructorAccessPriceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "showProgress": true, "annotationsEnabled": true, "forceLinearProgress": true, "completionTimePerPage": 0, "askLearnerPassword": true, "roster": true, "topicGroupId": "00000000-0000-0000-0000-000000000000", "sku": "string", "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "...": "..." } ], "parentCourseId": "00000000-0000-0000-0000-000000000000", "isChild": true, "hasMultipleCurrencies": true, "prices": [ { "...": "..." } ], "enrollmentActive": true, "waitlistActive": true, "webinarDates": [ { "...": "..." } ], "webinarTimeZone": "string", "meetings": [ { "...": "..." } ], "inPersonEventInfo": { "...": "..." }, "currentUserHasAccess": true, "location": { "...": "..." }, "displayMap": true, "publishDate": "2024-01-01T00:00:00.000Z", "isActive": true, "futurePublishDate": "2024-01-01T00:00:00.000Z", "balanceRequirement": 0, "awardClaimingOptions": [ { "...": "..." } ], "availabilityStatus": "string", "embeddedEnabled": true, "displayResults": true, "bulkPurchasingEnabled": true, "seatTiers": [ { "...": "..." } ], "seatPackages": [ { "...": "..." } ], "lastTierPriceInCents": 0, "webinarId": "string", "hasChildren": true, "customFields": {}, "kind": "microCourse", "rosterSubmittedDate": "2024-01-01T00:00:00.000Z", "finalAssessment": "string", "scormCollectUserDetails": true, "seatsUsedCount": 0, "seatsAllocatedCount": 0, "webinarDetails": { "...": "..." }, "showSyllabusWhileUnenrolled": true, "topicGroup": { "...": "..." }, "courseGroupTitle": "string", "currentUserDueDate": "2024-01-01T00:00:00.000Z" } ], "learningPaths": [ { "id": "00000000-0000-0000-0000-000000000000", "layoutId": "00000000-0000-0000-0000-000000000000", "layout": { "...": "..." }, "learnerLayoutId": "00000000-0000-0000-0000-000000000000", "learnerLayout": { "...": "..." }, "name": "string", "sku": "string", "ribbon": { "...": "..." }, "slug": "example-slug", "client": { "...": "..." }, "createdAt": "2024-01-01T00:00:00.000Z", "metaTitle": "string", "metaDescription": "string", "asset": "https://example.com/path", "assetAltText": "string", "assetLibraryId": "00000000-0000-0000-0000-000000000000", "detailAsset": "https://example.com/path", "detailAssetAltText": "string", "detailAssetLibraryId": "00000000-0000-0000-0000-000000000000", "heroAsset": "https://example.com/path", "heroAssetAltText": "string", "priceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "shortDescription": "string", "longDescription": "string", "tags": [ { "...": "..." } ], "status": "draft", "freeWithRegistration": true, "hasMultipleCurrencies": true, "prices": [ { "...": "..." } ], "customFields": {}, "contentType": "string", "confirmationHtml": "string", "startDate": "2024-01-01T00:00:00.000Z", "endDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "milestones": [ { "...": "..." } ], "availabilityStatus": "string", "currentUserStartActionDate": "2024-01-01T00:00:00.000Z", "isPayAsYouGo": true, "language": "string", "futurePublishDate": "2024-01-01T00:00:00.000Z", "purchasable": true, "taxable": true, "fulfillmentCenter": { "...": "..." }, "taxCategory": "00000000-0000-0000-0000-000000000000", "currentUserPendingCertificate": true, "currentUserEarnedCertificate": true, "currentUserDueDate": "2024-01-01T00:00:00.000Z", "bulkPurchasingEnabled": true, "seatTiers": [ { "...": "..." } ], "seatPackages": [ { "...": "..." } ], "lastTierPriceInCents": 0, "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "...": "..." } ], "resourceType": "string", "seatsLimit": 0, "videoAsset": "00000000-0000-0000-0000-000000000000", "relatedQuery": "string", "relatedQuerySignature": "string", "source": "string", "authors": [ "string" ], "publishDate": "2024-01-01T00:00:00.000Z", "externalDetailUrl": "string", "externalConditionalRedirectUrl": "string", "kind": "string", "tabs": { "...": "..." }, "updatedAt": "2024-01-01T00:00:00.000Z", "dueDate": "2024-01-01T00:00:00.000Z", "dueDateDays": 0, "allowUnorderedMilestoneAccess": true, "authoringClient": "00000000-0000-0000-0000-000000000000", "isTemplate": true, "externalPurchaseUrl": "string" } ], "learningPathIds": [ "00000000-0000-0000-0000-000000000000" ], "courseIds": [ "00000000-0000-0000-0000-000000000000" ], "courseTags": [ { "id": "00000000-0000-0000-0000-000000000000", "label": "string", "isFolder": true, "updatedAt": "2024-01-01T00:00:00.000Z", "description": "string" } ], "courseTagIds": [ "00000000-0000-0000-0000-000000000000" ], "purchasableContentIds": [ "00000000-0000-0000-0000-000000000000" ], "purchasableTagIds": [ "00000000-0000-0000-0000-000000000000" ], "purchasableTags": [ { "id": "00000000-0000-0000-0000-000000000000", "label": "string", "isFolder": true, "updatedAt": "2024-01-01T00:00:00.000Z", "description": "string" } ], "licenseEndDate": "2024-01-01T00:00:00.000Z", "primaryLicense": { "...": "..." }, "dashboardLayoutId": "00000000-0000-0000-0000-000000000000", "panorama": true, "emailLayout": { "id": "00000000-0000-0000-0000-000000000000", "fromAddress": "string", "fromName": "string", "bccAddress": "string", "header": "string", "footer": "string", "emailCaptureTerms": "string" }, "appearance": { "id": "00000000-0000-0000-0000-000000000000", "company": { "...": "..." }, "backgroundAsset": "string", "backgroundAssetTiled": true, "logoAsset": "https://example.com/path", "retinaLogo": true, "altFont": "string", "font": "string", "accentColor": "#000000", "secondaryColor": "#000000", "linkColor": "#000000", "favicon": "https://example.com/path", "customCss": "string", "globalNavigationLinks": [ { "...": "..." } ] }, "catalog": { "id": "00000000-0000-0000-0000-000000000000", "defaultSort": "string", "defaultQuery": "string", "boostRibbon": "string", "categories": [ { "...": "..." } ], "displayBundle": [ { "...": "..." } ], "displayBundleId": "00000000-0000-0000-0000-000000000000", "resultsDisplayType": "list", "displayTypeListEnabled": true, "displayTypeGridEnabled": true, "displayTypeCalendarEnabled": true, "sortUpdatedAtEnabled": true, "sortCreatedAtEnabled": true, "sortTitleEnabled": true, "sortPublishDateEnabled": true, "sortCourseStartDateEnabled": true, "sortRelevanceEnabled": true, "contentTypeFilterEnabled": true, "displayStartDateEnabled": true, "displayAuth --- ### Show Tree Returns the license tree for the specified license. ### GET /v2/licenses/:id/licenseTree #### Example request ```bash curl "$BASE_URL/licenses/:id/licenseTree" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The unique identifier of the license whose tree to retrieve. | #### Example response ```json { "data": { "LicenseTree": {} } } ``` --- ### Licenses Creates a new license and associates it with the specified client, courses, learning paths, and course tags. ### POST /v2/licenses #### Example request ```bash curl -X POST "$BASE_URL/licenses" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "clientId": "00000000-0000-0000-0000-000000000000", "courseIds": "00000000-0000-0000-0000-000000000000", "learningPathIds": "00000000-0000-0000-0000-000000000000", "courseTagIds": "00000000-0000-0000-0000-000000000000" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `clientId` | `uuid` | Yes | body | ID of the client to associate this license with. | | `courseIds` | `uuid[]` | Yes | body | IDs of the courses to include in this license. | | `learningPathIds` | `uuid[]` | Yes | body | IDs of the learning paths to include in this license. | | `courseTagIds` | `uuid[]` | Yes | body | IDs of the course tags to include in this license. | | `replaceCourseIds` | `boolean` | No | body | When `true`, replaces the client's existing course assignments with the provided `courseIds` instead of appending. | | `replaceLearningPathIds` | `boolean` | No | body | When `true`, replaces the client's existing learning path assignments with the provided `learningPathIds` instead of appending. | | `replaceCourseTagIds` | `boolean` | No | body | When `true`, replaces the client's existing course tag assignments with the provided `courseTagIds` instead of appending. | | `name` | `string` | Yes | body | Display name of the license. | | `label` | `string` | No | body | Short label used to identify the license in listings and reports. | | `sku` | `string` | No | body | Stock-keeping unit identifier for the license. | | `seatsLimit` | `integer` | No | body | Maximum number of seats available under this license; omit for unlimited seats. | | `parentLicenseId` | `uuid` | No | body | ID of the parent license when creating a child license in a hierarchy. | | `accessDays` | `integer` | No | body | Number of days a learner retains access to licensed content after enrollment. | | `expirationDate` | `ISO 8601 timestamp` | No | body | Date and time at which the license expires, in ISO 8601 format. | | `enableBranding` | `boolean` | No | body | When `true`, applies the client's custom branding to the license experience. | | `isDefault` | `boolean` | No | body | When `true`, designates this license as the default for the client. | | `ref1` | `string` | No | body | Custom reference field 1 for storing arbitrary metadata. | | `ref2` | `string` | No | body | Custom reference field 2 for storing arbitrary metadata. | | `ref3` | `string` | No | body | Custom reference field 3 for storing arbitrary metadata. | | `ref4` | `string` | No | body | Custom reference field 4 for storing arbitrary metadata. | | `ref5` | `string` | No | body | Custom reference field 5 for storing arbitrary metadata. | | `ref6` | `string` | No | body | Custom reference field 6 for storing arbitrary metadata. | | `ref7` | `string` | No | body | Custom reference field 7 for storing arbitrary metadata. | | `ref8` | `string` | No | body | Custom reference field 8 for storing arbitrary metadata. | | `ref9` | `string` | No | body | Custom reference field 9 for storing arbitrary metadata. | | `ref10` | `string` | No | body | Custom reference field 10 for storing arbitrary metadata. | #### Example response ```json { "data": { "CreateLicense": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "label": "string", "sku": "string", "client": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "schoolName": "string", "courses": [ { "id": "00000000-0000-0000-0000-000000000000", "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "slug": "example-slug", "status": "draft", "title": "string", "sessionIsTitled": true, "sampleLesson": { "...": "..." }, "sampleLessonId": "00000000-0000-0000-0000-000000000000", "sections": [ { "...": "..." } ], "appearanceBlock": "00000000-0000-0000-0000-000000000000", "appearanceHash": "string", "paywallsBlock": "00000000-0000-0000-0000-000000000000", "termsBlock": "00000000-0000-0000-0000-000000000000", "confirmationBlock": "00000000-0000-0000-0000-000000000000", "courseStartDate": "2024-01-01T00:00:00.000Z", "courseEndDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "gracePeriodEndDate": "2024-01-01T00:00:00.000Z", "seatsLimit": 0, "waitlistCount": 0, "seatsLimitMet": true, "waitlistingEnabled": true, "waitlistingTriggered": true, "taxable": true, "purchasable": true, "taxCategory": "00000000-0000-0000-0000-000000000000", "fulfillmentCenter": "00000000-0000-0000-0000-000000000000", "courseGroup": { "...": "..." }, "syllabusTitlesEnabled": true, "galleryEnabled": true, "workbookEnabled": true, "linkedWorkbookEnabled": true, "linkedWorkbookSkipEnabled": true, "superquizEnabled": true, "discussionsEnabled": true, "resourcesEnabled": true, "meetingsOverviewEnabled": true, "recurrenceRule": "string", "recurrence": {}, "readableRecurrenceString": "string", "instructorAccessEnabled": true, "instructorEmails": [ "string" ], "freeWithRegistration": true, "priceInCents": 0, "instructorAccessPriceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "showProgress": true, "annotationsEnabled": true, "forceLinearProgress": true, "completionTimePerPage": 0, "askLearnerPassword": true, "roster": true, "topicGroupId": "00000000-0000-0000-0000-000000000000", "sku": "string", "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "...": "..." } ], "parentCourseId": "00000000-0000-0000-0000-000000000000", "isChild": true, "hasMultipleCurrencies": true, "prices": [ { "...": "..." } ], "enrollmentActive": true, "waitlistActive": true, "webinarDates": [ { "...": "..." } ], "webinarTimeZone": "string", "meetings": [ { "...": "..." } ], "inPersonEventInfo": { "...": "..." }, "currentUserHasAccess": true, "location": { "...": "..." }, "displayMap": true, "publishDate": "2024-01-01T00:00:00.000Z", "isActive": true, "futurePublishDate": "2024-01-01T00:00:00.000Z", "balanceRequirement": 0, "awardClaimingOptions": [ { "...": "..." } ], "availabilityStatus": "string", "embeddedEnabled": true, "displayResults": true, "bulkPurchasingEnabled": true, "seatTiers": [ { "...": "..." } ], "seatPackages": [ { "...": "..." } ], "lastTierPriceInCents": 0, "webinarId": "string", "hasChildren": true, "customFields": {}, "kind": "microCourse", "rosterSubmittedDate": "2024-01-01T00:00:00.000Z", "finalAssessment": "string", "scormCollectUserDetails": true, "seatsUsedCount": 0, "seatsAllocatedCount": 0, "webinarDetails": { "...": "..." }, "showSyllabusWhileUnenrolled": true, "topicGroup": { "...": "..." }, "courseGroupTitle": "string", "currentUserDueDate": "2024-01-01T00:00:00.000Z" } ], "learningPaths": [ { "id": "00000000-0000-0000-0000-000000000000", "layoutId": "00000000-0000-0000-0000-000000000000", "layout": { "...": "..." }, "learnerLayoutId": "00000000-0000-0000-0000-000000000000", "learnerLayout": { "...": "..." }, "name": "string", "sku": "string", "ribbon": { "...": "..." }, "slug": "example-slug", "client": { "...": "..." }, "createdAt": "2024-01-01T00:00:00.000Z", "metaTitle": "string", "metaDescription": "string", "asset": "https://example.com/path", "assetAltText": "string", "assetLibraryId": "00000000-0000-0000-0000-000000000000", "detailAsset": "https://example.com/path", "detailAssetAltText": "string", "detailAssetLibraryId": "00000000-0000-0000-0000-000000000000", "heroAsset": "https://example.com/path", "heroAssetAltText": "string", "priceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "shortDescription": "string", "longDescription": "string", "tags": [ { "...": "..." } ], "status": "draft", "freeWithRegistration": true, "hasMultipleCurrencies": true, "prices": [ { "...": "..." } ], "customFields": {}, "contentType": "string", "confirmationHtml": "string", "startDate": "2024-01-01T00:00:00.000Z", "endDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "milestones": [ { "...": "..." } ], "availabilityStatus": "string", "currentUserStartActionDate": "2024-01-01T00:00:00.000Z", "isPayAsYouGo": true, "language": "string", "futurePublishDate": "2024-01-01T00:00:00.000Z", "purchasable": true, "taxable": true, "fulfillmentCenter": { "...": "..." }, "taxCategory": "00000000-0000-0000-0000-000000000000", "currentUserPendingCertificate": true, "currentUserEarnedCertificate": true, "currentUserDueDate": "2024-01-01T00:00:00.000Z", "bulkPurchasingEnabled": true, "seatTiers": [ { "...": "..." } ], "seatPackages": [ { "...": "..." } ], "lastTierPriceInCents": 0, "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "...": "..." } ], "resourceType": "string", "seatsLimit": 0, "videoAsset": "00000000-0000-0000-0000-000000000000", "relatedQuery": "string", "relatedQuerySignature": "string", "source": "string", "authors": [ "string" ], "publishDate": "2024-01-01T00:00:00.000Z", "externalDetailUrl": "string", "externalConditionalRedirectUrl": "string", "kind": "string", "tabs": { "...": "..." }, "updatedAt": "2024-01-01T00:00:00.000Z", "dueDate": "2024-01-01T00:00:00.000Z", "dueDateDays": 0, "allowUnorderedMilestoneAccess": true, "authoringClient": "00000000-0000-0000-0000-000000000000", "isTemplate": true, "externalPurchaseUrl": "string" } ], "learningPathIds": [ "00000000-0000-0000-0000-000000000000" ], "courseIds": [ "00000000-0000-0000-0000-000000000000" ], "courseTags": [ { "id": "00000000-0000 --- ### Licenses Updates an existing license by ID, replacing or appending its associated courses, learning paths, and course tags, along with any metadata such as name, seat limits, and expiration date. ### PUT /v2/licenses/:id #### Example request ```bash curl -X PUT "$BASE_URL/licenses/:id" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "courseIds": "00000000-0000-0000-0000-000000000000", "learningPathIds": "00000000-0000-0000-0000-000000000000", "courseTagIds": "00000000-0000-0000-0000-000000000000", "replaceCourseIds": true }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The id identifier. | | `courseIds` | `uuid[]` | Yes | body | IDs of the courses to associate with this license. | | `learningPathIds` | `uuid[]` | Yes | body | IDs of the learning paths to associate with this license. | | `courseTagIds` | `uuid[]` | Yes | body | IDs of the course tags to associate with this license. | | `replaceCourseIds` | `boolean` | No | body | When `true`, replaces the existing course list with the provided `courseIds`; when `false`, appends to the existing list. | | `replaceLearningPathIds` | `boolean` | No | body | When `true`, replaces the existing learning path list with the provided `learningPathIds`; when `false`, appends to the existing list. | | `replaceCourseTagIds` | `boolean` | No | body | When `true`, replaces the existing course tag list with the provided `courseTagIds`; when `false`, appends to the existing list. | | `name` | `string` | Yes | body | Display name of the license. | | `label` | `string` | No | body | Short label used to identify the license in listings and reports. | | `sku` | `string` | No | body | Stock-keeping unit code for the license. | | `seatsLimit` | `integer` | No | body | Maximum number of seats available under this license; omit or set to `0` for unlimited. | | `parentLicenseId` | `uuid` | No | body | ID of the parent license when this license is part of a hierarchy. | | `accessDays` | `integer` | No | body | Number of days a learner retains access to licensed content after enrollment. | | `expirationDate` | `ISO 8601 timestamp` | No | body | Date and time at which the license expires, in ISO 8601 format. | | `enableBranding` | `boolean` | No | body | When `true`, applies the license-level branding overrides to the learner experience. | | `isDefault` | `boolean` | No | body | When `true`, marks this license as the default for its client. | | `ref1` | `string` | No | body | Custom reference field 1 for storing external identifiers or metadata. | | `ref2` | `string` | No | body | Custom reference field 2 for storing external identifiers or metadata. | | `ref3` | `string` | No | body | Custom reference field 3 for storing external identifiers or metadata. | | `ref4` | `string` | No | body | Custom reference field 4 for storing external identifiers or metadata. | | `ref5` | `string` | No | body | Custom reference field 5 for storing external identifiers or metadata. | | `ref6` | `string` | No | body | Custom reference field 6 for storing external identifiers or metadata. | | `ref7` | `string` | No | body | Custom reference field 7 for storing external identifiers or metadata. | | `ref8` | `string` | No | body | Custom reference field 8 for storing external identifiers or metadata. | | `ref9` | `string` | No | body | Custom reference field 9 for storing external identifiers or metadata. | | `ref10` | `string` | No | body | Custom reference field 10 for storing external identifiers or metadata. | #### Example response ```json { "data": { "UpdateLicense": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "label": "string", "sku": "string", "client": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "schoolName": "string", "courses": [ { "id": "00000000-0000-0000-0000-000000000000", "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "slug": "example-slug", "status": "draft", "title": "string", "sessionIsTitled": true, "sampleLesson": { "...": "..." }, "sampleLessonId": "00000000-0000-0000-0000-000000000000", "sections": [ { "...": "..." } ], "appearanceBlock": "00000000-0000-0000-0000-000000000000", "appearanceHash": "string", "paywallsBlock": "00000000-0000-0000-0000-000000000000", "termsBlock": "00000000-0000-0000-0000-000000000000", "confirmationBlock": "00000000-0000-0000-0000-000000000000", "courseStartDate": "2024-01-01T00:00:00.000Z", "courseEndDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "gracePeriodEndDate": "2024-01-01T00:00:00.000Z", "seatsLimit": 0, "waitlistCount": 0, "seatsLimitMet": true, "waitlistingEnabled": true, "waitlistingTriggered": true, "taxable": true, "purchasable": true, "taxCategory": "00000000-0000-0000-0000-000000000000", "fulfillmentCenter": "00000000-0000-0000-0000-000000000000", "courseGroup": { "...": "..." }, "syllabusTitlesEnabled": true, "galleryEnabled": true, "workbookEnabled": true, "linkedWorkbookEnabled": true, "linkedWorkbookSkipEnabled": true, "superquizEnabled": true, "discussionsEnabled": true, "resourcesEnabled": true, "meetingsOverviewEnabled": true, "recurrenceRule": "string", "recurrence": {}, "readableRecurrenceString": "string", "instructorAccessEnabled": true, "instructorEmails": [ "string" ], "freeWithRegistration": true, "priceInCents": 0, "instructorAccessPriceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "showProgress": true, "annotationsEnabled": true, "forceLinearProgress": true, "completionTimePerPage": 0, "askLearnerPassword": true, "roster": true, "topicGroupId": "00000000-0000-0000-0000-000000000000", "sku": "string", "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "...": "..." } ], "parentCourseId": "00000000-0000-0000-0000-000000000000", "isChild": true, "hasMultipleCurrencies": true, "prices": [ { "...": "..." } ], "enrollmentActive": true, "waitlistActive": true, "webinarDates": [ { "...": "..." } ], "webinarTimeZone": "string", "meetings": [ { "...": "..." } ], "inPersonEventInfo": { "...": "..." }, "currentUserHasAccess": true, "location": { "...": "..." }, "displayMap": true, "publishDate": "2024-01-01T00:00:00.000Z", "isActive": true, "futurePublishDate": "2024-01-01T00:00:00.000Z", "balanceRequirement": 0, "awardClaimingOptions": [ { "...": "..." } ], "availabilityStatus": "string", "embeddedEnabled": true, "displayResults": true, "bulkPurchasingEnabled": true, "seatTiers": [ { "...": "..." } ], "seatPackages": [ { "...": "..." } ], "lastTierPriceInCents": 0, "webinarId": "string", "hasChildren": true, "customFields": {}, "kind": "microCourse", "rosterSubmittedDate": "2024-01-01T00:00:00.000Z", "finalAssessment": "string", "scormCollectUserDetails": true, "seatsUsedCount": 0, "seatsAllocatedCount": 0, "webinarDetails": { "...": "..." }, "showSyllabusWhileUnenrolled": true, "topicGroup": { "...": "..." }, "courseGroupTitle": "string", "currentUserDueDate": "2024-01-01T00:00:00.000Z" } ], "learningPaths": [ { "id": "00000000-0000-0000-0000-000000000000", "layoutId": "00000000-0000-0000-0000-000000000000", "layout": { "...": "..." }, "learnerLayoutId": "00000000-0000-0000-0000-000000000000", "learnerLayout": { "...": "..." }, "name": "string", "sku": "string", "ribbon": { "...": "..." }, "slug": "example-slug", "client": { "...": "..." }, "createdAt": "2024-01-01T00:00:00.000Z", "metaTitle": "string", "metaDescription": "string", "asset": "https://example.com/path", "assetAltText": "string", "assetLibraryId": "00000000-0000-0000-0000-000000000000", "detailAsset": "https://example.com/path", "detailAssetAltText": "string", "detailAssetLibraryId": "00000000-0000-0000-0000-000000000000", "heroAsset": "https://example.com/path", "heroAssetAltText": "string", "priceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "shortDescription": "string", "longDescription": "string", "tags": [ { "...": "..." } ], "status": "draft", "freeWithRegistration": true, "hasMultipleCurrencies": true, "prices": [ { "...": "..." } ], "customFields": {}, "contentType": "string", "confirmationHtml": "string", "startDate": "2024-01-01T00:00:00.000Z", "endDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "milestones": [ { "...": "..." } ], "availabilityStatus": "string", "currentUserStartActionDate": "2024-01-01T00:00:00.000Z", "isPayAsYouGo": true, "language": "string", "futurePublishDate": "2024-01-01T00:00:00.000Z", "purchasable": true, "taxable": true, "fulfillmentCenter": { "...": "..." }, "taxCategory": "00000000-0000-0000-0000-000000000000", "currentUserPendingCertificate": true, "currentUserEarnedCertificate": true, "currentUserDueDate": "2024-01-01T00:00:00.000Z", "bulkPurchasingEnabled": true, "seatTiers": [ { "...": "..." } ], "seatPackages": [ { "...": "..." } ], "lastTierPriceInCents": 0, "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "...": "..." } ], "resourceType": "string", "seatsLimit": 0, "videoAsset": "00000000-0000-0000-0000-000000000000", "relatedQuery": "string", "relatedQuerySignature": "string", "source": "string", "authors": [ "string" ], "publishDate": "2024-01-01T00:00:00.000Z", "externalDetailUrl": "string", "externalConditionalRedirectUrl": "string", "kind": "string", "tabs": { "...": "..." }, "updatedAt": "2024-01-01T00:00:00.000Z", "dueDate": "2024-01-01T00:00:00.000Z", "dueDateDays": 0, "allowUnorderedMilestoneAccess": true, "authoringClient": "00000000-0000-0000-0000-000000000000", "isTemplate": true, "externalPurchaseUrl": "string" } ], "learningPathIds": [ "00000000-0000-0000-0000-000000000000" ], " --- ### Licenses Deletes the specified license and returns the deleted license object with its full associated data. ### DELETE /v2/licenses/:id #### Example request ```bash curl -X DELETE "$BASE_URL/licenses/:id" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | Unique identifier of the license to delete. | #### Example response ```json { "id": "00000000-0000-0000-0000-000000000000", "deleted": true } ``` If the underlying mutation returns no result, the raw GraphQL response is passed through instead. --- --- ## System ### Jobs List jobs ### GET /v2/jobs/:id Returns the details of a background job by its unique identifier. #### Example request ```bash curl "$BASE_URL/jobs/:id" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The unique identifier of the background job to retrieve. | #### Example response ```json { "data": { "APIBackgroundJob": { "id": "00000000-0000-0000-0000-000000000000", "description": "string", "errorMessage": "string", "status": "complete" } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Unique identifier of the background job. | | `description` | `string` | Human-readable summary of what the job does. | | `errorMessage` | `string` | Error details if the job failed; empty or absent when the job succeeded. | | `status` | `BackgroundJobStatus` | Current execution state of the job (e.g., `complete`). | --- ### Learning Path Milestones And Courses Returns a list of learning path milestones and their associated courses. ### GET /v2/learningPathMilestones #### Example request ```bash curl "$BASE_URL/learningPathMilestones" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `learningPathId` | `string` | No | query | JSON-array-formatted string of learning path UUIDs (e.g. `["00000000-0000-0000-0000-000000000000"]`). At least one of `learningPathId` or `learningPathSku` is required. | | `learningPathSku` | `string` | No | query | JSON-array-formatted string of learning path SKUs. At least one of `learningPathId` or `learningPathSku` is required. | The combined number of IDs and SKUs across both parameters cannot exceed 100. #### Example response ```json { "data": { "LearningPathMilestonesAndCourses": {} } } ``` --- ### Health Check Confirms the API is reachable and the API key is valid. ### GET /v2/ping #### Example request ```bash curl "$BASE_URL/ping" \ -H 'Authorization: Bearer APIKEY' ``` #### Example response ```json { "valid": true } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `valid` | `boolean` | True when the API key is valid. | --- --- ## Users ## Conventions used on this page - **Pagination** uses `cursor` on list endpoints. `page` is not accepted. Responses expose `pageInfo { total, hasMore, cursor }` — no `perPage`, no `currentPage`, no `aggregations`. - **Response envelope** is unwrapped: list endpoints return `{ pageInfo, users: [...] }` directly. There is no `{ data: { APIUsers: … } }` wrapper. - **User objects** are shaped by the controller's `UserFragment`. Fields not listed in that fragment (for example `abbreviatedName`, `name`, `bio`, `twoFactorEnabled`, `firstInitial`, `invitedByName`, `shouldHighlight`, and the deeply-nested `activeLicense` / `purchasedCourses[].course` objects) are not returned. See `incoming/controllers/usersv2.ts` for the authoritative field list. - **Array body parameters** on create / update / bulk endpoints (`courseIds`, `learningPathIds`, `licenseIds`, `bundleIds`) are optional unless a section explicitly notes otherwise — earlier drafts often marked them "Required: Yes" incorrectly. ### Users Returns a paginated list of users, optionally filtered by role. ### GET /v2/users #### Example request ```bash curl "$BASE_URL/users" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `role` | `string` | No | query | Filters by role key. Defaults to `student` when omitted. | | `cursor` | `string` | No | query | Opaque pagination cursor from a previous response. | #### Example response ```json { "pageInfo": { "total": 142, "hasMore": true, "cursor": "eyJwYWdlIjoyfQ==" }, "users": [ { "id": "...", "email": "jenna.park@acme.com", "firstName": "Jenna", "lastName": "Park", "roleKey": "student" } ] } ``` --- ### Users Returns the full profile for a single user, including purchased courses and bundles, license assignments, learning path allocations, meeting attendance, and client configuration. ### GET /v2/users/:id #### Example request ```bash curl "$BASE_URL/users/:id" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | Unique identifier of the user to retrieve. | #### Example response ```json { "data": { "UserById": { "id": "00000000-0000-0000-0000-000000000000", "email": "string", "firstName": "string", "abbreviatedName": "string", "asset": "string", "lastName": "string", "name": "string", "lastActiveAt": "2024-01-01T00:00:00.000Z", "bio": "string", "twoFactorEnabled": true, "firstInitial": "string", "lastInitial": "string", "invitedByName": "string", "shouldHighlight": true, "purchasedCourses": [ { "course": { "id": "00000000-0000-0000-0000-000000000000", "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "slug": "example-slug", "status": "draft", "title": "string", "sessionIsTitled": true, "sampleLesson": { "...": "..." }, "sampleLessonId": "00000000-0000-0000-0000-000000000000", "sections": [ { "...": "..." } ], "appearanceBlock": "00000000-0000-0000-0000-000000000000", "appearanceHash": "string", "paywallsBlock": "00000000-0000-0000-0000-000000000000", "termsBlock": "00000000-0000-0000-0000-000000000000", "confirmationBlock": "00000000-0000-0000-0000-000000000000", "courseStartDate": "2024-01-01T00:00:00.000Z", "courseEndDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "gracePeriodEndDate": "2024-01-01T00:00:00.000Z", "seatsLimit": 0, "waitlistCount": 0, "seatsLimitMet": true, "waitlistingEnabled": true, "waitlistingTriggered": true, "taxable": true, "purchasable": true, "taxCategory": "00000000-0000-0000-0000-000000000000", "fulfillmentCenter": "00000000-0000-0000-0000-000000000000", "courseGroup": { "...": "..." }, "syllabusTitlesEnabled": true, "galleryEnabled": true, "workbookEnabled": true, "linkedWorkbookEnabled": true, "linkedWorkbookSkipEnabled": true, "superquizEnabled": true, "discussionsEnabled": true, "resourcesEnabled": true, "meetingsOverviewEnabled": true, "recurrenceRule": "string", "recurrence": {}, "readableRecurrenceString": "string", "instructorAccessEnabled": true, "instructorEmails": [ "string" ], "freeWithRegistration": true, "priceInCents": 0, "instructorAccessPriceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "showProgress": true, "annotationsEnabled": true, "forceLinearProgress": true, "completionTimePerPage": 0, "askLearnerPassword": true, "roster": true, "topicGroupId": "00000000-0000-0000-0000-000000000000", "sku": "string", "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "...": "..." } ], "parentCourseId": "00000000-0000-0000-0000-000000000000", "isChild": true, "hasMultipleCurrencies": true, "prices": [ { "...": "..." } ], "enrollmentActive": true, "waitlistActive": true, "webinarDates": [ { "...": "..." } ], "webinarTimeZone": "string", "meetings": [ { "...": "..." } ], "inPersonEventInfo": { "...": "..." }, "currentUserHasAccess": true, "location": { "...": "..." }, "displayMap": true, "publishDate": "2024-01-01T00:00:00.000Z", "isActive": true, "futurePublishDate": "2024-01-01T00:00:00.000Z", "balanceRequirement": 0, "awardClaimingOptions": [ { "...": "..." } ], "availabilityStatus": "string", "embeddedEnabled": true, "displayResults": true, "bulkPurchasingEnabled": true, "seatTiers": [ { "...": "..." } ], "seatPackages": [ { "...": "..." } ], "lastTierPriceInCents": 0, "webinarId": "string", "hasChildren": true, "customFields": {}, "kind": "microCourse", "rosterSubmittedDate": "2024-01-01T00:00:00.000Z", "finalAssessment": "string", "scormCollectUserDetails": true, "seatsUsedCount": 0, "seatsAllocatedCount": 0, "webinarDetails": { "...": "..." }, "showSyllabusWhileUnenrolled": true, "topicGroup": { "...": "..." }, "courseGroupTitle": "string", "currentUserDueDate": "2024-01-01T00:00:00.000Z" }, "courseId": "00000000-0000-0000-0000-000000000000", "certificate": "00000000-0000-0000-0000-000000000000", "certificateIssuedAt": "2024-01-01T00:00:00.000Z", "instructorAccessPurchased": true, "status": "string" } ], "purchasedBundles": [ { "bundle": { "id": "00000000-0000-0000-0000-000000000000", "slug": "example-slug", "name": "string", "purchasable": true, "planId": "00000000-0000-0000-0000-000000000000", "variations": [ { "...": "..." } ], "taxable": true, "needShippingAddress": true, "priceInCents": 0, "weight": 0, "annualPlanId": "00000000-0000-0000-0000-000000000000", "annualPriceInCents": 0 }, "bundleId": "00000000-0000-0000-0000-000000000000" } ], "activeLicense": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "label": "string", "sku": "string", "client": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "schoolName": "string", "courses": [ { "...": "..." } ], "learningPaths": [ { "...": "..." } ], "learningPathIds": [ "00000000-0000-0000-0000-000000000000" ], "courseIds": [ "00000000-0000-0000-0000-000000000000" ], "courseTags": [ { "...": "..." } ], "courseTagIds": [ "00000000-0000-0000-0000-000000000000" ], "purchasableContentIds": [ "00000000-0000-0000-0000-000000000000" ], "purchasableTagIds": [ "00000000-0000-0000-0000-000000000000" ], "purchasableTags": [ { "...": "..." } ], "licenseEndDate": "2024-01-01T00:00:00.000Z", "primaryLicense": { "...": "..." }, "dashboardLayoutId": "00000000-0000-0000-0000-000000000000", "panorama": true, "emailLayout": { "...": "..." }, "appearance": { "...": "..." }, "catalog": { "...": "..." }, "sku": "string", "slug": "example-slug", "customHost": "string", "sso": { "...": "..." }, "redemptionLayoutId": "00000000-0000-0000-0000-000000000000", "pendingJobs": [ { "...": "..." } ], "seatsAllocatedCount": 0, "seatsLimit": 0, "seatsUsedCount": 0, "tags": [ { "...": "..." } ], "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "licenses": [ { "...": "..." } ], "licenseTree": [ { "...": "..." } ], "currentUserManagerAllocatedLicenseTree": [ { "...": "..." } ], "notificationEmails": "string", "enableSegmentation": true, "enableDiscussions": true, "enableCommunitiesSegmentation": true, "enableBranding": true, "enableEcommerce": true, "enableOnboardingSurvey": true, "enableRecommendationAssessment": true, "enableGlobalLinks": true, "enableNavLinks": true, "enableContentDetailPage": true, "enableLicenseDashboards": true, "enableCustomEmailSettings": true, "disabled": true, "clientSubscriptionNeedsSetup": true, "defaultLanguage": "string", "languageSelectorEnabled": true, "languages": [ "string" ], "autoFilterForSelectedLanguage": true, "secretKey": "string", "enableCreditPurchasing": true, "currentBalance": 0, "startingBalance": 0, "allocatedCredits": 0, "clientAdminAllocatableLearningPaths": [ "00000000-0000-0000-0000-000000000000" ] }, "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "accessDays": 0, "expirationDate": "2024-01-01T00:00:00.000Z", "seatsLimit": 0, "dashboardLayoutId": "00000000-0000-0000-0000-000000000000", "enableBranding": true, "schoolName": "string", "isDefault": true, "learningPaths": [ { "id": "00000000-0000-0000-0000-000000000000", "layoutId": "00000000-0000-0000-0000-000000000000", "layout": { "...": "..." }, "learnerLayoutId": "00000000-0000-0000-0000-000000000000", "learnerLayout": { "...": "..." }, "name": "string", "sku": "string", "ribbon": { "...": "..." }, "slug": "example-slug", "client": { "...": "..." }, "createdAt": "2024-01-01T00:00:00.000Z", "metaTitle": "string", "metaDescription": "string", "asset": "https://example.com/path", "assetAltText": "string", "assetLibraryId": "00000000-0000-0000-0000-000000000000", "detailAsset": "https://example.com/path", "detailAssetAltText": "string", "detailAssetLibraryId": "00000000-0000-0000-0000-000000000000", "heroAsset": "https://example.com/path", "heroAssetAltText": "string", "priceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "shortDescription": "string", "longDescription": "string", "tags": [ { "...": "..." } ], "status": "draft", "freeWithRegistration": true, "hasMultipleCurrencies": true, "prices": [ { "...": "..." } ], "customFields": {}, "contentType": "string", "confirmationHtml": "string", "startDate": "2024-01-01T00:00:00.000Z", "endDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z" } } ] } } } ``` --- ### Zendesk Content Returns Zendesk content items associated with a specific user, including enrollment status, completion percentage, and any issued certificates. ### GET /v2/users/:id/zendeskContent #### Example request ```bash curl "$BASE_URL/users/:id/zendeskContent" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `string` | Yes | path | The id identifier. | | `userId` | `uuid` | Yes | query | The UUID of the user whose Zendesk content to retrieve. | #### Example response ```json { "data": { "ZendeskContentsByUserId": [ { "id": "00000000-0000-0000-0000-000000000000", "userId": "00000000-0000-0000-0000-000000000000", "title": "string", "kind": "string", "startDate": "2024-01-01T00:00:00.000Z", "endDate": "2024-01-01T00:00:00.000Z", "certificate": { "id": "00000000-0000-0000-0000-000000000000", "certificateTemplate": { "id": "00000000-0000-0000-0000-000000000000", "resource": null, "resourceType": "course", "title": "string", "asset": "https://example.com/path", "supplementalAssets": [ "https://example.com/path" ], "expirationDate": "2024-01-01T00:00:00.000Z", "expirationDays": 0, "recertificationAction": "resetProgress", "recertificationInstructions": "string", "recertificationActionText": "string", "recertificationActionUrl": "https://example.com/path", "recertificationAutoReset": true, "recertificationAfterDays": 0, "userCustomField": { "...": "..." }, "userCustomFieldSlug": "example-slug", "userCustomFieldValue": "string", "customCss": "string", "labels": [ { "...": "..." } ], "isExpired": true, "assetLibraryId": "00000000-0000-0000-0000-000000000000" }, "user": { "id": "00000000-0000-0000-0000-000000000000", "email": "string", "firstName": "string", "abbreviatedName": "string", "asset": "string", "lastName": "string", "name": "string", "lastActiveAt": "2024-01-01T00:00:00.000Z", "bio": "string", "twoFactorEnabled": true, "firstInitial": "string", "lastInitial": "string", "invitedByName": "string", "shouldHighlight": true, "purchasedCourses": [ { "...": "..." } ], "purchasedBundles": [ { "...": "..." } ], "activeLicense": { "...": "..." }, "allocatedLicenses": [ { "...": "..." } ], "allocatedLearningPaths": [ { "...": "..." } ], "attendedMeetings": [ { "...": "..." } ], "waitlistedCourses": [ { "...": "..." } ], "adminClients": [ { "...": "..." } ], "roleKey": "string", "address1": "string", "address2": "string", "city": "string", "state": "string", "zipCode": "string", "country": "string", "telephone": "string", "stripeCustomerId": "00000000-0000-0000-0000-000000000000", "externalCustomerId": "00000000-0000-0000-0000-000000000000", "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "sfAccountId": "00000000-0000-0000-0000-000000000000", "sfContactId": "00000000-0000-0000-0000-000000000000", "shippingName": "string", "client": { "...": "..." }, "clientId": "00000000-0000-0000-0000-000000000000", "ref1": "string", "ref2": "string", "ref3": "string", "ref4": "string", "ref5": "string", "ref6": "string", "ref7": "string", "ref8": "string", "ref9": "string", "ref10": "string", "lang": "string", "preferredCurrency": "string", "customFields": {}, "recommendedTags": [ { "...": "..." } ], "recommendedSlugs": [ { "...": "..." } ], "accessedFlows": [ "string" ], "disabled": true, "learnerUserId": "00000000-0000-0000-0000-000000000000", "managerUserId": "00000000-0000-0000-0000-000000000000", "balance": 0, "mustVerifyEmail": true, "certificatesCount": 0, "collaborationsCount": 0, "availableCoursesCount": 0, "startedCoursesCount": 0, "completedCoursesCount": 0 }, "issuedAt": "2024-01-01T00:00:00.000Z", "url": "https://example.com/path", "pdfAsset": "https://example.com/path", "resource": null, "resourceType": "course", "resourceTypeLabel": "string", "resourceId": "00000000-0000-0000-0000-000000000000", "progressWasReset": true, "expirationDate": "2024-01-01T00:00:00.000Z", "didExpire": true, "deleted": true, "recertificationDate": "2024-01-01T00:00:00.000Z", "isExpired": true, "source": "string", "contentItem": { "id": "00000000-0000-0000-0000-000000000000", "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "expiresAt": "2024-01-01T00:00:00.000Z", "courseGroup": "string", "title": "string", "sessionTitle": "string", "ribbon": { "...": "..." }, "slug": "example-slug", "kind": "courseGroup", "metaTitle": "string", "language": "string", "metaDescription": "string", "displayCourse": "00000000-0000-0000-0000-000000000000", "displayCourseSlug": "example-slug", "displayDate": "2024-01-01T00:00:00.000Z", "availabilityStatus": "string", "asset": "string", "assetAltText": "string", "freeWithRegistration": true, "priceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "rating": 0, "description": "string", "sku": "string", "hasChildren": true, "tags": [ { "...": "..." } ], "status": "draft", "customFields": {}, "authors": [ "string" ], "authorsAndInstructors": [ "string" ], "contentTypeLabel": "string", "contentTypeAssetAspectRatio": "string", "waitlistCount": 0, "seatsLimit": 0, "enrollmentCount": 0, "source": "string", "publishDate": "2024-01-01T00:00:00.000Z", "courseStartDate": "2024-01-01T00:00:00.000Z", "courseEndDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "timeZone": "string", "meetingStartDate": "2024-01-01T00:00:00.000Z", "coursePresold": true, "courseGracePeriodEnded": true, "currentUserMayReschedule": true, "isActive": true, "embeddedEnabled": true, "altDescriptionBody": "string", "hideCourseDescription": true, "annotationsEnabled": true, "currentUserUnmetCoursePrerequisites": [ "00000000-0000-0000-0000-000000000000" ], "currentUserUnmetLearningPathPrerequisites": [ "00000000-0000-0000-0000-000000000000" ], "currentUserDueDate": "2024-01-01T00:00:00.000Z", "url": "https://example.com/path", "waitlistingEnabled": true, "waitlistingTriggered": true, "canAddToQueue": true, "bulkPurchasingEnabled": true, "prices": [ { "...": "..." } ], "location": { "...": "..." }, "state": "string", "acceptBadgeUrl": "string", "issuedAt": "2024-01-01T00:00:00.000Z", "badgeUrl": "string", "credlyBadgeExpiresAt": "2024-01-01T00:00:00.000Z", "imageUrl": "string", "badgeName": "string", "completedPassedDueDate": true }, "isExternal": true, "externalResourceTitle": "string", "identifier": "string" }, "percentComplete": 0, "status": "string" } ] } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `[].id` | `uuid` | Unique identifier for the Zendesk content item. | | `[].userId` | `uuid` | UUID of the user associated with this content item. | | `[].title` | `string` | Display title of the content item. | | `[].kind` | `string` | The content type classification (e.g., course, learning path). | | `[].startDate` | `ISO 8601 timestamp` | Date and time when the user's access to the content began. | | `[].endDate` | `ISO 8601 timestamp` | Date and time when the user's access to the content ends. | | `[].certificate` | `Certificate` | Certificate issued to the user upon completing this content item. | | `[].certificate.id` | `uuid` | Unique identifier for the certificate. | | `[].certificate.certificateTemplate` | `CertificateTemplate` | Template used to generate this certificate. | | `[].certificate.user` | `User` | User to whom the certificate was issued. | | `[].certificate.issuedAt` | `ISO 8601 timestamp` | Date and time the certificate was issued. | | `[].certificate.url` | `string` | URL where the certificate can be viewed online. | | `[].certificate.pdfAsset` | `string` | URL of the downloadable PDF version of the certificate. | | `[].certificate.resource` | `CertificateResource` | The resource (e.g., course or learning path) linked to this certificate. | | `[].certificate.resourceType` | `CertificateResourceType` | The type of resource associated with the certificate, such as `course`. | | `[].certificate.resourceTypeLabel` | `string` | Human-readable label for the resource type. | | `[].certificate.resourceId` | `uuid` | UUID of the resource for which the certificate was issued. | | `[].certificate.progressWasReset` | `boolean` | Indicates whether the user's progress was reset before this certificate was issued. | | `[].certificate.expirationDate` | `ISO 8601 timestamp` | Date and time the certificate expires. | | `[].certificate.didExpire` | `boolean` | Indicates whether the certificate has already expired. | | `[].certificate.deleted` | `boolean` | Indicates whether the certificate has been deleted. | | `[].certificate.recertificationDate` | `ISO 8601 timestamp` | Date and time by which the user must complete recertification. | | `[].certificate.isExpired` | `boolean` | Indicates whether the certificate is currently in an expired state. | | `[].certificate.source` | `string` | Origin or system from which the certificate was generated. | | `[].certificate.contentItem` | `Content` | Content item associated with the certificate. | | `[].certificate.isExternal` | `boolean` | Indicates whether the certificate was issued for an externally hosted resource. | | `[].certificate.externalResourceTitle` | `string` | Title of the external resource for which the certificate was issued. | | `[].certificate.identifier` | `string` | Unique identifier string assigned to the certificate, used for tracking or display. | | `[].percentComplete` | `integer` | User's completion progress for this content item, expressed as a percentage (0–100). | | `[].status` | `string` | Current enrollment or completion status of the content item for the user. | --- ### Users Creates a new user account, optionally upserting by email and granting access to licenses, courses, bundles, and learning paths in a single request. ### POST /v2/users #### Example request ```bash curl -X POST "$BASE_URL/users" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "email": "string", "upsert": true, "externalCustomerId": "string", "studentLicenseSkus": [] }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `email` | `string` | No | body | Email address for the new user account. | | `upsert` | `boolean` | No | body | When `true`, updates the existing user if one with the given email already exists instead of returning an error. | | `externalCustomerId` | `string` | No | body | Identifier for the user in an external system. | | `studentLicenseSkus` | `string[]` | Yes | body | SKUs of licenses to assign to the user with the student role. | | `managerLicenseSkus` | `string[]` | Yes | body | SKUs of licenses to assign to the user with the manager role. | | `studentLicenseIds` | `uuid[]` | Yes | body | UUIDs of licenses to assign to the user with the student role. | | `managerLicenseIds` | `uuid[]` | Yes | body | UUIDs of licenses to assign to the user with the manager role. | | `role` | `string` | No | body | Platform-level role to assign to the user (e.g., `admin`, `learner`). | | `clientSku` | `string` | No | body | SKU of the client (tenant) to associate with the user. | | `clientSlug` | `string` | No | body | URL slug of the client (tenant) to associate with the user. | | `clientId` | `uuid` | No | body | UUID of the client (tenant) to associate with the user. | | `replaceLicenseAccess` | `boolean` | No | body | When `true`, replaces the user's existing license assignments with those provided in this request. | | `courseIds` | `uuid[]` | Yes | body | UUIDs of courses to grant the user access to. | | `courseSlugs` | `string[]` | Yes | body | URL slugs of courses to grant the user access to. | | `courseSkus` | `string[]` | Yes | body | SKUs of courses to grant the user access to. | | `bundleSlugs` | `string[]` | Yes | body | URL slugs of bundles to grant the user access to. | | `learningPathSlugs` | `string[]` | Yes | body | URL slugs of learning paths to grant the user access to. | | `learningPathSkus` | `string[]` | Yes | body | SKUs of learning paths to grant the user access to. | | `learningPathIds` | `uuid[]` | Yes | body | UUIDs of learning paths to grant the user access to. | | `firstName` | `string` | No | body | User's first name. | | `lastName` | `string` | No | body | User's last name. | | `address1` | `string` | No | body | Primary street address line for the user. | | `address2` | `string` | No | body | Secondary street address line (e.g., apartment or suite number) for the user. | | `city` | `string` | No | body | City component of the user's address. | | `state` | `string` | No | body | State or province component of the user's address. | | `zipCode` | `string` | No | body | Postal code component of the user's address. | | `country` | `string` | No | body | Country component of the user's address. | | `telephone` | `string` | No | body | User's phone number. | | `ref1` | `string` | No | body | Custom reference field 1 for storing arbitrary external data. | | `ref2` | `string` | No | body | Custom reference field 2 for storing arbitrary external data. | | `ref3` | `string` | No | body | Custom reference field 3 for storing arbitrary external data. | | `ref4` | `string` | No | body | Custom reference field 4 for storing arbitrary external data. | | `ref5` | `string` | No | body | Custom reference field 5 for storing arbitrary external data. | | `ref6` | `string` | No | body | Custom reference field 6 for storing arbitrary external data. | | `ref7` | `string` | No | body | Custom reference field 7 for storing arbitrary external data. | | `ref8` | `string` | No | body | Custom reference field 8 for storing arbitrary external data. | | `ref9` | `string` | No | body | Custom reference field 9 for storing arbitrary external data. | | `ref10` | `string` | No | body | Custom reference field 10 for storing arbitrary external data. | | `language` | `string` | No | body | Preferred language for the user interface, as a BCP 47 language tag (e.g., `en`, `fr`). | | `tieredSubscription` | `boolean` | No | body | When `true`, enrolls the user under a tiered subscription plan. | | `replaceCourseAccess` | `boolean` | No | body | When `true`, replaces the user's existing course access with the courses provided in this request. | | `replaceLearningPathAccess` | `boolean` | No | body | When `true`, replaces the user's existing learning path access with the learning paths provided in this request. | | `replaceBundleAccess` | `boolean` | No | body | When `true`, replaces the user's existing bundle access with the bundles provided in this request. | | `customFields` | `object` | No | body | Key-value pairs for any custom profile fields defined on the client. | | `sfContactId` | `uuid` | No | body | Salesforce Contact ID to associate with this user. | | `sfAccountId` | `uuid` | No | body | Salesforce Account ID to associate with this user. | | `sendInvite` | `boolean` | No | body | When `true`, sends an invitation email to the user after account creation. | | `inviteMessage` | `string` | No | body | Custom message body included in the invitation email when `sendInvite` is `true`. | | `balance` | `number` | No | body | Initial credit balance to assign to the user, in the client's configured credit units. | | `enforceAccessDays` | `boolean` | No | body | When `true`, enforces the access-day limit defined on the assigned license or content. | | `preferredCurrency` | `string` | No | body | ISO 4217 currency code representing the user's preferred display currency (e.g., `USD`, `EUR`). | #### Example response ```json { "data": { "APICreateUser": { "id": "00000000-0000-0000-0000-000000000000", "email": "string", "firstName": "string", "abbreviatedName": "string", "asset": "string", "lastName": "string", "name": "string", "lastActiveAt": "2024-01-01T00:00:00.000Z", "bio": "string", "twoFactorEnabled": true, "firstInitial": "string", "lastInitial": "string", "invitedByName": "string", "shouldHighlight": true, "purchasedCourses": [ { "course": { "id": "00000000-0000-0000-0000-000000000000", "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "slug": "example-slug", "status": "draft", "title": "string", "sessionIsTitled": true, "sampleLesson": { "...": "..." }, "sampleLessonId": "00000000-0000-0000-0000-000000000000", "sections": [ { "...": "..." } ], "appearanceBlock": "00000000-0000-0000-0000-000000000000", "appearanceHash": "string", "paywallsBlock": "00000000-0000-0000-0000-000000000000", "termsBlock": "00000000-0000-0000-0000-000000000000", "confirmationBlock": "00000000-0000-0000-0000-000000000000", "courseStartDate": "2024-01-01T00:00:00.000Z", "courseEndDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "gracePeriodEndDate": "2024-01-01T00:00:00.000Z", "seatsLimit": 0, "waitlistCount": 0, "seatsLimitMet": true, "waitlistingEnabled": true, "waitlistingTriggered": true, "taxable": true, "purchasable": true, "taxCategory": "00000000-0000-0000-0000-000000000000", "fulfillmentCenter": "00000000-0000-0000-0000-000000000000", "courseGroup": { "...": "..." }, "syllabusTitlesEnabled": true, "galleryEnabled": true, "workbookEnabled": true, "linkedWorkbookEnabled": true, "linkedWorkbookSkipEnabled": true, "superquizEnabled": true, "discussionsEnabled": true, "resourcesEnabled": true, "meetingsOverviewEnabled": true, "recurrenceRule": "string", "recurrence": {}, "readableRecurrenceString": "string", "instructorAccessEnabled": true, "instructorEmails": [ "string" ], "freeWithRegistration": true, "priceInCents": 0, "instructorAccessPriceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "showProgress": true, "annotationsEnabled": true, "forceLinearProgress": true, "completionTimePerPage": 0, "askLearnerPassword": true, "roster": true, "topicGroupId": "00000000-0000-0000-0000-000000000000", "sku": "string", "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "...": "..." } ], "parentCourseId": "00000000-0000-0000-0000-000000000000", "isChild": true, "hasMultipleCurrencies": true, "prices": [ { "...": "..." } ], "enrollmentActive": true, "waitlistActive": true, "webinarDates": [ { "...": "..." } ], "webinarTimeZone": "string", "meetings": [ { "...": "..." } ], "inPersonEventInfo": { "...": "..." }, "currentUserHasAccess": true, "location": { "...": "..." }, "displayMap": true, "publishDate": "2024-01-01T00:00:00.000Z", "isActive": true, "futurePublishDate": "2024-01-01T00:00:00.000Z", "balanceRequirement": 0, "awardClaimingOptions": [ { "...": "..." } ], "availabilityStatus": "string", "embeddedEnabled": true, "displayResults": true, "bulkPurchasingEnabled": true, "seatTiers": [ { "...": "..." } ], "seatPackages": [ { "...": "..." } ], "lastTierPriceInCents": 0, "webinarId": "string", "hasChildren": true, "customFields": {}, "kind": "microCourse", "rosterSubmittedDate": "2024-01-01T00:00:00.000Z", "finalAssessment": "string", "scormCollectUserDetails": true, "seatsUsedCount": 0, "seatsAllocatedCount": 0, "webinarDetails": { "...": "..." }, "showSyllabusWhileUnenrolled": true, "topicGroup": { "...": "..." }, "courseGroupTitle": "string", "currentUserDueDate": "2024-01-01T00:00:00.000Z" }, "courseId": "00000000-0000-0000-0000-000000000000", "certificate": "00000000-0000-0000-0000-000000000000", "certificateIssuedAt": "2024-01-01T00:00:00.000Z", "instructorAccessPurchased": true, "status": "string" } ], "purchasedBundles": [ { "bundle": { "id": "00000000-0000-0000-0000-000000000000", "slug": "example-slug", "name": "string", "purchasable": true, "planId": "00000000-0000-0000-0000-000000000000", "variations": [ { "...": "..." } ], "taxable": true, "needShippingAddress": true, "priceInCents": 0, "weight": 0, "annualPlanId": "00000000-0000-0000-0000-000000000000", "annualPriceInCents": 0 }, "bundleId": "00000000-0000-0000-0000-000000000000" } ], "activeLicense": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "label": "string", "sku": "string", "client": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "schoolName": "string", "courses": [ { "...": "..." } ], "learningPaths": [ { "...": "..." } ], "learningPathIds": [ "00000000-0000-0000-0000-000000000000" ], "courseIds": [ "00000000-0000-0000-0000-000000000000" ], "courseTags": [ { "...": "..." } ], "courseTagIds": [ --- ### Update User Status Enables or disables a user account. `:id` accepts a UUID, email address, or `externalCustomerId`. If the user has a paired dual-role account, the status change applies to both. ### PUT /v2/users/:id/updateStatus #### Example request ```bash curl -X PUT "$BASE_URL/users/:id/updateStatus" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "status": "string" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `string` | Yes | path | User UUID, email, or externalCustomerId. | | `status` | `string` | Yes | body | Required. 'active' or 'disabled'. | #### Example response ```json { "status": "disabled" } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `status` | `string` | 'active' or 'disabled'. | --- ### Reset Course Status Resets a user's progress on a specified course, with options to revoke access and erase transcript data. ### POST /v2/users/:id/resetProgress #### Example request ```bash curl -X POST "$BASE_URL/users/:id/resetProgress" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "courseSlug": "onboarding-101", "resetProgress": true, "revokeAccess": false, "eraseTranscript": false }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `string` | Yes | path | The user UUID. Used as `userId` on the mutation. | | `courseSlug` | `string` | No | body | Slug of the course to reset. Takes precedence over `courseId` / `courseSku`. | | `courseId` | `uuid` | No | body | UUID of the course to reset. Used to look up the course slug when `courseSlug` is not provided. | | `courseSku` | `string` | No | body | External SKU of the course, used to look up the slug when `courseSlug` and `courseId` are absent. | | `resetProgress` | `boolean` | No | body | Defaults to `true`. When `true`, clears all progress data recorded for the user on this course. | | `revokeAccess` | `boolean` | No | body | When `true`, removes the user's access to the course after the reset. | | `eraseTranscript` | `boolean` | No | body | When `true`, removes the course completion record from the user's transcript. | One of `courseSlug`, `courseId`, or `courseSku` must be supplied. #### Example response ```json { "data": { "ResetUserCourseProgress": true } } ``` --- ### Reset Learning Path Progress Resets a user's progress on a learning path, with options to revoke access and erase transcript data. ### POST /v2/users/:id/resetLearningPathProgress #### Example request ```bash curl -X POST "$BASE_URL/users/:id/resetLearningPathProgress" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "learningPathSlug": "manager-fundamentals", "resetProgress": true, "revokeAccess": false, "eraseTranscript": false }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `string` | Yes | path | The user UUID. Used as `userId` on the mutation. | | `learningPathSlug` | `string` | No | body | Slug of the learning path to reset. Takes precedence over `learningPathId` / `learningPathSku`. | | `learningPathId` | `uuid` | No | body | UUID of the learning path. Used to look up its slug when `learningPathSlug` is not provided. | | `learningPathSku` | `string` | No | body | External SKU of the learning path, used to look up the slug when `learningPathSlug` and `learningPathId` are absent. | | `resetProgress` | `boolean` | No | body | Defaults to `true`. When `true`, clears all progress the user has made on the learning path. | | `revokeAccess` | `boolean` | No | body | When `true`, removes the user's access to the learning path after the reset. | | `eraseTranscript` | `boolean` | No | body | When `true`, removes the learning path activity from the user's transcript. | One of `learningPathSlug`, `learningPathId`, or `learningPathSku` must be supplied. #### Example response ```json { "data": { "ResetUserLearningPathProgress": true } } ``` --- ### Bulk Remove Dual Role Manager Removes the dual-role manager designation from one or more users in bulk. Returns a count of updated records and a list of any user IDs that could not be processed. ### PUT /v2/users/bulkRemoveDualRoleManager #### Example request ```bash curl -X PUT "$BASE_URL/users/bulkRemoveDualRoleManager" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "userIds": "00000000-0000-0000-0000-000000000000" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `userIds` | `uuid[]` | Yes | body | One or more user UUIDs identifying the users whose dual-role manager status should be removed. | #### Example response ```json { "data": { "APIBulkRemoveDualRoleManager": { "updatedRecordCount": 0, "invalidUserIds": [ "00000000-0000-0000-0000-000000000000" ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `updatedRecordCount` | `integer` | The number of user records successfully updated. | | `invalidUserIds` | `uuid[]` | User UUIDs from the request that could not be found or updated. | --- ### Users Updates the specified user's profile, license assignments, course access, and related attributes. ### PUT /v2/users/:id #### Example request ```bash curl -X PUT "$BASE_URL/users/:id" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "email": "string", "externalCustomerId": "string", "studentLicenseSkus": [], "managerLicenseSkus": [] }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The id identifier. | | `email` | `string` | No | body | The user's email address. | | `externalCustomerId` | `string` | No | body | An external system identifier for the user, used to map the user to a record in an outside platform. | | `studentLicenseSkus` | `string[]` | Yes | body | SKUs of licenses to assign to the user in the student role. | | `managerLicenseSkus` | `string[]` | Yes | body | SKUs of licenses to assign to the user in the manager role. | | `studentLicenseIds` | `uuid[]` | Yes | body | IDs of licenses to assign to the user in the student role. | | `managerLicenseIds` | `uuid[]` | Yes | body | IDs of licenses to assign to the user in the manager role. | | `role` | `string` | No | body | The user's platform role (e.g., `admin`, `learner`). | | `clientSku` | `string` | No | body | SKU of the client (tenant) to associate with the user. | | `clientSlug` | `string` | No | body | URL slug of the client (tenant) to associate with the user. | | `clientId` | `uuid` | No | body | ID of the client (tenant) to associate with the user. | | `replaceLicenseAccess` | `boolean` | No | body | When `true`, replaces the user's existing license assignments with the ones provided; otherwise merges them. | | `courseIds` | `uuid[]` | Yes | body | IDs of courses to grant the user direct access to. | | `courseSlugs` | `string[]` | Yes | body | URL slugs of courses to grant the user direct access to. | | `courseSkus` | `string[]` | Yes | body | SKUs of courses to grant the user direct access to. | | `bundleSlugs` | `string[]` | Yes | body | URL slugs of bundles to grant the user access to. | | `learningPathSlugs` | `string[]` | Yes | body | URL slugs of learning paths to grant the user access to. | | `learningPathSkus` | `string[]` | Yes | body | SKUs of learning paths to grant the user access to. | | `learningPathIds` | `uuid[]` | Yes | body | IDs of learning paths to grant the user access to. | | `firstName` | `string` | No | body | The user's first name. | | `lastName` | `string` | No | body | The user's last name. | | `address1` | `string` | No | body | The first line of the user's mailing address. | | `address2` | `string` | No | body | The second line of the user's mailing address (e.g., suite or apartment number). | | `city` | `string` | No | body | The city component of the user's mailing address. | | `state` | `string` | No | body | The state or province component of the user's mailing address. | | `zipCode` | `string` | No | body | The postal code component of the user's mailing address. | | `country` | `string` | No | body | The country component of the user's mailing address. | | `telephone` | `string` | No | body | The user's phone number. | | `ref1` | `string` | No | body | Custom reference field 1 for storing arbitrary external data. | | `ref2` | `string` | No | body | Custom reference field 2 for storing arbitrary external data. | | `ref3` | `string` | No | body | Custom reference field 3 for storing arbitrary external data. | | `ref4` | `string` | No | body | Custom reference field 4 for storing arbitrary external data. | | `ref5` | `string` | No | body | Custom reference field 5 for storing arbitrary external data. | | `ref6` | `string` | No | body | Custom reference field 6 for storing arbitrary external data. | | `ref7` | `string` | No | body | Custom reference field 7 for storing arbitrary external data. | | `ref8` | `string` | No | body | Custom reference field 8 for storing arbitrary external data. | | `ref9` | `string` | No | body | Custom reference field 9 for storing arbitrary external data. | | `ref10` | `string` | No | body | Custom reference field 10 for storing arbitrary external data. | | `language` | `string` | No | body | The user's preferred language code (e.g., `en`, `fr`). | | `tieredSubscription` | `boolean` | No | body | When `true`, enables tiered subscription pricing for the user. | | `replaceCourseAccess` | `boolean` | No | body | When `true`, replaces the user's existing course access with the courses provided; otherwise merges them. | | `replaceLearningPathAccess` | `boolean` | No | body | When `true`, replaces the user's existing learning path access with the learning paths provided; otherwise merges them. | | `replaceBundleAccess` | `boolean` | No | body | When `true`, replaces the user's existing bundle access with the bundles provided; otherwise merges them. | | `customFields` | `object` | No | body | Key-value pairs of custom field data defined for the user's client. | | `sfContactId` | `uuid` | No | body | The Salesforce Contact ID linked to this user. | | `sfAccountId` | `uuid` | No | body | The Salesforce Account ID linked to this user. | | `dualRole` | `boolean` | No | body | When `true`, grants the user both learner and manager roles simultaneously. | | `balance` | `number` | No | body | The user's credit balance, in the platform's configured currency units. | | `enforceAccessDays` | `boolean` | No | body | When `true`, enforces license access-day limits on the user's content access. | | `preferredCurrency` | `string` | No | body | The user's preferred currency code for pricing display (e.g., `USD`, `EUR`). | | `panoramaEnrollmentOptions` | `PanoramaEnrollmentOptions` | No | body | Configuration options controlling the user's enrollment behavior in Panorama. | #### Example response ```json { "data": { "APIUpdateUser": { "id": "00000000-0000-0000-0000-000000000000", "email": "string", "firstName": "string", "abbreviatedName": "string", "asset": "string", "lastName": "string", "name": "string", "lastActiveAt": "2024-01-01T00:00:00.000Z", "bio": "string", "twoFactorEnabled": true, "firstInitial": "string", "lastInitial": "string", "invitedByName": "string", "shouldHighlight": true, "purchasedCourses": [ { "course": { "id": "00000000-0000-0000-0000-000000000000", "createdAt": "2024-01-01T00:00:00.000Z", "updatedAt": "2024-01-01T00:00:00.000Z", "slug": "example-slug", "status": "draft", "title": "string", "sessionIsTitled": true, "sampleLesson": { "...": "..." }, "sampleLessonId": "00000000-0000-0000-0000-000000000000", "sections": [ { "...": "..." } ], "appearanceBlock": "00000000-0000-0000-0000-000000000000", "appearanceHash": "string", "paywallsBlock": "00000000-0000-0000-0000-000000000000", "termsBlock": "00000000-0000-0000-0000-000000000000", "confirmationBlock": "00000000-0000-0000-0000-000000000000", "courseStartDate": "2024-01-01T00:00:00.000Z", "courseEndDate": "2024-01-01T00:00:00.000Z", "enrollmentStartDate": "2024-01-01T00:00:00.000Z", "enrollmentEndDate": "2024-01-01T00:00:00.000Z", "gracePeriodEndDate": "2024-01-01T00:00:00.000Z", "seatsLimit": 0, "waitlistCount": 0, "seatsLimitMet": true, "waitlistingEnabled": true, "waitlistingTriggered": true, "taxable": true, "purchasable": true, "taxCategory": "00000000-0000-0000-0000-000000000000", "fulfillmentCenter": "00000000-0000-0000-0000-000000000000", "courseGroup": { "...": "..." }, "syllabusTitlesEnabled": true, "galleryEnabled": true, "workbookEnabled": true, "linkedWorkbookEnabled": true, "linkedWorkbookSkipEnabled": true, "superquizEnabled": true, "discussionsEnabled": true, "resourcesEnabled": true, "meetingsOverviewEnabled": true, "recurrenceRule": "string", "recurrence": {}, "readableRecurrenceString": "string", "instructorAccessEnabled": true, "instructorEmails": [ "string" ], "freeWithRegistration": true, "priceInCents": 0, "instructorAccessPriceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "showProgress": true, "annotationsEnabled": true, "forceLinearProgress": true, "completionTimePerPage": 0, "askLearnerPassword": true, "roster": true, "topicGroupId": "00000000-0000-0000-0000-000000000000", "sku": "string", "prerequisiteCourses": [ { "...": "..." } ], "prerequisiteLearningPaths": [ { "...": "..." } ], "parentCourseId": "00000000-0000-0000-0000-000000000000", "isChild": true, "hasMultipleCurrencies": true, "prices": [ { "...": "..." } ], "enrollmentActive": true, "waitlistActive": true, "webinarDates": [ { "...": "..." } ], "webinarTimeZone": "string", "meetings": [ { "...": "..." } ], "inPersonEventInfo": { "...": "..." }, "currentUserHasAccess": true, "location": { "...": "..." }, "displayMap": true, "publishDate": "2024-01-01T00:00:00.000Z", "isActive": true, "futurePublishDate": "2024-01-01T00:00:00.000Z", "balanceRequirement": 0, "awardClaimingOptions": [ { "...": "..." } ], "availabilityStatus": "string", "embeddedEnabled": true, "displayResults": true, "bulkPurchasingEnabled": true, "seatTiers": [ { "...": "..." } ], "seatPackages": [ { "...": "..." } ], "lastTierPriceInCents": 0, "webinarId": "string", "hasChildren": true, "customFields": {}, "kind": "microCourse", "rosterSubmittedDate": "2024-01-01T00:00:00.000Z", "finalAssessment": "string", "scormCollectUserDetails": true, "seatsUsedCount": 0, "seatsAllocatedCount": 0, "webinarDetails": { "...": "..." }, "showSyllabusWhileUnenrolled": true, "topicGroup": { "...": "..." }, "courseGroupTitle": "string", "currentUserDueDate": "2024-01-01T00:00:00.000Z" }, "courseId": "00000000-0000-0000-0000-000000000000", "certificate": "00000000-0000-0000-0000-000000000000", "certificateIssuedAt": "2024-01-01T00:00:00.000Z", "instructorAccessPurchased": true, "status": "string" } ], "purchasedBundles": [ { "bundle": { "id": "00000000-0000-0000-0000-000000000000", "slug": "example-slug", "name": "string", "purchasable": true, "planId": "00000000-0000-0000-0000-000000000000", "variations": [ { "...": "..." } ], "taxable": true, "needShippingAddress": true, "priceInCents": 0, "weight": 0, "annualPlanId": "00000000-0000-0000-0000-000000000000", "annualPriceInCents": 0 }, "bundleId": "00000000-0000-0000-0000-000000000000" } ], "activeLicense": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "label": "string", "sku": "string", "client": { "id": "00000000-0000-0000-0000-000000000000", "name": "string", "schoolName": "string", "courses": [ { "...": "..." } ], "learningPaths": [ { "...": "..." } ], "learningPathIds": [ "00000000-0000-0000-0000-000000000000" ], "courseIds": [ "00000000-0000-0000-0000-000000000000" ], "courseTags": [ { "...": "..." } ], "courseTagIds": [ "00000000-0000-0000-0 --- ### Make Dual Role Assigns a dual role to the specified user, enabling them to act as both a learner and a manager within the platform. ### PUT /v2/users/:id/makeDualRole #### Example request ```bash curl -X PUT "$BASE_URL/users/:id/makeDualRole" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "role": "string" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The id identifier. | | `role` | `string` | No | body | The role to assign to the user in addition to their existing role (e.g., `"manager"` or `"learner"`). | #### Example response ```json { "data": { "SetupUserDualRole": { "learnerId": "00000000-0000-0000-0000-000000000000", "managerId": "00000000-0000-0000-0000-000000000000" } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `learnerId` | `uuid` | The unique identifier of the user in their learner role. | | `managerId` | `uuid` | The unique identifier of the user in their manager role. | --- ### Anonymize Anonymizes and deletes a user by ID. All personally identifiable information is removed. Returns an empty `AnonymizeUser` object on success. ### DELETE /v2/users/:id #### Example request ```bash curl -X DELETE "$BASE_URL/users/:id" \ -H 'Authorization: Bearer APIKEY' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `uuid` | Yes | path | The unique identifier of the user to anonymize and delete. | #### Example response ```json { "data": { "AnonymizeUser": {} } } ``` --- ### Bulk Import Create bulkImport ### POST /v2/users/bulkImport #### Example request ```bash curl -X POST "$BASE_URL/users/bulkImport" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "url": "string", "inviteMessage": "string", "bundleIds": "00000000-0000-0000-0000-000000000000", "courseIds": "00000000-0000-0000-0000-000000000000" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `url` | `string` | Yes | body | URL of the CSV file containing the users to import. | | `inviteMessage` | `string` | No | body | Custom message to include in the invitation email sent to imported users. | | `bundleIds` | `uuid[]` | Yes | body | IDs of the bundles to enroll imported users in. | | `courseIds` | `uuid[]` | Yes | body | IDs of the courses to enroll imported users in. | | `learningPathIds` | `uuid[]` | Yes | body | IDs of the learning paths to enroll imported users in. | | `licenseIds` | `uuid[]` | Yes | body | IDs of the licenses to assign to imported users. | | `clientId` | `uuid` | No | body | ID of the client portal to associate the imported users with. | | `role` | `string` | No | body | Role to assign to each imported user within the platform. | | `dryRun` | `boolean` | No | body | When `true`, validates the import without creating or modifying any users. | #### Example response ```json { "data": { "APIBulkImport": { "id": "00000000-0000-0000-0000-000000000000", "type": "string", "description": "string", "errorMessage": "string", "createdAt": "2024-01-01T00:00:00.000Z", "repeatable": true, "status": "complete" } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `id` | `uuid` | Unique identifier of the bulk import background job. | | `type` | `string` | Category or kind of background job that was created. | | `description` | `string` | Human-readable summary of the background job. | | `errorMessage` | `string` | Error detail returned if the import job failed; empty when the job succeeded. | | `createdAt` | `ISO 8601 timestamp` | Date and time when the import job was created, in ISO 8601 format. | | `repeatable` | `boolean` | Indicates whether the import job can be re-run with the same parameters. | | `status` | `BackgroundJobStatus` | Current processing state of the import job (e.g. `complete`). | --- ### Bulk Content Assignment Assigns content to multiple users in a single request. ### POST /v2/users/bulkContentAssignment #### Example request ```bash curl -X POST "$BASE_URL/users/bulkContentAssignment" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "users": [] }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `users` | `BulkContentAssignmentUser[]` | Yes | body | Array of user-content assignment objects specifying which content to assign to each user. | #### Example response ```json { "data": { "APIBulkContentAssignment": { "updatedRecordCount": 0, "errors": [ { "id": "00000000-0000-0000-0000-000000000000", "identifier": "string", "error": "string", "record": "string" } ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `updatedRecordCount` | `integer` | Number of user-content assignment records successfully updated. | | `errors` | `BulkAssignmentUserError[]` | Array of error objects for any assignments that failed to process. | | `errors.id` | `uuid` | Unique identifier of the user record that encountered an error. | | `errors.identifier` | `string` | External identifier of the user associated with the failed assignment. | | `errors.error` | `string` | Human-readable message describing why the assignment failed. | | `errors.record` | `string` | Raw record data associated with the failed assignment, provided for debugging. | --- ### Bulk Panorama Assignment Assigns Panorama client licenses to multiple users in a single request. ### POST /v2/users/bulkClientLicenseAssignment #### Example request ```bash curl -X POST "$BASE_URL/users/bulkClientLicenseAssignment" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "users": [] }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `users` | `BulkClientLicenseAssignmentUser[]` | Yes | body | Array of user objects to assign Panorama client licenses to. | #### Example response ```json { "data": { "APIBulkPanoramaAssignment": { "updatedRecordCount": 0, "errors": [ { "id": "00000000-0000-0000-0000-000000000000", "identifier": "string", "error": "string", "record": "string" } ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `updatedRecordCount` | `integer` | Number of user records successfully updated. | | `errors` | `BulkAssignmentUserError[]` | Array of error objects for any users whose license assignment failed. | | `errors.id` | `uuid` | Unique identifier of the user whose assignment failed. | | `errors.identifier` | `string` | The identifier (such as email or username) used to look up the user. | | `errors.error` | `string` | Description of why the assignment failed for this user. | | `errors.record` | `string` | The raw record value associated with the failed assignment. | --- ### Bulk Content Remove Access Removes content access from multiple users in a single request. ### POST /v2/users/bulkContentRemoveAccess #### Example request ```bash curl -X POST "$BASE_URL/users/bulkContentRemoveAccess" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "users": [] }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `users` | `BulkContentRemoveAccessUser[]` | Yes | body | Array of user objects specifying the users and content from which access should be removed. | #### Example response ```json { "data": { "APIBulkContentRemoveAccess": { "updatedRecordCount": 0, "errors": [ { "id": "00000000-0000-0000-0000-000000000000", "identifier": "string", "error": "string", "record": "string" } ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `updatedRecordCount` | `integer` | Number of user records successfully updated. | | `errors` | `BulkAssignmentUserError[]` | Array of errors for any users whose access could not be removed. | | `errors.id` | `uuid` | Unique identifier of the user record that encountered an error. | | `errors.identifier` | `string` | External identifier or email address used to look up the user. | | `errors.error` | `string` | Human-readable message describing why the access removal failed for this user. | | `errors.record` | `string` | Raw record value associated with the failed entry, for debugging purposes. | --- ### Reschedule Moves a user's enrollment from one course to another. The user is taken from the `:id` path parameter; the body carries the two course IDs. ### PUT /v2/users/:id/reschedule #### Example request ```bash curl -X PUT "$BASE_URL/users/:id/reschedule" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "existingCourseId": "00000000-0000-0000-0000-000000000000", "rescheduleCourseId": "00000000-0000-0000-0000-000000000000" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `string` | Yes | path | The user UUID. Used as `userId` on the mutation. | | `existingCourseId` | `uuid` | Yes | body | The UUID of the course the user is currently enrolled in. | | `rescheduleCourseId` | `uuid` | Yes | body | The UUID of the course to move the user into. | #### Example response ```json { "success": true } ``` If the underlying mutation returns no result, the raw GraphQL response is passed through instead. --- ### Update Meeting Attendance Records or updates a user's attendance for a specific meeting. ### POST /v2/users/:id/updateMeetingAttendance #### Example request ```bash curl -X POST "$BASE_URL/users/:id/updateMeetingAttendance" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "meetingId": "00000000-0000-0000-0000-000000000000", "attended": true }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `string` | Yes | path | The user UUID. Used as `userId` on the mutation. | | `meetingId` | `uuid` | Yes | body | Unique identifier of the meeting to record attendance for. | | `attended` | `boolean` | Yes | body | Set to `true` to mark the user as attended, or `false` to mark them as absent. | #### Example response ```json { "success": true } ``` If the mutation encounters an error, the raw GraphQL result is returned instead. --- --- ### Update Expirations Creates or updates expiration dates for a user's courses, learning paths, and licenses. ### POST /v2/users/:id/expirations #### Example request ```bash curl -X POST "$BASE_URL/users/:id/expirations" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "userId": "00000000-0000-0000-0000-000000000000", "courseIds": "00000000-0000-0000-0000-000000000000", "learningPathIds": "00000000-0000-0000-0000-000000000000", "licenseIds": "00000000-0000-0000-0000-000000000000" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `string` | Yes | path | The id identifier. | | `userId` | `uuid` | Yes | body | The UUID of the user whose expirations are being updated. | | `courseIds` | `uuid[]` | Yes | body | One or more UUIDs identifying the courses to apply the expiration date to. | | `learningPathIds` | `uuid[]` | Yes | body | One or more UUIDs identifying the learning paths to apply the expiration date to. | | `licenseIds` | `uuid[]` | Yes | body | One or more UUIDs identifying the licenses to apply the expiration date to. | | `expirationDate` | `ISO 8601 timestamp` | Yes | body | The expiration date and time to set, formatted as an ISO 8601 timestamp. | #### Example response ```json { "data": { "APIUpdateExpirations": { "updatedRecordCount": 0, "errors": [ "string" ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `updatedRecordCount` | `integer` | The number of expiration records successfully updated. | | `errors` | `string[]` | A list of error messages describing any records that failed to update. | --- ### Destroy Expirations Deletes expiration records for a user's courses, learning paths, and licenses. ### POST /v2/users/:id/deleteExpirations #### Example request ```bash curl -X POST "$BASE_URL/users/:id/deleteExpirations" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "userId": "00000000-0000-0000-0000-000000000000", "courseIds": "00000000-0000-0000-0000-000000000000", "learningPathIds": "00000000-0000-0000-0000-000000000000", "licenseIds": "00000000-0000-0000-0000-000000000000" }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `id` | `string` | Yes | path | The id identifier. | | `userId` | `uuid` | Yes | body | Unique identifier of the user whose expirations will be deleted. | | `courseIds` | `uuid[]` | Yes | body | One or more course IDs whose expiration records will be removed for the user. | | `learningPathIds` | `uuid[]` | Yes | body | One or more learning path IDs whose expiration records will be removed for the user. | | `licenseIds` | `uuid[]` | Yes | body | One or more license IDs whose expiration records will be removed for the user. | #### Example response ```json { "data": { "APIDestroyExpirations": { "updatedRecordCount": 0, "errors": [ "string" ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `updatedRecordCount` | `integer` | Number of expiration records successfully deleted. | | `errors` | `string[]` | List of error messages for any expiration records that could not be deleted. | --- ### Bulk Update Meeting Attendance Bulk-updates meeting attendance records for multiple users in a single request. ### POST /v2/users/bulkUpdateMeetingAttendance #### Example request ```bash curl -X POST "$BASE_URL/users/bulkUpdateMeetingAttendance" \ -H 'Authorization: Bearer APIKEY' \ -H 'Content-Type: application/json' \ -d '{ "users": [], "meetingId": "00000000-0000-0000-0000-000000000000", "attended": true }' ``` #### Parameters | Name | Type | Required | Location | Description | |------|------|----------|----------|-------------| | `users` | `BulkUpdateMeetingAttendanceUser[]` | Yes | body | Array of users whose attendance records will be updated. | | `meetingId` | `uuid` | Yes | body | Unique identifier of the meeting to update attendance for. | | `attended` | `boolean` | Yes | body | Set to `true` to mark the users as having attended, or `false` to mark them as absent. | #### Example response ```json { "data": { "APIBulkUpdateMeetingAttendance": { "updatedRecordCount": 0, "errors": [ { "id": "00000000-0000-0000-0000-000000000000", "error": "string" } ] } } } ``` #### Response fields | Field | Type | Description | |-------|------|-------------| | `updatedRecordCount` | `integer` | Number of attendance records successfully updated. | | `errors` | `BulkUpdateMeetingAttendanceError[]` | Array of errors for any users whose attendance records could not be updated. | | `errors.id` | `uuid` | Unique identifier of the user whose record failed to update. | | `errors.error` | `string` | Human-readable message describing why the update failed for this user. | --- --- ## Pagination List endpoints return paginated results to keep response sizes manageable. The REST API uses cursor-based pagination: pass the opaque `cursor` value from the previous response to fetch the next page. ## Resource endpoints Resource list endpoints (Users, Courses, Course Groups, Clients, and so on) return an unwrapped response whose top level contains a `pageInfo` object plus an array named after the resource. **Response: 200 List response** ```json { "courseGroups": [ { "id": "abc123", "title": "Onboarding", "slug": "onboarding" }, { "id": "def456", "title": "Compliance", "slug": "compliance" } ], "pageInfo": { "perPage": 50, "total": 142, "hasMore": true, "cursor": "eyJwYWdlIjoyfQ" } } ``` ### Query parameters | Parameter | Type | Required | Default | Description | |---|---|---|---|---| | `cursor` | string | No | — | Opaque cursor returned by the previous response. Omit for the first page. | | `perPage` | integer | No | `50` | Number of records per page. | ### `pageInfo` fields | Field | Type | Description | |---|---|---| | `perPage` | integer | Records returned in this page. | | `total` | integer | Total records matching the query. | | `hasMore` | boolean | Whether additional pages are available. | | `cursor` | string | Opaque cursor to pass on the next request. Present only when `hasMore` is `true`. | ## Events endpoints Event and notification endpoints (`GET /v2/events/:kind`) use a lighter pagination model. Responses include a `pageInfo` object with only `cursor` and `hasMore`, plus an `events` array. **Response: 200 Events response** ```json { "pageInfo": { "cursor": "MTUxODM2NTg1Mzk5OQ", "hasMore": true }, "events": [ /* ... */ ] } ``` Events endpoints accept `cursor` as the only pagination query parameter; `perPage` is not supported. ## Iterating through pages Pass the `cursor` from each response until `hasMore` is `false`. ```bash title="curl" # First page curl -s -H "Authorization: Bearer $TI_API_KEY" \ "https://api.thoughtindustries.com/incoming/v2/courseGroups?perPage=50" # Subsequent pages curl -s -H "Authorization: Bearer $TI_API_KEY" \ "https://api.thoughtindustries.com/incoming/v2/courseGroups?perPage=50&cursor=eyJwYWdlIjoyfQ" ``` ```javascript title="JavaScript" async function fetchAllCourseGroups(apiKey) { const groups = []; let cursor = null; do { const url = new URL("https://api.thoughtindustries.com/incoming/v2/courseGroups"); url.searchParams.set("perPage", "50"); if (cursor) url.searchParams.set("cursor", cursor); const response = await fetch(url, { headers: { "Authorization": `Bearer ${apiKey}` } }); const body = await response.json(); groups.push(...body.courseGroups); cursor = body.pageInfo.hasMore ? body.pageInfo.cursor : null; } while (cursor); return groups; } ``` ```python title="Python" def fetch_all_course_groups(api_key): groups = [] cursor = None base_url = "https://api.thoughtindustries.com/incoming/v2/courseGroups" while True: params = {"perPage": 50} if cursor: params["cursor"] = cursor response = requests.get( base_url, headers={"Authorization": f"Bearer {api_key}"}, params=params ) body = response.json() groups.extend(body["courseGroups"]) if not body["pageInfo"]["hasMore"]: break cursor = body["pageInfo"]["cursor"] return groups ``` ```php title="PHP" function fetchAllCourseGroups(string $apiKey): array { $groups = []; $cursor = null; $baseUrl = "https://api.thoughtindustries.com/incoming/v2/courseGroups"; do { $params = ["perPage" => 50]; if ($cursor) $params["cursor"] = $cursor; $url = $baseUrl . "?" . http_build_query($params); $ch = curl_init($url); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Authorization: Bearer $apiKey" ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $body = json_decode(curl_exec($ch), true); $groups = array_merge($groups, $body["courseGroups"]); $cursor = ($body["pageInfo"]["hasMore"] ?? false) ? $body["pageInfo"]["cursor"] : null; } while ($cursor); return $groups; } ``` ## Best practices - Prefer `perPage=50` or higher for bulk exports to minimize round-trips. - Treat `cursor` as opaque. Do not decode, mutate, or persist it beyond the immediately following request. - Stop iterating as soon as `hasMore` is `false`; the next `cursor` field will be absent. - Use `total` for progress indicators, but do not rely on it to detect the last page — `hasMore` is authoritative. ## Related - [Rate limits](/docs/rest/rate-limits/) — request quotas apply per page request - [Authentication](/docs/rest/authentication/) — include credentials on every page request --- ## Rate limits The REST API applies throttling on a **per-company** basis to a specific set of routes. Throttled routes count each request as one point against a fixed-size 60‑second window (5 minutes for a few IP‑scoped routes). When a company exceeds the limit for a given tier, the API returns `429 Too Many Requests` until the window resets. Throttling is **route-scoped**, not global: many endpoints have no throttle attached and are not counted against any tier. See [Which routes are throttled](#which-routes-are-throttled) below. ## Throttle tiers Each throttled route is bound to a tier. The **default** limit applies to all companies; the **increased** limit applies only when the account flag `increasedAPIRateLimits` is enabled or when a custom per-company override is configured. | Tier | Default limit | Increased limit | Window | Scope | |---|---|---|---|---| | `userAPI` | 20 / min | 250 / min | 60 s | company | | `eventAPI` | 60 / min | 200 / min | 60 s | company | | `meetingAPI` | 40 / min | 500 / min | 60 s | company | | `ssoAPI` | 500 / min | 1000 / min | 60 s | company | | `redemptionCodes`, `publicCertificate` | 30 / 5 min | — | 300 s | request IP | | `certificateShareToken` | 10 / 5 min | — | 300 s | request IP | There is no "Standard" vs. "Enterprise" plan tiering, no burst allowance, and no cross‑endpoint quota. Each tier maintains its own independent counter. :::note A separate `heliumAPI` tier (2500 / min, per-company) governs the GraphQL `/helium` surface, not the REST API documented here. ::: ## Which routes are throttled Only routes that opt in through the throttle middleware are counted. The current attachments include: - **`userAPI`** — user create/update/delete endpoints (for example, `POST /v2/users`, `PUT /v2/users`). - **`eventAPI`** — events ingest endpoints (for example, `GET`/`POST` on `/v2/events`). - **`meetingAPI`** — meeting endpoints. - **`ssoAPI`** — SSO endpoints. - **`redemptionCodes`, `publicCertificate`, `certificateShareToken`** — IP‑scoped public routes. Routes without a throttle attached are **not** rate-limited by this middleware. Notable examples include `POST /v2/content/course/create`, `PUT /v2/content/course/update`, webinar and in‑person bulk imports, `GET /v1/users`, and most GET endpoints. These may still be subject to platform-level protections such as edge WAF or connection limits. :::caution Attachment is per-route in the API code; the exact set of throttled endpoints can change between releases. Treat throttling as an infrastructure guarantee for the tiers above rather than as an endpoint contract. ::: ## Rate limit headers Rate limit headers are set **only on throttled routes** (routes without a throttle attached do not emit them). On a **successful** response from a throttled route, all three headers are returned: ``` X-RateLimit-Limit: 20 X-RateLimit-Remaining: 17 X-RateLimit-Reset: 1716505260 ``` | Header | Description | |---|---| | `X-RateLimit-Limit` | Maximum requests allowed per window for the tier bound to this route | | `X-RateLimit-Remaining` | Requests remaining in the current window | | `X-RateLimit-Reset` | Unix timestamp (seconds) when the window resets | On a **rejected `429`** response, only `X-RateLimit-Reset` and `Retry-After` are set — `X-RateLimit-Limit` and `X-RateLimit-Remaining` are not included on the fallback path. ## Handling rate limit errors When the limit is exceeded on a throttled route, the API returns `429 Too Many Requests` with a `Retry-After` header. The response body is a plain error envelope: **Response: 429 Too Many Requests** ```json { "errors": ["Rate limit exceeded, retry in 32 seconds"] } ``` `Retry-After` is the authoritative signal for how long to wait; clients should read it rather than parsing the message string. ## Retry strategy Read `Retry-After` and back off before retrying. Add jitter to avoid retry storms across parallel clients. ```bash title="curl" # Read Retry-After from the response and sleep before retrying RETRY_AFTER=$(curl -s -o /dev/null -D - -w "%header{retry-after}" \ -H "Authorization: Bearer ti_live_a1b2c3d4e5f6g7h8i9j0" \ "https://api.thoughtindustries.com/incoming/v2/users") sleep "${RETRY_AFTER:-5}" # Retry the request ``` ```javascript title="JavaScript" async function fetchWithRetry(url, options, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { const response = await fetch(url, options); if (response.status !== 429) return response; // Prefer Retry-After; fall back to a small default. const retryAfter = parseInt(response.headers.get("Retry-After") || "5", 10); const jitter = Math.random() * 1000; await new Promise(r => setTimeout(r, retryAfter * 1000 + jitter)); } throw new Error("Rate limit exceeded after max retries"); } ``` ```python title="Python" def fetch_with_retry(url, headers, max_retries=3): for attempt in range(max_retries): response = requests.get(url, headers=headers) if response.status_code != 429: return response # Prefer Retry-After; fall back to a small default. retry_after = int(response.headers.get("Retry-After", 5)) jitter = random.uniform(0, 1) time.sleep(retry_after + jitter) raise Exception("Rate limit exceeded after max retries") ``` ```php title="PHP" function fetchWithRetry(string $url, array $headers, int $maxRetries = 3): string { for ($attempt = 0; $attempt < $maxRetries; $attempt++) { $ch = curl_init($url); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HEADER, true); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($httpCode !== 429) return $response; // In production, parse Retry-After from the response headers. $retryAfter = 5; usleep((int) (($retryAfter + mt_rand(0, 1000) / 1000) * 1000000)); } throw new \Exception("Rate limit exceeded after max retries"); } ``` ## Best practices - Cache responses where possible to reduce request volume. - Use [pagination](/docs/rest/pagination/) with reasonable page sizes. - Spread bulk operations across multiple windows and, where possible, across tiers. - Monitor `X-RateLimit-Remaining` on throttled routes and back off before it reaches zero. - Contact support if your integration consistently needs the `increasedAPIRateLimits` tier or a custom override. ## Related - [Authentication](/docs/rest/authentication/) — API key setup and usage - [Status codes](/docs/rest/status-codes/) — full error code reference - [Pagination](/docs/rest/pagination/) — efficient data retrieval --- ## Status codes The REST API uses standard HTTP status codes to indicate the outcome of each request. The `/v2` endpoints emit a compact set of codes; error response bodies vary by endpoint and are not wrapped in a common envelope. ## Codes the API emits | Code | Meaning | When returned | |---|---|---| | `200 OK` | Request succeeded | All successful responses, including creates and deletes — no `201` or `204` is emitted by `/v2` endpoints. | | `400 Bad Request` | Malformed request body or invalid parameters | Missing required fields, wrong types, unsupported content type. | | `401 Unauthorized` | Missing or invalid API key | See [Authentication](/docs/rest/authentication/). Response body is empty. | | `404 Not Found` | Resource does not exist | A small number of endpoints (for example, redemption codes) return this when a specific record is missing. | | `422 Unprocessable Entity` | Request was valid but the operation could not be completed | Business-rule violations reported by the underlying GraphQL layer (for example, an invalid status transition). | | `429 Too Many Requests` | Rate limit exceeded | See [Rate limits](/docs/rest/rate-limits/). | | `500 Internal Server Error` | Unexpected server failure | Retry after a brief delay; contact support if persistent. | `/v2` endpoints do not return `201`, `204`, `403`, `405`, or `409`. Treat `2xx` as success and any `4xx`/`5xx` as failure. ## Error response bodies Error bodies are not standardized across endpoints. Expect any of the following shapes depending on the failure: **Plain string body** Many controllers set `this.body` to a short human-readable string alongside a `4xx` status. ```text Invalid ID or SKU ``` **Rate-limit response (429)** The throttle middleware returns a JSON object with an `errors` array. **Response: 429 Too Many Requests** ```json { "errors": [ "Rate limit exceeded, retry in 27 seconds" ] } ``` The response also sets `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers. **GraphQL passthrough (422)** When a mutation succeeds at the HTTP layer but fails a business rule, the endpoint returns the raw GraphQL result. Inspect `errors[]` inside it for details. ```json { "errors": [ { "message": "Cannot transition user from disabled to disabled", "path": ["UpdateUserStatus"] } ] } ``` **Empty body (401)** Authentication failures return `401` with no body — the failure mode is inferred from the status code alone. ## Handling errors in code Because bodies vary, defensively parse the response before reading fields: ```javascript title="JavaScript" const response = await fetch(url, { headers }); if (!response.ok) { const text = await response.text(); let payload; try { payload = JSON.parse(text); } catch { payload = text; } console.error(`[${response.status}]`, payload); if (response.status === 429) { const retryAfter = response.headers.get("Retry-After"); // schedule a retry after `retryAfter` seconds } } ``` ```python title="Python" response = requests.get(url, headers=headers) if not response.ok: try: payload = response.json() except ValueError: payload = response.text print(f"[{response.status_code}]", payload) if response.status_code == 429: retry_after = response.headers.get("Retry-After") # sleep(int(retry_after)) before retrying ``` ```php title="PHP" $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($httpCode >= 400) { $payload = json_decode($response, true); if ($payload === null) { $payload = $response; } error_log("[{$httpCode}] " . print_r($payload, true)); } ``` ## Related - [Authentication](/docs/rest/authentication/) — resolve 401 errors - [Rate limits](/docs/rest/rate-limits/) — resolve 429 errors - [Pagination](/docs/rest/pagination/) — avoid unnecessary requests --- ## Authentication & Endpoint How to authenticate requests against the Thought Industries GraphQL API. ## Endpoint GraphQL queries and mutations are executed by sending `POST` requests to your learning instance: ``` POST https://{learning_instance}.com/helium?apiKey=YOUR_API_KEY ``` Replace `{learning_instance}` with your subdomain and `YOUR_API_KEY` with the API key from **Settings → Security**. ## API Key vs. Auth Token | Credential | Purpose | Usage | |------------|---------|-------| | **API Key** | Identifies your **learning instance** | Required on every request as the `apiKey` query parameter | | **Auth Token** | Identifies an **individual learner** | Generated by the `Login` mutation. Inside Helium, tokens are managed automatically via `pageContext` | ## Generating an Auth Token Call the `Login` mutation with the learner's email and password to receive a token. ```graphql mutation { Login(email: "user@example.com", password: "****") { token user { id firstName lastName } } } ``` --- ## Comments & Discussion Threads Fetch comments on videos and discussion threads on courses. ## Fetching Comments on a Video Call the `Forums` query first, then use the returned ID with the `Comments` query. ### Step 1: Call `Forums` ```graphql query Forums( $courseId: ID! ) { Forums( courseId: $courseId ) { id # pass this into the Comments query label } } // SAMPLE VARIABLES { "courseId": "{{ ID from CatalogContent > displayCourse }}" } ``` ### Step 2: Call `Comments` Use data returned from the `Forums` query. ```graphql query Comments( $commentableId: ID!, $commentableType: CommentableType!, ) { Comments( commentableId: $commentableId, commentableType: $commentableType, ) { comments { asset assetFileName body } } } // SAMPLE VARIABLES { "commentableId": "{{ ID from Forums query }}", "commentableType": "discussionBoard" } ``` ## Fetching Discussion Threads on a Course To fetch a discussion thread on a course, call three queries consecutively: `Forums`, `Threads`, then `Comments`. ### Step 1: Call `Forums` ```graphql query Forums( $courseId: ID! ) { Forums( courseId: $courseId ) { id # pass this into the Comments query label } } // SAMPLE VARIABLES { "courseId": "{{ ID from CatalogContent > displayCourse }}" } ``` ### Step 2: Call `Threads` ```graphql query Threads( $forumId: ID!, $courseId: ID! ) { Threads( forumId: $forumId, courseId: $courseId ) { threads { body id } } } // SAMPLE VARIABLES { "forumId": "{{ ID from Forums query }}", "courseId": "{{ ID from CatalogContent > DisplayCourse }}" } ``` ### Step 3: Call `Comments` Use data returned from the `Threads` query. ```graphql query Comments( $commentableId: ID!, $commentableType: CommentableType!, ) { Comments( commentableId: $commentableId, commentableType: $commentableType, ) { comments { asset assetFileName body } } } // SAMPLE VARIABLES { "commentableId": "{{ ID from Forums query }}", "commentableType": "discussionBoard" } ``` --- ## Bookmark Endpoints Save, organize, and retrieve learner bookmarks and folders. ### UserBookmarks **`Query`** | Complexity: **1 pt** — Fetch bookmark folders for the current user Returns all bookmark folders belonging to the currently authenticated user, along with the bookmarks nested within each folder. Each bookmark references the course topic and course it was created in, as well as any user-authored notes. This query requires an `authToken` header to identify the user — without it, no meaningful data will be returned. Use this query to build a user's personal bookmark library or to display saved content items across courses. #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the bookmark folder. | | `name` | `String!` | The display name of the bookmark folder. | | `user` | `User!` | The user who owns this bookmark folder. | | `deleted` | `Boolean!` | Indicates whether this bookmark folder has been soft-deleted. | | `bookmarks` | `[Bookmark!]` | The list of bookmarks contained within this folder, each referencing a course topic and optional user note. | | `defaultFolder` | `Boolean` | Indicates whether this is the user's default bookmark folder. | | `bookmarkCount` | `Int` | The total number of bookmarks in this folder. | | `externalResourceId` | `ID` | An optional external resource identifier associated with this bookmark folder. | #### Example Request ```graphql query GetUserBookmarks { UserBookmarks { id name deleted defaultFolder bookmarkCount bookmarks { id note createdAt deleted topicId topic { id title type } course { id title slug } } } } ``` #### Example Response ```json { "data": { "UserBookmarks": [ { "id": "folder_001", "name": "My Favorites", "deleted": false, "defaultFolder": true, "bookmarkCount": 3, "bookmarks": [ { "id": "bookmark_101", "note": "Review this section before the exam.", "createdAt": "2024-03-10T11:00:00Z", "deleted": false, "topicId": "topic_555", "topic": { "id": "topic_555", "title": "Introduction to Variables", "type": "lesson" }, "course": { "id": "course_xyz789", "title": "Introduction to GraphQL", "slug": "intro-to-graphql" } } ] } ] }, "id": "abc-123", "name": "example", "user": null, "deleted": false, "bookmarks": null, "defaultFolder": false, "bookmarkCount": 0, "externalResourceId": "abc-123" } ``` :::note This query is user-scoped and requires an `authToken` header to identify the current user. Without authentication the query will return no results. ::: --- ### UserBookmarksByFolder **`Query`** | Complexity: **1 pt** — Fetch user bookmarks by folder Returns all bookmarks belonging to the current user within a specified bookmark folder. Each bookmark includes details about the bookmarked course, the associated topic (if any), optional user notes, and the folder it belongs to. The active user context can be overridden by supplying an `authToken` header, which is useful for server-side operations acting on behalf of a specific learner. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID!` | Yes | The ID of the bookmark folder whose bookmarks should be returned. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier of the bookmark. | | `note` | `String` | An optional note the user attached to the bookmark. | | `user` | `User!` | The user who created the bookmark. | | `topic` | `Topic` | The specific topic within the course that was bookmarked, if applicable. | | `topicId` | `ID` | The ID of the bookmarked topic, if applicable. | | `course` | `Course!` | The course associated with this bookmark. | | `createdAt` | `Date!` | The date and time the bookmark was created. | | `deleted` | `Boolean!` | Whether the bookmark has been soft-deleted. | | `bookmarkFolder` | `BookmarkFolder!` | The folder this bookmark belongs to. | #### Example Request ```graphql query UserBookmarksByFolder($id: ID!) { UserBookmarksByFolder(id: $id) { id note createdAt deleted topicId topic { id title type } course { id title slug } bookmarkFolder { id name defaultFolder bookmarkCount } user { id email firstName lastName } } } # Variables: # { "id": "folder_abc123" } ``` #### Example Response ```json { "data": { "UserBookmarksByFolder": [ { "id": "bm_001", "note": "Great module on React hooks", "createdAt": "2024-03-15T10:22:00Z", "deleted": false, "topicId": "topic_789", "topic": { "id": "topic_789", "title": "Introduction to React Hooks", "type": "video" }, "course": { "id": "course_456", "title": "Modern JavaScript Development", "slug": "modern-javascript-development" }, "bookmarkFolder": { "id": "folder_abc123", "name": "My Favorites", "defaultFolder": false, "bookmarkCount": 3 }, "user": { "id": "user_123", "email": "jane.doe@example.com", "firstName": "Jane", "lastName": "Doe" } } ] }, "id": "abc-123", "note": "example", "user": null, "topic": null, "topicId": "abc-123", "course": null, "createdAt": "2026-01-01T00:00:00Z", "deleted": false, "bookmarkFolder": null } ``` --- ### CreateBookmark **`Mutation`** | Complexity: **1 pt** — Create a bookmark Creates a new bookmark within a bookmark folder for the authenticated user. You can optionally attach a note to the bookmark, associate it with a specific topic (lesson page), and place it in a designated folder. The bookmark is linked to a course and the user is derived from the authentication context. By passing an `authToken` header you can create the bookmark on behalf of a specific user. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `bookmark` | `CreateBookmarkInput!` | Yes | The input fields to create a bookmark. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier for the newly created bookmark. | | `note` | `String` | Optional text note attached to the bookmark. | | `user` | `User!` | The user who owns the bookmark. | | `topic` | `Topic` | The topic (lesson page) the bookmark points to, if any. | | `topicId` | `ID` | ID of the associated topic, if applicable. | | `course` | `Course!` | The course the bookmark is associated with. | | `createdAt` | `Date!` | Timestamp when the bookmark was created. | | `deleted` | `Boolean!` | Indicates whether the bookmark has been soft-deleted. | | `bookmarkFolder` | `BookmarkFolder!` | The folder in which the bookmark is stored. | #### Example Request ```graphql mutation CreateBookmark($bookmark: CreateBookmarkInput!) { CreateBookmark(bookmark: $bookmark) { id note createdAt deleted topicId user { id email name } topic { id title type } course { id title slug } bookmarkFolder { id name defaultFolder bookmarkCount } } } ``` #### Example Response ```json { "data": { "CreateBookmark": { "id": "bm_7f3a2c1e-4b9d-4e8a-a123-000000000001", "note": "Review this section before the final exam.", "createdAt": "2024-05-15T10:30:00Z", "deleted": false, "topicId": "topic_abc123", "user": { "id": "user_001", "email": "learner@example.com", "name": "Jane Learner" }, "topic": { "id": "topic_abc123", "title": "Introduction to GraphQL", "type": "article" }, "course": { "id": "course_xyz789", "title": "Advanced API Development", "slug": "advanced-api-development" }, "bookmarkFolder": { "id": "folder_001", "name": "My Favorites", "defaultFolder": false, "bookmarkCount": 12 } } }, "id": "abc-123", "note": "example", "user": null, "topic": null, "topicId": "abc-123", "course": null, "createdAt": "2026-01-01T00:00:00Z", "deleted": false, "bookmarkFolder": null } ``` :::note The acting user is determined by the session or the `authToken` header. If no folder is specified in the input, the bookmark may be placed in the user's default folder depending on platform configuration. ::: --- ### CreateBookmarkFolder **`Mutation`** | Complexity: **1 pt** — Create a bookmark folder Creates a new bookmark folder for the authenticated user, which can be used to organize bookmarked content items. Each user can have multiple bookmark folders. Optionally, you can designate the new folder as the user's default bookmark folder by setting `defaultFolder` to `true`. This is useful when building custom learner dashboards or reading-list features. The operation is scoped to the currently authenticated user, or to a specific user if an `authToken` header is provided. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `name` | `String!` | Yes | The name of the bookmark folder. | | `defaultFolder` | `Boolean` | No | Flag to set it as the default bookmark folder. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the newly created bookmark folder. | | `name` | `String!` | The display name of the bookmark folder. | | `user` | `User!` | The user who owns this bookmark folder. | | `deleted` | `Boolean!` | Whether the bookmark folder has been soft-deleted. | | `bookmarks` | `[Bookmark!]` | The list of bookmarks contained within this folder. | | `defaultFolder` | `Boolean` | Whether this folder is the user's default bookmark folder. | | `bookmarkCount` | `Int` | The total number of bookmarks in this folder. | | `externalResourceId` | `ID` | An optional external resource ID associated with this bookmark folder. | #### Example Request ```graphql mutation CreateBookmarkFolder { CreateBookmarkFolder( name: "My Favorites" defaultFolder: false ) { id name defaultFolder deleted bookmarkCount user { id email } bookmarks { id note topicId createdAt } } } ``` #### Example Response ```json { "data": { "CreateBookmarkFolder": { "id": "bkf_01HX7M8N9O0P1Q2R", "name": "My Favorites", "defaultFolder": false, "deleted": false, "bookmarkCount": 0, "user": { "id": "usr_01HX1A2B3C4D5E6F", "email": "jane.doe@example.com" }, "bookmarks": [] } }, "id": "abc-123", "name": "example", "user": null, "deleted": false, "bookmarks": null, "defaultFolder": false, "bookmarkCount": 0, "externalResourceId": "abc-123" } ``` :::note Only one folder should be marked as the default at a time. Setting a new folder as default may automatically unset the previous default folder depending on platform configuration. ::: --- ### DestroyBookmark **`Mutation`** | Complexity: **1 pt** — Delete a bookmark Permanently removes a single bookmark from within a bookmark folder for the current user. Returns the ID of the deleted bookmark on success, or `null` if the operation did not return a value. Pass an `authToken` header to perform this action on behalf of a specific user. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID!` | Yes | The unique ID of the bookmark to delete. | #### Returns | Field | Type | Description | |-------|------|-------------| | `DestroyBookmark` | `ID` | The ID of the bookmark that was deleted, or `null` if unavailable. | #### Example Request ```graphql mutation DestroyBookmark { DestroyBookmark(id: "bookmark456abc") } ``` #### Example Response ```json { "data": { "DestroyBookmark": "bookmark456abc" } } ``` :::note This action is irreversible. Supply an `authToken` request header to target a specific user. ::: --- ### DestroyBookmarkFolder **`Mutation`** | Complexity: **1 pt** — Delete a bookmark folder Permanently destroys the specified bookmark folder for the current user. All bookmarks contained within the folder will also be removed. Returns the ID of the deleted folder on success. Pass an `authToken` header to perform this action on behalf of a specific user. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID!` | Yes | The unique ID of the bookmark folder to delete. | #### Returns | Field | Type | Description | |-------|------|-------------| | `DestroyBookmarkFolder` | `ID!` | The ID of the bookmark folder that was deleted. | #### Example Request ```graphql mutation DestroyBookmarkFolder { DestroyBookmarkFolder(id: "folder789xyz") } ``` #### Example Response ```json { "data": { "DestroyBookmarkFolder": "folder789xyz" } } ``` :::note This action is irreversible. Deleting a folder also removes all bookmarks within it. Supply an `authToken` request header to target a specific user. ::: --- ### OrderBookmarkFolders **`Mutation`** | Complexity: **1 pt** — Reorder bookmark folders Updates the display order of a user's bookmark folders by accepting an ordered list of folder IDs. The folders will be persisted in the sequence provided. Like other bookmark mutations, this operation is user-scoped and can be directed to a specific user by supplying an `authToken` header. Returns the ordered list of folder ID strings after the update. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `ids` | `[ID!]` | No | An ordered list of bookmark folder IDs representing the desired display order. Folders will be sorted according to the sequence of IDs provided. | #### Returns | Field | Type | Description | |-------|------|-------------| | `OrderBookmarkFolders` | `[String!]` | The ordered list of bookmark folder IDs reflecting the new sort order. | #### Example Request ```graphql mutation OrderBookmarkFolders { OrderBookmarkFolders( ids: ["folder_789", "folder_123", "folder_456"] ) } ``` #### Example Response ```json { "data": { "OrderBookmarkFolders": [ "folder_789", "folder_123", "folder_456" ] } } ``` :::note Omitting `ids` or passing `null` may result in no reordering. Provide an `authToken` header to target a specific user's folders. ::: --- ### UpdateBookmark **`Mutation`** | Complexity: **1 pt** — Update a bookmark Updates an existing bookmark's note text and/or the folder it belongs to. Use this mutation when a learner wants to annotate a bookmarked topic or reorganize bookmarks across folders. You must supply the bookmark's ID and the target folder ID; the note is optional and can be cleared by omitting it. The mutation is user-scoped — provide an `authToken` header to operate on behalf of a specific user. The full updated `Bookmark` object is returned, including associated user, course, topic, and folder details. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID!` | Yes | The unique ID of the bookmark to update. | | `note` | `String` | No | An optional text annotation to attach to the bookmark. Omit or pass null to leave without a note. | | `bookmarkFolder` | `ID!` | Yes | The ID of the bookmark folder to move or assign this bookmark to. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the bookmark. | | `note` | `String` | The optional text note attached to this bookmark. | | `user` | `User!` | The user who owns this bookmark. | | `topic` | `Topic` | The topic (lesson page/resource) that was bookmarked, if applicable. | | `topicId` | `ID` | The ID of the bookmarked topic. | | `course` | `Course!` | The course associated with the bookmarked content. | | `createdAt` | `Date!` | The timestamp when the bookmark was originally created. | | `deleted` | `Boolean!` | Indicates whether this bookmark has been soft-deleted. | | `bookmarkFolder` | `BookmarkFolder!` | The folder this bookmark currently belongs to. | #### Example Request ```graphql mutation UpdateBookmark { UpdateBookmark( id: "bookmark_001" note: "Review this before the exam" bookmarkFolder: "folder_123" ) { id note createdAt deleted user { id email name } course { id title } topic { id title type } bookmarkFolder { id name defaultFolder } } } ``` #### Example Response ```json { "data": { "UpdateBookmark": { "id": "bookmark_001", "note": "Review this before the exam", "createdAt": "2024-03-15T10:22:00Z", "deleted": false, "user": { "id": "user_42", "email": "learner@example.com", "name": "Jane Doe" }, "course": { "id": "course_99", "title": "Introduction to GraphQL" }, "topic": { "id": "topic_7", "title": "Schema Design Basics", "type": "article" }, "bookmarkFolder": { "id": "folder_123", "name": "Favorites", "defaultFolder": false } } }, "id": "abc-123", "note": "example", "user": null, "topic": null, "topicId": "abc-123", "course": null, "createdAt": "2026-01-01T00:00:00Z", "deleted": false, "bookmarkFolder": null } ``` :::note Despite the schema description mentioning 'note on a bookmark folder', this mutation updates the note on the bookmark itself and reassigns it to the specified folder. Provide an `authToken` header to operate on a specific user's bookmarks. ::: --- ### UpdateBookmarkFolder **`Mutation`** | Complexity: **1 pt** — Rename a bookmark folder Updates the display name of an existing bookmark folder belonging to the current user. Returns the full `BookmarkFolder` object reflecting the updated state, including the folder's associated user and any bookmarks it contains. Pass an `authToken` header to perform this action on behalf of a specific user. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID!` | Yes | The unique ID of the bookmark folder to rename. | | `name` | `String!` | Yes | The new display name to assign to the bookmark folder. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique ID of the bookmark folder. | | `name` | `String!` | The updated display name of the bookmark folder. | | `user` | `User!` | The user who owns this bookmark folder. | | `deleted` | `Boolean!` | Whether the bookmark folder has been deleted. | | `bookmarks` | `[Bookmark!]` | The list of bookmarks contained within this folder. | | `defaultFolder` | `Boolean` | Whether this is the user's default bookmark folder. | | `bookmarkCount` | `Int` | The total number of bookmarks in this folder. | | `externalResourceId` | `ID` | An optional external resource identifier associated with this folder. | #### Example Request ```graphql mutation UpdateBookmarkFolder { UpdateBookmarkFolder(id: "folder789xyz", name: "My Favorite Courses") { id name deleted defaultFolder bookmarkCount user { id email name } bookmarks { id note topicId createdAt deleted } } } ``` #### Example Response ```json { "data": { "UpdateBookmarkFolder": { "id": "folder789xyz", "name": "My Favorite Courses", "deleted": false, "defaultFolder": false, "bookmarkCount": 3, "externalResourceId": null, "user": { "id": "user123abc", "email": "jane.doe@example.com", "firstName": "Jane", "lastName": "Doe", "name": "Jane Doe", "abbreviatedName": "Jane D.", "disabled": false, "mustVerifyEmail": false, "shouldHighlight": false, "certificatesCount": 2, "collaborationsCount": 0, "availableCoursesCount": 5, "startedCoursesCount": 3, "completedCoursesCount": 1 }, "bookmarks": [ { "id": "bookmark001", "note": "Review before the exam", "topicId": "topic555", "createdAt": "2024-02-10T08:00:00Z", "deleted": false } ] } }, "id": "abc-123", "name": "example", "user": null, "deleted": false, "bookmarks": null, "defaultFolder": false, "bookmarkCount": 0, "externalResourceId": "abc-123" } ``` :::note Supply an `authToken` request header to target a specific user instead of the caller. ::: --- --- ## Comments & Forum Endpoints Fetch discussion threads and comments for videos and content pages. ### Comments **`Query`** | Complexity: **1 pt** — Fetch comments for a commentable object Returns a paginated list of comments associated with a specific commentable object such as a thread, discussion board post, or assignment submission. You must supply both `commentableType` (e.g. `thread`, `assignmentSubmission`) and `commentableId`. Use `parentId` to retrieve replies nested beneath a specific parent comment. Filter by `userId` to retrieve only comments authored by a particular user. Each comment includes the author's user profile, any attached media, child comment counts, and like metadata. Providing an `authToken` header contextualizes per-user fields such as `userLikeId`. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `clientId` | `ID` | No | The ID of the client. | | `parentId` | `ID` | No | The ID of the parent. | | `commentableType` | `CommentableType!` | Yes | The type of thread requested, based on the forum's location, e.g. discussion board, assignment, widget thread, etc. | | `commentableId` | `ID!` | Yes | The ID of the commentable type. | | `page` | `Int` | No | The page number to return within the collection. | | `userId` | `ID` | No | The ID of the user. | #### Returns | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `PageInfo!` | Pagination metadata including current page, items per page, total count, and whether additional pages exist. | | `comments` | `[Comment!]` | The list of comment objects for the requested page, each including body text, author, media attachments, nested replies, and like data. | #### Example Request ```graphql query GetComments { Comments( commentableType: thread commentableId: "thread_001" page: 1 ) { pageInfo { perPage currentPage total hasMore } comments { id body createdAt updatedAt notificationsEnabled likesCount userLikeId commentableId commentableType user { id name email } childComments { pageInfo { total } } } } } ``` #### Example Response ```json { "data": { "Comments": { "pageInfo": { "perPage": 25, "currentPage": 1, "total": 8, "hasMore": false }, "comments": [ { "id": "comment_001", "body": "Great introduction! Really looking forward to this course.", "createdAt": "2024-03-02T09:15:00Z", "updatedAt": "2024-03-02T09:15:00Z", "notificationsEnabled": true, "likesCount": 5, "userLikeId": null, "commentableId": "thread_001", "commentableType": "thread", "user": { "id": "user_222", "name": "John Learner", "email": "john@example.com" }, "childComments": { "pageInfo": { "total": 2 } } } ] } }, "pageInfo": null, "comments": null } ``` :::note Results may vary depending on the authenticated user context supplied via the `authToken` header. Nested child comments are available on each comment via the `childComments` field. ::: --- ### Forums **`Query`** | Complexity: **1 pt** — Fetch forums for a course Retrieves all forums associated with a given course, including discussion threads scoped to the course, a specific widget, or an assignment. Each forum object includes metadata such as a label, thread count, section association, and whether it is the general forum. Because this query is user-aware, passing an `authToken` header will scope the results to the authenticated user's perspective — for example, to determine access or visibility. Use this query to build discussion UIs or to enumerate available forums before fetching individual threads. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `courseId` | `ID!` | Yes | The ID of the course. | | `clientId` | `ID` | No | The ID of the client. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID` | Unique identifier of the forum. | | `course` | `Course!` | The course this forum belongs to, including full course details. | | `sectionId` | `ID` | The ID of the course section this forum is associated with, if scoped to a section. | | `label` | `String` | Display label for the forum. | | `threadsCount` | `Int!` | The total number of discussion threads in this forum. | | `createdAt` | `Date` | Timestamp when the forum was created. | | `updatedAt` | `Date` | Timestamp when the forum was last updated. | | `isGeneral` | `Boolean!` | Whether this is the general (top-level) forum for the course. | #### Example Request ```graphql query GetCourseForums($courseId: ID!) { Forums(courseId: $courseId) { id label sectionId threadsCount isGeneral createdAt updatedAt course { id title slug status } } } ``` #### Example Response ```json { "data": { "Forums": [ { "id": "forum_abc123", "label": "General Discussion", "sectionId": null, "threadsCount": 14, "isGeneral": true, "createdAt": "2024-01-15T10:00:00Z", "updatedAt": "2024-06-01T08:30:00Z", "course": { "id": "course_xyz789", "title": "Introduction to Data Science", "slug": "intro-to-data-science", "status": "published" } }, { "id": "forum_def456", "label": "Week 2 Assignment Help", "sectionId": "section_001", "threadsCount": 5, "isGeneral": false, "createdAt": "2024-02-01T09:00:00Z", "updatedAt": "2024-05-20T14:00:00Z", "course": { "id": "course_xyz789", "title": "Introduction to Data Science", "slug": "intro-to-data-science", "status": "published" } } ] }, "id": "abc-123", "course": null, "sectionId": "abc-123", "label": "example", "threadsCount": 0, "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "isGeneral": false } ``` :::note The results of this query are user-context-sensitive. Provide an `authToken` header to set the active user and adjust visibility accordingly. ::: --- ### SearchThreads **`Query`** | Complexity: **1 pt** — Search threads within a widget forum Performs a full-text search across threads scoped to a specific widget forum. This query is exclusively for widget-type forums — it will not search course discussion board forums. Supply a `query` string to filter threads by matching text. Results are always paginated; both `page` and `perPage` are required. The `sort` argument controls result ordering. Optionally filter by `clientId` to restrict results to a particular client. Providing an `authToken` header contextualizes per-user fields such as notification state and bookmarks. The `matchCount` field on each thread indicates how many times the query matched within that thread. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `widgetForumId` | `ID!` | Yes | The ID of the Widget Forum. | | `clientId` | `ID` | No | The ID of the client. | | `query` | `String` | No | The search query used to return the list of threads. | | `page` | `Int!` | Yes | The page number to return within the collection. | | `perPage` | `Int!` | Yes | The amount of items to be returned on the page. | | `sort` | `String!` | Yes | How the items are to be sorted. | #### Returns | Field | Type | Description | |-------|------|-------------| | `discussionRules` | `String` | Optional plain-text rules or guidelines for the widget forum discussion. | | `pageInfo` | `PageInfo!` | Pagination metadata including current page, items per page, total result count, and whether more pages exist. | | `threads` | `[Thread!]` | The list of matching thread objects for the requested page, each including authoring details and a `matchCount` reflecting search relevance. | #### Example Request ```graphql query SearchWidgetThreads { SearchThreads( widgetForumId: "widget_forum_456" query: "getting started" page: 1 perPage: 10 sort: "recent" ) { pageInfo { perPage currentPage total hasMore } threads { id title body commentsCount matchCount createdAt notificationsEnabled user { id name } } } } ``` #### Example Response ```json { "data": { "SearchThreads": { "discussionRules": null, "pageInfo": { "perPage": 10, "currentPage": 1, "total": 3, "hasMore": false }, "threads": [ { "id": "thread_201", "title": "Getting started tips", "body": "Here are some tips for getting started with the platform...", "commentsCount": 7, "matchCount": 2, "createdAt": "2024-02-20T14:00:00Z", "notificationsEnabled": false, "user": { "id": "user_333", "name": "Alice Admin" } } ] } }, "discussionRules": "example", "pageInfo": null, "threads": null } ``` :::note This query only works for widget forum threads. It will not return results for course discussion board forums. The `matchCount` field on each thread indicates how many search matches were found within that thread. ::: --- ### ThreadById **`Query`** | Complexity: **1 pt** — Fetch a single thread by ID Retrieves a single discussion thread by its unique ID. Optionally scope the lookup to a specific client or forum by providing `clientId` or `forumId`. Use `commentableType` to disambiguate threads that exist across different forum contexts (e.g. widget threads vs. discussion board threads). Providing an `authToken` header will enrich the response with per-user context such as notification and bookmark state for the authenticated user. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID!` | Yes | The ID of the thread. | | `clientId` | `ID` | No | The ID of the client. | | `forumId` | `ID` | No | The ID of the Forum. | | `commentableType` | `CommentableType` | No | The type of thread requested, based on the forum's location, e.g. discussion board, assignment, widget thread, etc. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the thread. | | `title` | `String` | The title or subject of the thread. | | `body` | `String` | The main body text of the thread post. | | `commentsCount` | `Int!` | The total number of comments posted on this thread. | | `user` | `User!` | The user who created the thread. | | `forum` | `Forum!` | The forum in which this thread resides. | | `forumId` | `ID!` | The ID of the forum this thread belongs to. | | `course` | `Course` | The course associated with this thread, if applicable. | | `client` | `Client` | The client (tenant) this thread belongs to, if applicable. | | `createdAt` | `Date` | The timestamp when the thread was created. | | `updatedAt` | `Date` | The timestamp when the thread was last updated. | | `asset` | `URL` | URL of an image or file asset attached to the thread. | | `assetFileName` | `String` | The filename of the attached asset. | | `videoAsset` | `ID` | The ID of a video asset attached to the thread. | | `notificationsEnabled` | `Boolean!` | Whether the current user has notifications enabled for this thread. | | `matchCount` | `Int` | The number of search matches within this thread, populated when returned from a search query. | | `bookmark` | `ID` | The bookmark ID if the current authenticated user has bookmarked this thread. | #### Example Request ```graphql query GetThreadById { ThreadById( id: "thread_001" forumId: "forum_abc123" ) { id title body commentsCount createdAt updatedAt notificationsEnabled bookmark forumId user { id name email } forum { id label threadsCount isGeneral } course { id title slug } } } ``` #### Example Response ```json { "data": { "ThreadById": { "id": "thread_001", "title": "Welcome to the course discussion!", "body": "Feel free to introduce yourself here.", "commentsCount": 12, "createdAt": "2024-03-01T10:00:00Z", "updatedAt": "2024-03-15T08:30:00Z", "notificationsEnabled": true, "bookmark": null, "forumId": "forum_abc123", "user": { "id": "user_111", "name": "Jane Instructor", "email": "jane@example.com" }, "forum": { "id": "forum_abc123", "label": "General Discussion", "threadsCount": 45, "isGeneral": true }, "course": { "id": "course_xyz789", "title": "Introduction to GraphQL", "slug": "intro-to-graphql" } } }, "id": "abc-123", "course": null, "forum": null, "forumId": "abc-123", "client": null, "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "asset": "https://example.com", "assetFileName": "example", "videoAsset": "abc-123", "title": "example", "body": "example", "commentsCount": 0, "user": null, "notificationsEnabled": false, "matchCount": 0, "bookmark": "abc-123" } ``` :::note Results may vary depending on the authenticated user context supplied via the `authToken` header. ::: --- ### Threads **`Query`** | Complexity: **1 pt** — Fetch paginated threads for a forum Returns a paginated list of discussion threads belonging to a specific forum. You must always supply `forumId`; when scoping to a course-level forum you should also provide `courseId`. Optionally filter by `clientId` to restrict results to a particular client, or by `commentableType` to target a specific forum location (e.g. a discussion board, assignment, or widget thread). Pass an `authToken` header to surface per-user data such as bookmark and notification state for the authenticated user. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `clientId` | `ID` | No | The ID of the client. | | `courseId` | `ID` | No | The ID of the course. | | `forumId` | `ID!` | Yes | The ID of the forum. | | `commentableType` | `CommentableType` | No | The type of thread requested, based on the forum's location, e.g. discussion board, assignment, widget thread, etc. | | `page` | `Int` | No | The page number to return within the collection. | | `perPage` | `Int` | No | The amount of items to be returned on the page. | #### Returns | Field | Type | Description | |-------|------|-------------| | `discussionRules` | `String` | Optional plain-text rules or guidelines displayed to participants of the discussion forum. | | `pageInfo` | `PageInfo!` | Pagination metadata including the current page, items per page, total count, and whether more pages exist. | | `threads` | `[Thread!]` | The list of thread objects returned for the requested page, each containing authoring, forum, course, and engagement details. | #### Example Request ```graphql query GetThreads { Threads( forumId: "forum_abc123" courseId: "course_xyz789" page: 1 perPage: 20 ) { discussionRules pageInfo { perPage currentPage total hasMore } threads { id title body commentsCount createdAt updatedAt notificationsEnabled bookmark forumId user { id name email } forum { id label threadsCount isGeneral } } } } ``` #### Example Response ```json { "data": { "Threads": { "discussionRules": "Please be respectful and on-topic.", "pageInfo": { "perPage": 20, "currentPage": 1, "total": 45, "hasMore": true }, "threads": [ { "id": "thread_001", "title": "Welcome to the course discussion!", "body": "Feel free to introduce yourself here.", "commentsCount": 12, "createdAt": "2024-03-01T10:00:00Z", "updatedAt": "2024-03-15T08:30:00Z", "notificationsEnabled": true, "bookmark": null, "forumId": "forum_abc123", "user": { "id": "user_111", "name": "Jane Instructor", "email": "jane@example.com" }, "forum": { "id": "forum_abc123", "label": "General Discussion", "threadsCount": 45, "isGeneral": true } } ] } }, "discussionRules": "example", "pageInfo": null, "threads": null } ``` :::note When providing `forumId`, the `courseId` argument is also required. Results may vary depending on the authenticated user context supplied via the `authToken` header. ::: --- ### CreateComment **`Mutation`** | Complexity: **1 pt** — Create a comment Creates a new comment on a commentable entity such as a discussion board thread, widget thread, assignment submission, or assignment. You must specify the target entity's ID and its type. Optionally, you can attach text content, a file asset, a video, or reply to an existing comment by providing a `parentId`. The `notificationsEnabled` flag controls whether the commenter receives notifications for subsequent replies. The acting user can be overridden by supplying an `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `clientId` | `ID` | No | The ID of the client. | | `commentableId` | `ID!` | Yes | The ID of the commentable entity. | | `commentableType` | `CommentableType!` | Yes | The type of the comment's parent thread, based on the comment's location, e.g. discussion board, assignment, widget thread, etc. | | `body` | `String` | No | The body of the comment. | | `asset` | `URL` | No | The URL of the asset. | | `assetFileName` | `String` | No | The file name of the asset. | | `videoAsset` | `ID` | No | The ID of the video asset. | | `parentId` | `ID` | No | The ID of the parent comment. | | `notificationsEnabled` | `Boolean!` | Yes | Flag to enable notifications. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier of the newly created comment. | | `commentable` | `Commentable!` | The entity (Topic or Thread) this comment belongs to. | | `commentableId` | `ID!` | The ID of the entity this comment is attached to. | | `commentableType` | `CommentableType!` | The type of entity being commented on (e.g., thread, assignmentSubmission, discussionBoard). | | `body` | `String` | The text content of the comment. | | `user` | `User!` | The user who created the comment. | | `parentComment` | `Comment` | The parent comment if this is a reply, otherwise null. | | `childComments` | `CommentsList` | Paginated list of replies to this comment. | | `asset` | `URL` | URL of a file asset attached to the comment. | | `assetFileName` | `String` | The original file name of the attached asset. | | `videoAsset` | `ID` | The ID of a video asset attached to the comment. | | `notificationsEnabled` | `Boolean!` | Whether the commenter has opted in to receive notifications for replies to this comment. | | `userLikeId` | `ID` | The ID of the current user's like on this comment, if they have liked it. | | `likesCount` | `Int` | Total number of likes this comment has received. | | `createdAt` | `Date` | Timestamp when the comment was created. | | `updatedAt` | `Date` | Timestamp when the comment was last updated. | #### Example Request ```graphql mutation CreateComment { CreateComment( commentableId: "thread_jkl012" commentableType: thread body: "This is a great discussion point!" notificationsEnabled: true parentId: null asset: null assetFileName: null videoAsset: null clientId: null ) { id body commentableId commentableType notificationsEnabled likesCount createdAt updatedAt user { id name email } parentComment { id body } asset assetFileName videoAsset } } ``` #### Example Response ```json { "data": { "CreateComment": { "id": "comment_mno345", "body": "This is a great discussion point!", "commentableId": "thread_jkl012", "commentableType": "thread", "notificationsEnabled": true, "likesCount": 0, "createdAt": "2024-06-01T11:00:00Z", "updatedAt": "2024-06-01T11:00:00Z", "user": { "id": "user_456", "name": "Jane Doe", "email": "learner@example.com" }, "parentComment": null, "asset": null, "assetFileName": null, "videoAsset": null } }, "id": "abc-123", "commentable": null, "commentableId": "abc-123", "commentableType": "thread", "body": "example", "user": null, "parentComment": null, "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "videoAsset": "abc-123", "asset": "https://example.com", "assetFileName": "example", "notificationsEnabled": false, "childComments": null, "userLikeId": "abc-123", "likesCount": 0 } ``` --- ### CreateCommentLike **`Mutation`** | Complexity: **1 pt** — Like a comment Adds a like from the currently authenticated user to the specified comment. You must supply the comment ID, the ID of the entity the comment is attached to (`commentableId`), and the `commentableType` string indicating the kind of thread (e.g., `"thread"`, `"discussionBoard"`, `"assignment"`). You may optionally pass a `clientId` to scope the operation to a specific client. On success, the mutation returns the ID of the newly created like, which can later be used to remove the like via `RemoveCommentLike`. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `commentId` | `ID!` | Yes | The ID of the comment. | | `commentableId` | `ID!` | Yes | The ID of the commentable entity. | | `commentableType` | `String!` | Yes | The type of thread, based on the thread's location, e.g. discussion board, assignment, widget thread, etc. | | `clientId` | `ID` | No | The ID of the client. | #### Returns | Field | Type | Description | |-------|------|-------------| | `ID!` | `ID!` | The ID of the newly created like record. | #### Example Request ```graphql mutation CreateCommentLike { CreateCommentLike( commentId: "comment_abc123", commentableId: "thread_xyz789", commentableType: "thread", clientId: "client_001" ) } ``` #### Example Response ```json { "data": { "CreateCommentLike": "like_def456" } } ``` :::note A user may only like a given comment once. Attempting to like a comment that has already been liked by the same user will result in an error. ::: --- ### CreateThread **`Mutation`** | Complexity: **1 pt** — Create a discussion thread Creates a new discussion thread within the specified forum. Threads are the top-level posts that other users can reply to with comments. You must supply a `forumId` and a `title`; all other arguments are optional. Use `commentableType` to indicate where the thread lives (e.g., a discussion board, assignment, or widget). You can optionally attach an image asset or a video asset to the opening post. The `notificationsEnabled` flag controls whether the thread author receives email notifications when replies are posted. The active user is determined by the `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `clientId` | `ID` | No | The ID of the client. | | `forumId` | `ID!` | Yes | The ID of the forum. | | `courseId` | `ID` | No | The ID of the course. | | `layoutId` | `ID` | No | The ID of the layout. | | `widgetTitle` | `String` | No | The title of the widget. | | `commentableType` | `CommentableType` | No | The type of thread to create, based on the thread's location, e.g. discussion board, assignment, widget thread, etc. | | `asset` | `URL` | No | The URL of the asset. | | `assetFileName` | `String` | No | The file name of the asset. | | `videoAsset` | `ID` | No | The ID of the video asset. | | `body` | `String` | No | The body of the comment. | | `title` | `String!` | Yes | The title of the comment. | | `notificationsEnabled` | `Boolean!` | Yes | Flag to enable notifications. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the newly created thread. | | `title` | `String` | The title of the thread. | | `body` | `String` | The body text of the thread's opening post. | | `commentsCount` | `Int!` | The total number of comments on this thread. | | `notificationsEnabled` | `Boolean!` | Whether the thread author has email notifications enabled for new replies. | | `forum` | `Forum!` | The forum in which the thread was created. | | `forumId` | `ID!` | The ID of the forum containing this thread. | | `course` | `Course` | The course associated with this thread, if any. | | `client` | `Client` | The client context for this thread. | | `user` | `User!` | The user who authored the thread. | | `asset` | `URL` | URL of an image asset attached to the thread. | | `assetFileName` | `String` | File name of the attached image asset. | | `videoAsset` | `ID` | ID of a video asset attached to the thread. | | `createdAt` | `Date` | Timestamp when the thread was created. | | `updatedAt` | `Date` | Timestamp when the thread was last updated. | | `matchCount` | `Int` | Search match count, relevant when threads are returned in a search context. | | `bookmark` | `ID` | ID of a bookmark associated with this thread for the current user, if any. | #### Example Request ```graphql mutation CreateThread { CreateThread( forumId: "frm_abc123" courseId: "crs_789xyz" title: "What did you think of Module 1?" body: "Share your thoughts on the first module's content and activities." notificationsEnabled: true commentableType: course ) { id title body commentsCount notificationsEnabled createdAt forum { id label } user { id name } } } ``` #### Example Response ```json { "data": { "CreateThread": { "id": "thr_new001", "title": "What did you think of Module 1?", "body": "Share your thoughts on the first module's content and activities.", "commentsCount": 0, "notificationsEnabled": true, "createdAt": "2024-03-20T14:30:00Z", "forum": { "id": "frm_abc123", "label": "General Discussion" }, "user": { "id": "usr_def456", "name": "Jane Smith" } } }, "id": "abc-123", "course": null, "forum": null, "forumId": "abc-123", "client": null, "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "asset": "https://example.com", "assetFileName": "example", "videoAsset": "abc-123", "title": "example", "body": "example", "commentsCount": 0, "user": null, "notificationsEnabled": false, "matchCount": 0, "bookmark": "abc-123" } ``` :::note The active user for this mutation is determined by the `authToken` request header. If no auth token is provided, the mutation uses the API key's default user context. ::: --- ### DestroyComment **`Mutation`** | Complexity: **1 pt** — Delete a comment Permanently removes a comment from its parent thread. Pass the comment's ID and, optionally, the `commentableType` to indicate which kind of thread the comment belongs to (e.g., a discussion board, assignment, or widget thread). On success, the mutation returns the ID of the deleted comment. The acting user is set via the `authToken` header; only the comment's author or an admin may delete a comment. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID!` | Yes | The ID of the comment. | | `commentableType` | `CommentableType` | No | The type of the comment's parent thread to destory, based on the thread's location, e.g. discussion board, assignment, widget thread, etc. | #### Returns | Field | Type | Description | |-------|------|-------------| | `ID` | `ID` | The ID of the comment that was deleted. | #### Example Request ```graphql mutation DestroyComment { DestroyComment( id: "comment_abc123", commentableType: thread ) } ``` #### Example Response ```json { "data": { "DestroyComment": "comment_abc123" } } ``` :::note This operation is irreversible. Child comments (replies) may also be removed depending on platform configuration. ::: --- ### DestroyThread **`Mutation`** | Complexity: **1 pt** — Delete a discussion thread Permanently removes a thread and all of its associated comments from the forum. The acting user must own the thread or have admin privileges. On success, the mutation returns the ID of the deleted thread. The `commentableType` argument can be supplied to explicitly scope the deletion to a specific thread context (e.g., discussion board vs. assignment). The acting user is resolved via the `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID!` | Yes | The ID of the thread. | | `commentableType` | `CommentableType` | No | The type of thread to destroy, based on the thread's location, e.g. discussion board, assignment, widget thread, etc. | #### Returns | Field | Type | Description | |-------|------|-------------| | `DestroyThread` | `ID` | The ID of the thread that was deleted, or null if the deletion failed. | #### Example Request ```graphql mutation DestroyThread { DestroyThread( id: "thr_new001" commentableType: course ) } ``` #### Example Response ```json { "data": { "DestroyThread": "thr_new001" } } ``` :::note This action is irreversible. All comments nested under the deleted thread will also be removed. Only the thread owner or an admin can perform this mutation. ::: --- ### RemoveCommentLike **`Mutation`** | Complexity: **1 pt** — Unlike a comment Removes a like previously added by the current user from a comment. You must supply both the `commentId` (the comment being unliked) and the `likeId` (the ID of the specific like record to remove, which is returned by `CreateCommentLike`). Returns `true` if the like was successfully removed. The acting user is determined by the `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `commentId` | `ID!` | Yes | The ID of the comment. | | `likeId` | `ID!` | Yes | The ID of the like. | #### Returns | Field | Type | Description | |-------|------|-------------| | `Boolean!` | `Boolean!` | Returns true when the like was successfully removed. | #### Example Request ```graphql mutation RemoveCommentLike { RemoveCommentLike( commentId: "comment_abc123", likeId: "like_def456" ) } ``` #### Example Response ```json { "data": { "RemoveCommentLike": true } } ``` :::note The `likeId` can be obtained from the `userLikeId` field on a `Comment` object or from the return value of `CreateCommentLike`. ::: --- ### UpdateComment **`Mutation`** | Complexity: **1 pt** — Update a comment Updates the body text and/or attached asset of an existing comment. You must supply the comment's ID; all other fields are optional, so you can update just the body, just the asset, or both at once. Optionally pass the `commentableType` to specify the context (e.g., a discussion board or assignment) in which the comment lives. The acting user is determined by the `authToken` header — omitting it will apply the change under the API's default authentication context. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID!` | Yes | The ID of the comment. | | `body` | `String` | No | The updated body of the comment. | | `asset` | `String` | No | The updated asset of the comment. | | `assetFileName` | `String` | No | The file name of the asset. | | `commentableType` | `String` | No | The type of the comment's parent thread to be updated, based on the thread's location, e.g. discussion board, assignment, widget thread, etc. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the comment. | | `commentable` | `Commentable!` | The parent entity this comment belongs to — either a Topic or a Thread. | | `commentableId` | `ID!` | The ID of the parent commentable entity (e.g., thread or topic). | | `commentableType` | `CommentableType!` | The type of the parent entity. One of: thread, widgetThread, assignmentSubmission, assignment, discussionBoard. | | `body` | `String` | The text content of the comment. | | `user` | `User!` | The user who authored the comment. | | `parentComment` | `Comment` | The parent comment if this is a reply, otherwise null. | | `createdAt` | `Date` | Timestamp when the comment was originally created. | | `updatedAt` | `Date` | Timestamp when the comment was last updated. | | `videoAsset` | `ID` | The ID of an attached video asset, if any. | | `asset` | `URL` | URL of an attached file asset, if any. | | `assetFileName` | `String` | The file name of the attached asset, if any. | | `notificationsEnabled` | `Boolean!` | Whether comment notifications are enabled for the acting user. | | `childComments` | `CommentsList` | Paginated list of replies to this comment. | | `userLikeId` | `ID` | The ID of the current user's like on this comment, if they have liked it. | | `likesCount` | `Int` | Total number of likes on this comment. | #### Example Request ```graphql mutation UpdateComment { UpdateComment( id: "comment_abc123", body: "Thanks for the clarification — this makes much more sense now!", commentableType: "thread" ) { id body commentableId commentableType createdAt updatedAt notificationsEnabled likesCount user { id name email } asset assetFileName } } ``` #### Example Response ```json { "data": { "UpdateComment": { "id": "comment_abc123", "body": "Thanks for the clarification — this makes much more sense now!", "commentableId": "thread_xyz789", "commentableType": "thread", "createdAt": "2024-01-15T10:00:00Z", "updatedAt": "2024-03-22T14:35:00Z", "notificationsEnabled": true, "likesCount": 4, "user": { "id": "user_001", "name": "Jane Doe", "email": "jane.doe@example.com" }, "asset": null, "assetFileName": null } }, "id": "abc-123", "commentable": null, "commentableId": "abc-123", "commentableType": "thread", "body": "example", "user": null, "parentComment": null, "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "videoAsset": "abc-123", "asset": "https://example.com", "assetFileName": "example", "notificationsEnabled": false, "childComments": null, "userLikeId": "abc-123", "likesCount": 0 } ``` :::note The acting user is resolved from the `authToken` request header. Only the comment's owner or an admin can update a comment; unauthorized attempts will result in an error. ::: --- ### UpdateThread **`Mutation`** | Complexity: **1 pt** — Update an existing discussion thread Updates the title and body of an existing thread's opening post. The current user must either own the thread or have admin access to the associated content. You may also update the optional `asset` (an image URL) or `commentableType` to recontextualize the thread's location type. The mutation returns the full updated `Thread` object. Provide the `authToken` header to perform this action on behalf of a specific user. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID!` | Yes | The ID of the thread. | | `body` | `String!` | Yes | The body of the opening comment of the thread. | | `title` | `String!` | Yes | The title of the comment. | | `asset` | `String` | No | The URL of the asset. | | `commentableType` | `CommentableType` | No | The type of thread to be updated, based on the thread's location, e.g. discussion board, assignment, widget thread, etc. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the thread. | | `title` | `String` | The updated title of the thread. | | `body` | `String` | The updated body text of the thread's opening post. | | `commentsCount` | `Int!` | The total number of comments on this thread. | | `notificationsEnabled` | `Boolean!` | Whether the thread author has notifications enabled. | | `forum` | `Forum!` | The forum containing this thread. | | `forumId` | `ID!` | The ID of the forum containing this thread. | | `course` | `Course` | The course associated with this thread, if any. | | `user` | `User!` | The user who authored the thread. | | `asset` | `URL` | URL of an image asset attached to the thread. | | `updatedAt` | `Date` | Timestamp when the thread was last updated. | | `createdAt` | `Date` | Timestamp when the thread was originally created. | #### Example Request ```graphql mutation UpdateThread { UpdateThread( id: "thr_new001" title: "Updated: What did you think of Module 1?" body: "I've updated the question — please share both thoughts and questions." commentableType: course ) { id title body updatedAt commentsCount user { id name } } } ``` #### Example Response ```json { "data": { "UpdateThread": { "id": "thr_new001", "title": "Updated: What did you think of Module 1?", "body": "I've updated the question — please share both thoughts and questions.", "updatedAt": "2024-03-21T09:15:00Z", "commentsCount": 3, "user": { "id": "usr_def456", "name": "Jane Smith" } } }, "id": "abc-123", "course": null, "forum": null, "forumId": "abc-123", "client": null, "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "asset": "https://example.com", "assetFileName": "example", "videoAsset": "abc-123", "title": "example", "body": "example", "commentsCount": 0, "user": null, "notificationsEnabled": false, "matchCount": 0, "bookmark": "abc-123" } ``` :::note Only the thread owner or an admin may update a thread. The mutation respects the `authToken` header to determine the acting user. ::: --- --- ## Content Endpoints Core queries for fetching catalog, courses, and lesson topics. ### CatalogContent **`Query`** | Complexity: **3 pts** — Fetch catalog content items Returns a paginated list of catalog content items along with metadata for the current learning instance. When an `authToken` header is provided, results are personalized for the authenticated user; otherwise, the public catalog profile is used. You can filter results by content type, apply custom field aggregation filters via `labels` and `values`, search with a `query`, and control sorting and display type. This query is useful for powering catalog pages, search experiences, and content discovery widgets. Note that this query has an additional cost of 3 points. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `layoutId` | `ID` | No | The id of the custom catalog widget layout. | | `widgetId` | `ID` | No | The id of the custom catalog widget. | | `sort` _(deprecated)_ | `String` | No | A legacy sort string used to specify the sort order. Prefer using `sortColumn` and `sortDirection` instead. | | `sortColumn` | `SortColumn` | No | The content data column used to sort the collection. Defaults to the value configured for catalog from the user's Thought Industries instance. If the catalog is not configured, it falls back to value `createdAt`. When the value is `relevance`, it requires the argument `query` also to be set, or it falls back to value `courseStartDate`. | | `sortDirection` | `SortDirection` | No | The direction used to sort the collection. Defaults to the value configured for catalog from the user's Thought Industries instance. If the catalog is not configured, it falls back to value `desc`. When the argument `sortColumn` is `relevance`, it requires the argument `query` also to be set, or it falls back to value `desc`. | | `resultsDisplayType` | `ContentItemDisplayType` | No | The display type. Defaults to the value configured for catalog from the user's Thought Industries instance. If the catalog is not configured, it falls back to value `grid`. When the value is `calendar`, it overrides the values for argument `sortColumn` to be `displayDate` and `sortDirection` to be `asc`. | | `page` | `Int!` | Yes | The page number to return within the collection. | | `token` | `String` | No | The secure catalog query. The value is encoded by the secret key of user's Thought Industries instance. | | `labels` | `[String!]` | No | The list of aggregation labels. The order of list items will match with argument `labels`. Labels are custom fields. | | `values` | `[String!]` | No | The list of aggregation values. The order of list items will match with argument `values`. Values are custom fields. | | `contentTypes` | `[String!]` | No | The list of content types to be queried. | | `query` | `String` | No | The search query. | #### Returns | Field | Type | Description | |-------|------|-------------| | `contentItems` | `[Content!]` | The paginated list of catalog content items matching the specified filters and sorting criteria. | | `meta` | `CatalogMeta!` | Metadata about the catalog query result, including pagination info, available sort options, display configuration, aggregations, and total count. | #### Example Request ```graphql query CatalogContent { CatalogContent( page: 1 sortColumn: createdAt sortDirection: desc resultsDisplayType: grid contentTypes: ["course", "webinar"] query: "leadership" ) { contentItems { id title slug kind status priceInCents description asset isActive publishDate waitlistingEnabled waitlistingTriggered tags { id label } prices { id currencyCode unitAmount isDefault } } meta { total hasMore resultsDisplayType selectedSortColumn selectedSortDirection contentTypes contentTypeFilterEnabled displayAuthorsEnabled isCurated } } } ``` #### Example Response ```json { "data": { "CatalogContent": { "contentItems": [ { "id": "content_001", "title": "Leadership Fundamentals", "slug": "leadership-fundamentals", "kind": "course", "status": "published", "priceInCents": 4999, "description": "An introductory course on leadership skills.", "asset": "https://cdn.acme.com/leadership.jpg", "isActive": true, "publishDate": "2024-01-15", "waitlistingEnabled": false, "waitlistingTriggered": false, "tags": [ { "id": "tag_001", "label": "Leadership" } ], "prices": [ { "id": "price_001", "currencyCode": "USD", "unitAmount": 4999, "isDefault": true } ] } ], "meta": { "total": 42, "hasMore": true, "resultsDisplayType": "grid", "selectedSortColumn": "createdAt", "selectedSortDirection": "desc", "contentTypes": [ "course", "webinar" ], "contentTypeFilterEnabled": true, "displayAuthorsEnabled": true, "isCurated": false } } }, "contentItems": null, "meta": null } ``` :::note This query has an additional cost of 3 points. Results are affected by providing an `authToken` header to set a user context. ::: --- ### CatalogQuery **`Query`** | Complexity: **3 pts × queryLimit** — Search catalog content with a query signature Performs a search across catalog content items using a free-text query and optional query signature for secure or pre-configured searches. This query is well-suited for search bars and programmatic content discovery where you need to control pagination, result limits, and exclude specific content items from results. Unlike `CatalogContent`, this query offers a simpler interface focused on text-based search and exclusion filtering rather than aggregation-based filtering. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `query` | `String` | No | The free-text search string used to find matching catalog content items. | | `querySignature` | `String` | No | A cryptographically signed string representing a pre-configured or secured search query. | | `querySort` | `String` | No | A string specifying the sort order for the search results (e.g., `title_asc`, `createdAt_desc`). | | `page` | `Int` | No | The page number to return within the collection. | | `queryExclusions` | `[String!]` | No | A list of content item IDs to exclude from the search results. | | `queryLimit` | `Int` | No | The maximum number of content items to return per page. | #### Returns | Field | Type | Description | |-------|------|-------------| | `contentItems` | `[Content!]` | The list of catalog content items matching the search query and filters. | | `meta` | `CatalogMeta!` | Metadata about the search result, including total count, pagination status, and active sort configuration. | #### Example Request ```graphql query CatalogQuery { CatalogQuery( query: "project management" page: 1 queryLimit: 10 queryExclusions: ["content_999"] querySort: "title_asc" ) { contentItems { id title slug kind status priceInCents description asset isActive publishDate waitlistingEnabled waitlistingTriggered } meta { total hasMore selectedSortColumn selectedSortDirection resultsDisplayType } } } ``` #### Example Response ```json { "data": { "CatalogQuery": { "contentItems": [ { "id": "content_042", "title": "Project Management Essentials", "slug": "project-management-essentials", "kind": "course", "status": "published", "priceInCents": 2999, "description": "Master the fundamentals of project management.", "asset": "https://cdn.acme.com/pm-essentials.jpg", "isActive": true, "publishDate": "2023-09-01", "waitlistingEnabled": false, "waitlistingTriggered": false } ], "meta": { "total": 5, "hasMore": false, "selectedSortColumn": "title", "selectedSortDirection": "asc", "resultsDisplayType": "grid" } } }, "contentItems": null, "meta": null } ``` --- ### CourseById **`Query`** | Complexity: **1 pt** — Fetch course by ID Retrieves the full metadata and structural outline of a single course by its unique identifier. Use this query when you need detailed information about a specific course, including its sections, lessons, pricing, enrollment windows, seat availability, prerequisite requirements, webinar details, and feature flags. The response is shaped by the authenticated user context — passing an `authToken` header will tailor certain fields (such as `currentUserHasAccess` and `currentUserDueDate`) to that user's perspective. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID!` | Yes | The ID of the course. This can fetched from CatalogContent > displayCourse. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the course. | | `createdAt` | `Date` | The date and time when the course was created. | | `updatedAt` | `Date` | The date and time when the course was last updated. | | `slug` | `Slug!` | The URL-friendly slug for the course. | | `status` | `Status` | The publication status of the course (e.g., draft, published, archived). | | `title` | `String` | The display title of the course. | | `sessionIsTitled` | `Boolean!` | Whether individual sessions within the course have titles. | | `sampleLesson` | `Lesson` | An optional free preview lesson associated with the course. | | `sampleLessonId` | `ID` | The ID of the sample/preview lesson. | | `sections` | `[Section]` | The list of sections (modules) that organize lessons within the course. | | `appearanceBlock` | `ID` | Reference to the appearance configuration block for the course. | | `appearanceHash` | `String` | A hash representing the current appearance configuration state. | | `paywallsBlock` | `ID` | Reference to the paywall configuration block for the course. | | `termsBlock` | `ID` | Reference to the terms and conditions block for the course. | | `confirmationBlock` | `ID` | Reference to the enrollment confirmation block for the course. | | `courseStartDate` | `Date` | The date when the course content becomes available to learners. | | `courseEndDate` | `Date` | The date when learner access to course content ends. | | `enrollmentStartDate` | `Date` | The date when enrollment opens for this course. | | `enrollmentEndDate` | `Date` | The date when enrollment closes for this course. | | `gracePeriodEndDate` | `Date` | The date after which access is fully revoked following course end. | | `seatsLimit` | `Int` | The maximum number of learners allowed to enroll. | | `waitlistCount` | `Int` | The number of learners currently on the waitlist. | | `seatsLimitMet` | `Boolean!` | Whether the enrollment seat limit has been reached. | | `waitlistingEnabled` | `Boolean!` | Whether waitlisting is enabled for this course. | | `waitlistingTriggered` | `Boolean!` | Whether the waitlist has been activated due to seats being full. | | `taxable` | `Boolean` | Whether the course is subject to tax. | | `purchasable` | `Boolean!` | Whether the course can be purchased. | | `taxCategory` | `ID` | Reference to the tax category applied to this course. | | `fulfillmentCenter` | `ID` | Reference to the fulfillment center associated with this course. | | `courseGroup` | `CourseGroup` | The CourseGroup this course belongs to, containing catalog-level metadata. | | `syllabusTitlesEnabled` | `Boolean` | Whether syllabus section titles are displayed to learners. | | `galleryEnabled` | `Boolean` | Whether the media gallery feature is enabled for this course. | | `workbookEnabled` | `Boolean` | Whether the workbook feature is enabled. | | `linkedWorkbookEnabled` | `Boolean` | Whether a linked workbook is enabled. | | `linkedWorkbookSkipEnabled` | `Boolean` | Whether learners can skip the linked workbook. | | `superquizEnabled` | `Boolean` | Whether the super quiz feature is enabled. | | `discussionsEnabled` | `Boolean` | Whether discussions/comments are enabled for this course. | | `resourcesEnabled` | `Boolean` | Whether the resources tab is enabled. | | `meetingsOverviewEnabled` | `Boolean` | Whether the meetings overview page is enabled. | | `recurrenceRule` | `String` | An iCal RRULE string describing the recurrence schedule for the course. | | `recurrence` | `JSON` | JSON object representing the recurrence configuration. | | `readableRecurrenceString` | `String` | A human-readable description of the recurrence schedule. | | `instructorAccessEnabled` | `Boolean` | Whether instructor-level access purchases are enabled. | | `instructorEmails` | `[String]` | List of instructor email addresses associated with the course. | | `freeWithRegistration` | `Boolean` | Whether the course is free upon site registration. | | `priceInCents` | `Int` | The course price in the smallest currency unit (e.g., cents for USD). | | `instructorAccessPriceInCents` | `Int` | The price for instructor-level access, in cents. | | `suggestedRetailPriceInCents` | `Int` | The suggested retail price before any discounts, in cents. | | `alternativePricingType` | `AlternativePricingType` | The alternative pricing model applied (e.g., membership). | | `alternativePricingRef` | `Int` | A reference identifier for the alternative pricing configuration. | | `accessDays` | `Int` | The number of days a learner retains access after enrollment. | | `showProgress` | `Boolean` | Whether the progress indicator is shown to learners. | | `annotationsEnabled` | `Boolean` | Whether learners can add annotations to course content. | | `forceLinearProgress` | `Boolean` | Whether learners must complete lessons in order. | | `completionTimePerPage` | `Int` | Minimum time (in seconds) a learner must spend on each page. | | `askLearnerPassword` | `Boolean` | Whether learners are prompted to set a password upon enrollment. | | `roster` | `Boolean` | Whether roster management is enabled for this course. | | `topicGroupId` | `ID` | Reference to the topic group used to categorize course topics. | | `sku` | `String` | The stock-keeping unit identifier for the course. | | `prerequisiteCourses` | `[Course]` | Courses that must be completed before this course can be accessed. | | `prerequisiteLearningPaths` | `[LearningPath]` | Learning paths that must be completed before this course can be accessed. | | `parentCourseId` | `ID` | The ID of the parent course, if this is a child/session course. | | `isChild` | `Boolean` | Whether this course is a child session of a parent course. | | `hasMultipleCurrencies` | `Boolean!` | Whether the course has pricing configured in multiple currencies. | | `prices` | `[ItemPrice!]!` | List of price objects for the course, supporting multiple currencies. | | `enrollmentActive` | `Boolean` | Whether enrollment is currently active based on dates and seat availability. | | `waitlistActive` | `Boolean` | Whether the waitlist is currently accepting sign-ups. | | `webinarDates` | `[DateRange]` | Date ranges for scheduled webinar sessions. | | `webinarTimeZone` | `String` | The time zone used for webinar scheduling. | | `meetings` | `[Meeting!]` | Scheduled in-person or virtual meeting sessions associated with the course. | | `inPersonEventInfo` | `InPersonEventInfo` | Additional details specific to in-person event courses. | | `currentUserHasAccess` | `Boolean!` | Whether the currently authenticated user has access to the course. | | `location` | `Location` | The physical location associated with this course, if applicable. | | `displayMap` | `Boolean!` | Whether a map of the location should be displayed. | | `publishDate` | `Date` | The date this course was published. | | `isActive` | `Boolean!` | Whether the course is currently active and accessible. | | `futurePublishDate` | `Date` | A scheduled future date when the course will be published. | | `balanceRequirement` | `Float` | The credit balance required to access this course. | | `awardClaimingOptions` | `[CourseAwardClaimingOption!]` | Award types and amounts learners can claim upon course completion. | | `availabilityStatus` | `String` | A string describing the current availability state of the course. | | `embeddedEnabled` | `Boolean` | Whether the course can be embedded in external pages. | | `displayResults` | `Boolean!` | Whether quiz or assessment results are displayed to learners. | | `bulkPurchasingEnabled` | `Boolean!` | Whether bulk seat purchasing is enabled for this course. | | `seatTiers` | `[SeatTier!]` | Tiered pricing options for bulk seat purchases. | | `seatPackages` | `[SeatPackage!]` | Pre-defined seat packages available for purchase. | | `lastTierPriceInCents` | `Int` | The price per seat in the highest bulk pricing tier, in cents. | | `webinarId` | `String` | External identifier for the associated webinar session. | | `hasChildren` | `Boolean!` | Whether this course has child session courses. | | `customFields` | `JSON` | A JSON object containing any custom metadata fields configured for the course. | | `kind` | `CourseGroupKind` | The content kind of the course (e.g., microCourse, webinar, inPersonEvent). | | `rosterSubmittedDate` | `Date` | The date when the attendance roster was submitted. | | `finalAssessment` | `String` | Identifier or reference for the course's final assessment. | | `scormCollectUserDetails` | `Boolean` | Whether user details are collected for SCORM reporting. | | `seatsUsedCount` | `Int!` | The number of seats currently in use. | | `seatsAllocatedCount` | `Int!` | The number of seats that have been allocated. | | `webinarDetails` | `WebinarBlock` | Detailed webinar configuration including provider, dates, and dial-in info. | | `showSyllabusWhileUnenrolled` | `Boolean` | Whether unenrolled users can view the course syllabus. | | `topicGroup` | `TopicGroup` | The topic group taxonomy associated with this course. | | `courseGroupTitle` | `String` | The title of the parent CourseGroup. | | `currentUserDueDate` | `Date` | The due date for the currently authenticated user to complete this course. | #### Example Request ```graphql query GetCourseById { CourseById(id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890") { id title slug status createdAt updatedAt priceInCents purchasable enrollmentActive seatsLimit seatsLimitMet waitlistingEnabled waitlistCount currentUserHasAccess isActive kind sections { id title slug status lessons { id title slug accessLevel } } courseGroup { id title slug } prices { id currencyCode unitAmount isDefault } } } ``` #### Example Response ```json { "data": { "CourseById": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "title": "Introduction to GraphQL", "slug": "intro-to-graphql", "status": "published", "createdAt": "2023-01-15T10:00:00Z", "updatedAt": "2024-03-20T14:30:00Z", "priceInCents": 4999, "purchasable": true, "enrollmentActive": true, "seatsLimit": 100, "seatsLimitMet": false, "waitlistingEnabled": false, "waitlistCount": 0, "currentUserHasAccess": false, "isActive": true, "kind": "courseGroup", "sections": [ { "id": "sec-001", "title": "Getting Started", "slug": "getting-started", "status": "published", "lessons": [ { "id": "les-001", "title": "What is GraphQL?", "slug": "what-is-graphql", "accessLevel": "open" } ] } ], "courseGroup": { "id": "cg-001", "title": "Introduction to GraphQL", "slug": "intro-to-graphql" }, "prices": [ { "id": "price-001", "currencyCode": "USD", "unitAmount": 4999, "isDefault": true } ] } }, "id": "abc-123", "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "slug": "example", "status": "draft", "title": "example", "sessionIsTitled": false, "sampleLesson": null, "sampleLessonId": "abc-123", "sections": null, "appearanceBlock": "abc-123", "appearanceHash": "example", "paywallsBlock": "abc-123", "termsBlock": "abc-123", "confirmationBlock": "abc-123", "courseStartDate": "2026-01-01T00:00:00Z", "courseEndDate": "2026-01-01T00:00:00Z", "enrollmentStartDate": "2026-01-01T00:00:00Z", "enrollmentEndDate": "2026-01-01T00:00:00Z", "gracePeriodEndDate": "2026-01-01T00:00:00Z", "seatsLimit": 0, "waitlistCount": 0, "seatsLimitMet": false, "waitlistingEnabled": false, "waitlistingTriggered": false, "taxable": false, "purchasable": false, "taxCategory": "abc-123", "fulfillmentCenter": "abc-123", "courseGroup": null, "syllabusTitlesEnabled": false, "galleryEnabled": false, "workbookEnabled": false, "linkedWorkbookEnabled": false, "linkedWorkbookSkipEnabled": false, "superquizEnabled": false, "discussionsEnabled": false, "resourcesEnabled": false, "meetingsOverviewEnabled": false, "recurrenceRule": "example", "recurrence": {}, "readableRecurrenceString": "example", "instructorAccessEnabled": false, "instructorEmails": [ "example" ], "freeWithRegistration": false, "priceInCents": 0, "instructorAccessPriceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "showProgress": false, "annotationsEnabled": false, "forceLinearProgress": false, "completionTimePerPage": 0, "askLearnerPassword": false, "roster": false, "topicGroupId": "abc-123", "sku": "example", "prerequisiteCourses": null, "prerequisiteLearningPaths": null, "parentCourseId": "abc-123", "isChild": false, "hasMultipleCurrencies": false, "prices": null, "enrollmentActive": false, "waitlistActive": false, "webinarDates": null, "webinarTimeZone": "example", "meetings": null, "inPersonEventInfo": null, "currentUserHasAccess": false, "location": null, "displayMap": false, "publishDate": "2026-01-01T00:00:00Z", "isActive": false, "futurePublishDate": "2026-01-01T00:00:00Z", "balanceRequirement": 0, "awardClaimingOptions": null, "availabilityStatus": "example", "embeddedEnabled": false, "displayResults": false, "bulkPurchasingEnabled": false, "seatTiers": null, "seatPackages": null, "lastTierPriceInCents": 0, "webinarId": "example", "hasChildren": false, "customFields": {}, "kind": "microCourse", "rosterSubmittedDate": "2026-01-01T00:00:00Z", "finalAssessment": "example", "scormCollectUserDetails": false, "seatsUsedCount": 0, "seatsAllocatedCount": 0, "webinarDetails": null, "showSyllabusWhileUnenrolled": false, "topicGroup": null, "courseGroupTitle": "example", "currentUserDueDate": "2026-01-01T00:00:00Z" } ``` :::note Providing an `authToken` header scopes user-specific fields such as `currentUserHasAccess` and `currentUserDueDate` to the identified learner. Without an auth token, those fields reflect the anonymous/default state. ::: --- ### CourseGroupBySlug **`Query`** | Complexity: **1 pt** — Fetch course group by slug Retrieves a CourseGroup and all of its associated metadata by its URL slug. A CourseGroup is the catalog-level container that groups one or more course sessions together and holds display information such as title, description, asset images, pricing, tags, layout, and related content. Use this query when building catalog detail pages or when you need to resolve a content item from a slug found in a URL. The query returns `null` if no CourseGroup matches the provided slug. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `slug` | `Slug!` | Yes | The slug for the Course Group being retrieved. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the CourseGroup. | | `authoringClientId` | `ID` | The ID of the client that originally authored this CourseGroup. | | `createdAt` | `Date` | The date and time the CourseGroup was created. | | `displayCourse` | `Course` | The primary course instance used for display and enrollment purposes. | | `displayBundle` | `DisplayBundle` | The subscription bundle associated with this CourseGroup for display purposes. | | `courses` | `[Course]` | All course sessions belonging to this CourseGroup. | | `asset` | `String` | URL of the card/thumbnail image for the CourseGroup. | | `assetAltText` | `String` | Accessibility alt text for the card image. | | `assetLibraryId` | `ID` | Reference to the media library entry for the card image. | | `detailAsset` | `String` | URL of the hero/detail page image. | | `detailAssetAltText` | `String` | Accessibility alt text for the detail image. | | `detailAssetLibraryId` | `ID` | Reference to the media library entry for the detail image. | | `videoAsset` | `String` | URL of the promotional video asset. | | `tabs` | `[CourseTab]` | Configurable content tabs shown on the course detail page. | | `description` | `String` | The marketing description of the CourseGroup. | | `title` | `String` | The display title of the CourseGroup. | | `archived` | `Boolean` | Whether the CourseGroup has been archived. | | `metaTitle` | `String` | SEO meta title for the CourseGroup detail page. | | `metaDescription` | `String` | SEO meta description for the CourseGroup detail page. | | `language` | `String` | The language code for the CourseGroup content. | | `slug` | `Slug!` | The URL-friendly slug for the CourseGroup. | | `rating` | `Int` | The average learner rating (typically 1–5). | | `ratingsCount` | `Int` | The total number of learner ratings submitted. | | `relatedCourseGroups` | `[CourseGroup]` | Other CourseGroups related to or recommended alongside this one. | | `relatedQuery` | `String` | A catalog search query string used to dynamically populate related content. | | `relatedQuerySort` | `String` | The sort order applied to the related content query. | | `relatedQueryLimit` | `Int` | The maximum number of results returned by the related content query. | | `relatedQuerySignature` | `String` | A signature hash for caching or validating the related content query. | | `relatedShowImageAsset` | `Boolean` | Whether to show image assets in the related content display. | | `relatedShowDescription` | `Boolean` | Whether to show descriptions in the related content display. | | `externalPurchaseUrl` | `String` | A URL to an external checkout or purchase page. | | `externalDetailUrl` | `String` | A URL to an external detail/landing page for this content. | | `layout` | `Layout` | The layout configuration used to render the CourseGroup detail page. | | `hasParentCourseGroup` | `Boolean` | Whether this CourseGroup is nested under a parent CourseGroup. | | `tags` | `[Tag]` | Tags associated with this CourseGroup for filtering and discovery. | | `customFields` | `JSON` | Custom metadata fields configured for this CourseGroup. | | `authors` | `[String]` | List of author names credited for this content. | | `source` | `String` | The originating source or publisher of the content. | | `isbn` | `String` | ISBN identifier, applicable for book-type content. | | `ribbon` | `Slug` | A slug identifying a ribbon/badge to display on the content card. | | `contentType` | `ContentType` | The content type classification for this CourseGroup. | | `kind` | `CourseGroupKind` | The content kind (e.g., courseGroup, webinar, article, inPersonEvent). | | `availabilityStatus` | `String` | A string indicating whether the content is available, coming soon, etc. | | `ownedByActiveScope` | `Boolean!` | Whether this CourseGroup is owned by the currently active client scope. | | `allowRescheduling` | `Boolean` | Whether learners are permitted to reschedule their enrollment. | | `currentUserMayReschedule` | `Boolean` | Whether the currently authenticated user is eligible to reschedule. | | `isTemplate` | `Boolean` | Whether this CourseGroup is a template used to create other content. | #### Example Request ```graphql query GetCourseGroupBySlug { CourseGroupBySlug(slug: "intro-to-graphql") { id title slug description status: kind language rating ratingsCount asset metaTitle metaDescription tags { id label } displayCourse { id priceInCents purchasable enrollmentActive currentUserHasAccess } layout { id kind hasEnrollmentWidget } contentType { label kind } authors customFields } } ``` #### Example Response ```json { "data": { "CourseGroupBySlug": { "id": "cg-abc123", "title": "Introduction to GraphQL", "slug": "intro-to-graphql", "description": "Learn the fundamentals of GraphQL in this hands-on course.", "kind": "courseGroup", "language": "en", "rating": 4, "ratingsCount": 32, "asset": "https://cdn.example.com/images/intro-graphql.jpg", "metaTitle": "Introduction to GraphQL | Example Academy", "metaDescription": "Master GraphQL queries, mutations, and schema design.", "tags": [ { "id": "tag-001", "label": "API Development" }, { "id": "tag-002", "label": "Backend" } ], "displayCourse": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "priceInCents": 4999, "purchasable": true, "enrollmentActive": true, "currentUserHasAccess": false }, "layout": { "id": "layout-001", "kind": "courseDetailSidebarLayout", "hasEnrollmentWidget": true }, "contentType": { "label": "Course", "kind": "course" }, "authors": [ "Jane Smith" ], "customFields": { "difficulty": "beginner" } } }, "id": "abc-123", "authoringClientId": "abc-123", "createdAt": "2026-01-01T00:00:00Z", "displayCourse": null, "displayBundle": null, "courses": null, "asset": "example", "assetAltText": "example", "assetLibraryId": "abc-123", "detailAsset": "example", "detailAssetAltText": "example", "detailAssetLibraryId": "abc-123", "videoAsset": "example", "tabs": null, "description": "example", "title": "example", "archived": false, "metaTitle": "example", "metaDescription": "example", "language": "example", "slug": "example", "rating": 0, "ratingsCount": 0, "relatedCourseGroups": null, "relatedQuery": "example", "relatedQuerySort": "example", "relatedQueryLimit": 0, "relatedQuerySignature": "example", "relatedShowImageAsset": false, "relatedShowDescription": false, "externalPurchaseUrl": "example", "externalDetailUrl": "example", "layout": null, "hasParentCourseGroup": false, "tags": null, "customFields": {}, "authors": [ "example" ], "source": "example", "isbn": "example", "ribbon": "example", "contentType": null, "kind": "microCourse", "availabilityStatus": "example", "ownedByActiveScope": false, "allowRescheduling": false, "currentUserMayReschedule": false, "isTemplate": false } ``` :::note Returns `null` if no CourseGroup exists for the given slug. The `displayCourse` field is the recommended starting point for enrollment and pricing data. ::: --- ### CourseGroupTestimonials **`Query`** | Complexity: **1 pt** — Fetch testimonials for a course group Returns all learner testimonials submitted for a specific CourseGroup, identified by its UUID. Each testimonial includes the review body text, a numerical rating, the date it was created, and the full profile of the user who submitted it. Use this query to populate social proof sections on course detail pages or to aggregate learner feedback for reporting purposes. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `UUID!` | Yes | The ID of the course group. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID` | The unique identifier of the testimonial. | | `createdAt` | `Date` | The date and time the testimonial was submitted. | | `user` | `User` | The user who submitted the testimonial, including their profile details. | | `body` | `String` | The text content of the testimonial review. | | `rating` | `Int` | The numerical rating given by the reviewer (typically 1–5). | #### Example Request ```graphql query GetCourseGroupTestimonials { CourseGroupTestimonials(id: "cg-abc123-def456-7890") { id createdAt rating body user { id name firstName lastName asset } } } ``` #### Example Response ```json { "data": { "CourseGroupTestimonials": [ { "id": "test-001", "createdAt": "2024-02-10T09:15:00Z", "rating": 5, "body": "This course completely changed how I think about API design. Highly recommended!", "user": { "id": "user-001", "name": "Alex Johnson", "firstName": "Alex", "lastName": "Johnson", "asset": "https://cdn.example.com/avatars/alex-johnson.jpg" } }, { "id": "test-002", "createdAt": "2024-01-28T16:40:00Z", "rating": 4, "body": "Very well structured with practical examples. A few topics could be covered in more depth.", "user": { "id": "user-002", "name": "Maria Chen", "firstName": "Maria", "lastName": "Chen", "asset": null } } ] }, "id": "abc-123", "createdAt": "2026-01-01T00:00:00Z", "user": null, "body": "example", "rating": 0 } ``` --- ### Pages **`Query`** | Complexity: **1 pt × identifiers** — Fetch page resources by identifiers Returns detailed data for one or more content pages by their identifier strings. The response is a union of strongly-typed page objects (e.g., `TextPage`, `QuizPage`, `VideoPage`, `AssignmentPage`) so you must use inline fragments to access type-specific fields. This query is useful when you need to render or inspect specific pages within a course without loading the full course structure. If an `authToken` header is provided, some fields — such as a learner's previous quiz responses — may be personalized to the authenticated user. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `identifiers` | `[String!]!` | Yes | An array of page identifier strings (typically topic IDs or slugs) for the pages to retrieve. | #### Returns | Field | Type | Description | |-------|------|-------------| | `Pages` | `[PageResource]!` | An array of page resource objects. Each item is one of many concrete page types (TextPage, QuizPage, VideoPage, etc.) resolved as a union. Use `__typename` and inline fragments to access type-specific fields. | #### Example Request ```graphql query Pages($identifiers: [String!]!) { Pages(identifiers: $identifiers) { __typename ... on TextPage { id title type lessonId createdAt updatedAt body sidebarIsHidden } ... on QuizPage { id title type lessonId createdAt updatedAt minPassingPercent maxAttempts timerEnabled timeLimitInSeconds questions { questionId body type choices { choiceId value correct } } } ... on VideoPage { id title type lessonId createdAt updatedAt asset } } } ``` #### Example Response ```json { "data": { "Pages": [ { "__typename": "QuizPage", "id": "page_quiz_789", "title": "Chapter 2 Knowledge Check", "type": "quiz", "lessonId": "lesson_456", "createdAt": "2024-01-10T08:00:00Z", "updatedAt": "2024-02-20T14:30:00Z", "minPassingPercent": 75, "maxAttempts": 3, "timerEnabled": false, "timeLimitInSeconds": null, "questions": [ { "questionId": "q_001", "body": "What does HTTP stand for?", "type": "multipleChoice", "choices": [ { "choiceId": "c_1", "value": "HyperText Transfer Protocol", "correct": true }, { "choiceId": "c_2", "value": "High Traffic Transport Process", "correct": false } ] } ] }, { "__typename": "TextPage", "id": "page_text_123", "title": "Introduction to Networking", "type": "text", "lessonId": "lesson_456", "createdAt": "2024-01-10T08:00:00Z", "updatedAt": "2024-01-15T09:00:00Z", "body": "Welcome to the networking module...", "sidebarIsHidden": false } ] }, "members": null } ``` :::note The return type is a GraphQL union. Always include `__typename` and use inline fragments (e.g., `... on QuizPage { }`) to access fields specific to each page type. ::: --- ### QueryContents **`Query`** | Complexity: **3 pts** — Fetch multiple content items by ID Retrieves detailed information for one or more content items by their IDs in a single request. This is useful when you already know the IDs of specific pieces of content — for example, when rendering a curated list, a learning path's contents, or a shopping cart — and need to fetch their full details including pricing, tags, enrollment data, and availability. This query has an additional cost of 3 points. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `ids` | `[ID!]!` | Yes | A required list of content item IDs to retrieve. At least one ID must be provided. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier for the content item. | | `title` | `String` | Display title of the content item. | | `slug` | `Slug!` | URL-friendly identifier for the content item. | | `kind` | `ContentKind` | The type of content (e.g., course, webinar, learningPath, inPersonEvent). | | `status` | `Status` | The publication status of the content item. | | `priceInCents` | `Int` | Price of the content item in cents. | | `prices` | `[ItemPrice!]` | Detailed pricing information including currency, amounts, and bulk purchase tiers. | | `tags` | `[Tag]` | Tags associated with the content item. | | `location` | `Location` | Physical location details for in-person events. | | `isActive` | `Boolean!` | Whether the content item is currently active. | | `hasChildren` | `Boolean!` | Whether the content item has child items. | | `enrollmentCount` | `Int` | Number of users currently enrolled in the content item. | | `seatsLimit` | `Int` | Maximum number of seats available for this content item. | | `waitlistingEnabled` | `Boolean!` | Whether waitlisting is enabled for this content item. | | `waitlistingTriggered` | `Boolean!` | Whether the waitlist has been triggered due to capacity. | #### Example Request ```graphql query QueryContents { QueryContents(ids: ["content_001", "content_002", "content_003"]) { id title slug kind status description asset priceInCents isActive hasChildren publishDate courseStartDate courseEndDate enrollmentCount seatsLimit waitlistingEnabled waitlistingTriggered tags { id label isFolder } prices { id currencyCode unitAmount isDefault variations { label unitAmount } } location { id name address1 city state country } } } ``` #### Example Response ```json { "data": { "QueryContents": [ { "id": "content_001", "title": "Leadership Fundamentals", "slug": "leadership-fundamentals", "kind": "course", "status": "published", "description": "An introductory course on leadership skills.", "asset": "https://cdn.acme.com/leadership.jpg", "priceInCents": 4999, "isActive": true, "hasChildren": false, "publishDate": "2024-01-15", "courseStartDate": null, "courseEndDate": null, "enrollmentCount": 312, "seatsLimit": null, "waitlistingEnabled": false, "waitlistingTriggered": false, "tags": [ { "id": "tag_001", "label": "Leadership", "isFolder": false } ], "prices": [ { "id": "price_001", "currencyCode": "USD", "unitAmount": 4999, "isDefault": true, "variations": [] } ], "location": null }, { "id": "content_002", "title": "In-Person Workshop: Agile Methods", "slug": "agile-methods-workshop", "kind": "inPersonEvent", "status": "published", "description": "A hands-on Agile workshop held in-person.", "asset": "https://cdn.acme.com/agile.jpg", "priceInCents": 29900, "isActive": true, "hasChildren": false, "publishDate": "2024-03-01", "courseStartDate": "2024-06-10", "courseEndDate": "2024-06-11", "enrollmentCount": 18, "seatsLimit": 25, "waitlistingEnabled": true, "waitlistingTriggered": false, "tags": [], "prices": [ { "id": "price_002", "currencyCode": "USD", "unitAmount": 29900, "isDefault": true, "variations": [] } ], "location": { "id": "loc_001", "name": "Acme Conference Center", "address1": "123 Main St", "city": "Austin", "state": "TX", "country": "US" } } ] }, "id": "abc-123", "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "expiresAt": "2026-01-01T00:00:00Z", "courseGroup": "example", "title": "example", "sessionTitle": "example", "ribbon": null, "slug": "example", "kind": "courseGroup", "metaTitle": "example", "language": "example", "metaDescription": "example", "displayCourse": "abc-123", "displayCourseSlug": "example", "displayDate": "2026-01-01T00:00:00Z", "availabilityStatus": "example", "asset": "example", "assetAltText": "example", "freeWithRegistration": false, "priceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "rating": 0, "description": "example", "sku": "example", "hasChildren": false, "tags": null, "status": "draft", "customFields": {}, "authors": [ "example" ], "authorsAndInstructors": [ "example" ], "contentTypeLabel": "example", "contentTypeAssetAspectRatio": "example", "waitlistCount": 0, "seatsLimit": 0, "enrollmentCount": 0, "source": "example", "publishDate": "2026-01-01T00:00:00Z", "courseStartDate": "2026-01-01T00:00:00Z", "courseEndDate": "2026-01-01T00:00:00Z", "enrollmentStartDate": "2026-01-01T00:00:00Z", "enrollmentEndDate": "2026-01-01T00:00:00Z", "timeZone": "example", "meetingStartDate": "2026-01-01T00:00:00Z", "coursePresold": false, "courseGracePeriodEnded": false, "currentUserMayReschedule": false, "isActive": false, "embeddedEnabled": false, "altDescriptionBody": "example", "hideCourseDescription": false, "annotationsEnabled": false, "currentUserUnmetCoursePrerequisites": [ "abc-123" ], "currentUserUnmetLearningPathPrerequisites": [ "abc-123" ], "currentUserDueDate": "2026-01-01T00:00:00Z", "url": "example", "waitlistingEnabled": false, "waitlistingTriggered": false, "canAddToQueue": false, "bulkPurchasingEnabled": false, "prices": null, "location": null, "state": "example", "acceptBadgeUrl": "example", "issuedAt": "2026-01-01T00:00:00Z", "badgeUrl": "example", "credlyBadgeExpiresAt": "2026-01-01T00:00:00Z", "imageUrl": "example", "badgeName": "example", "completedPassedDueDate": false } ``` :::note This query has an additional cost of 3 points. ::: --- ### RelatedProducts **`Query`** | Complexity: **1 pt** — Fetch products related to courses and products Returns a list of products that are related to a given set of courses and products. Related products are configured per-course in the course authoring interface under Design → Edit Catalog Settings → Detail Page. Use this query to power "you might also like" or "related content" sections on course detail pages. Both `productIds` and `courseIds` are required; supply empty arrays if you only want to filter by one dimension. Each returned product includes pricing, status, imagery, and its own nested related products. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `productIds` | `[ID!]!` | Yes | An array of product IDs to use as the basis for finding related products. Pass an empty array if filtering only by course IDs. | | `courseIds` | `[ID!]!` | Yes | An array of course IDs whose configured related products should be returned. Pass an empty array if filtering only by product IDs. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the product. | | `asset` | `URL` | The URL of the product's cover image or thumbnail asset. | | `assetAltText` | `String` | Accessibility alt text for the product's image asset. | | `assetLibraryId` | `ID` | The ID of the asset in the media library, if applicable. | | `status` | `Status` | The publication status of the product (e.g., `published`, `draft`, `archived`). | | `purchasable` | `Boolean` | Indicates whether the product is available for purchase. | | `name` | `String` | The display name of the product. | | `longDescription` | `String` | A detailed description of the product. | | `priceInCents` | `Int` | The sale price of the product in cents (e.g., 4999 = $49.99). | | `language` | `String` | The language code of the product's content. | | `suggestedRetailPriceInCents` | `Int` | The suggested retail price of the product in cents, used for displaying a crossed-out "original" price. | | `alternativePricingType` | `AlternativePricingType` | An alternative pricing model applied to the product, such as `membership`. | | `alternativePricingRef` | `Int` | A reference identifier for the alternative pricing configuration (e.g., membership tier ID). | | `relatedProducts` | `[Product]` | A nested list of products related to this product, following the same structure. | #### Example Request ```graphql query RelatedProducts { RelatedProducts( productIds: ["prod_abc123", "prod_def456"] courseIds: ["course_789xyz"] ) { id name status purchasable priceInCents suggestedRetailPriceInCents alternativePricingType alternativePricingRef language asset assetAltText assetLibraryId longDescription relatedProducts { id name status priceInCents purchasable } } } ``` #### Example Response ```json { "data": { "RelatedProducts": [ { "id": "prod_abc123", "name": "Advanced GraphQL Masterclass", "status": "published", "purchasable": true, "priceInCents": 4999, "suggestedRetailPriceInCents": 7999, "alternativePricingType": "membership", "alternativePricingRef": 12, "language": "en", "asset": "https://cdn.example.com/images/graphql-masterclass.jpg", "assetAltText": "Abstract network diagram representing GraphQL", "assetLibraryId": "lib_img_001", "longDescription": "Take your GraphQL skills to the next level with hands-on exercises and real-world projects.", "relatedProducts": [ { "id": "prod_ghi789", "name": "REST to GraphQL Migration Guide", "status": "published", "priceInCents": 2999, "purchasable": true } ] } ] }, "id": "abc-123", "asset": "https://example.com", "assetAltText": "example", "assetLibraryId": "abc-123", "status": "draft", "purchasable": false, "name": "example", "longDescription": "example", "priceInCents": 0, "language": "example", "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "relatedProducts": null } ``` --- ### RusticiLaunchScorm **`Query`** | Complexity: **1 pt** — Launch a Rustici SCORM course Generates a launch URL and associated metadata for playing a SCORM or xAPI content object managed by the Rustici SCORM engine. Provide the ID of the topic or course and specify whether the launch is a preview. When `isPreview` is `true`, the current user must have an admin or manager role. The response includes the launch URL, embed dimensions, a registration ID, a JWT for checking registration status, and the endpoint to poll for completion updates. Use this query when building custom SCORM player integrations. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` _(deprecated)_ | `ID` | No | Optional ID of a related content or enrollment record to associate with the launch. | | `topicOrCourseId` | `ID!` | Yes | The ID of the topic or course containing the SCORM content to launch. | | `type` | `ContentOrTopicEnum` | No | The type of the Topic Or Course. | | `isPreview` | `Boolean!` | Yes | Requests the preview URL to launch SCORM content. This requires the current user to be in admin or manager role. | #### Returns | Field | Type | Description | |-------|------|-------------| | `isRusticiCourse` | `Boolean!` | Indicates whether the content is managed by the Rustici SCORM engine. | | `url` | `AbsoluteOrRelativeURL!` | The URL used to launch the SCORM content in a player. | | `fullscreenEmbed` | `Boolean!` | Whether the SCORM content should be launched in fullscreen embed mode. | | `height` | `Int` | Recommended embed height in pixels for the SCORM player iframe. | | `width` | `Int` | Recommended embed width in pixels for the SCORM player iframe. | | `registrationId` | `ID!` | The Rustici registration ID associated with this user's launch session. | | `courseTitle` | `String` | The title of the SCORM course being launched. | | `registrationCheckerJWT` | `String!` | A JWT used to authenticate polling requests to the registration checker endpoint. | | `registrationCheckerEndpoint` | `URL!` | The endpoint URL to poll for updates on the user's registration and completion status. | | `currentUserDueDate` | `Date` | The due date by which the current user must complete the content, if applicable. | #### Example Request ```graphql query RusticiLaunchScorm { RusticiLaunchScorm( topicOrCourseId: "topic_abc789" type: topic isPreview: false ) { isRusticiCourse url fullscreenEmbed height width registrationId courseTitle registrationCheckerJWT registrationCheckerEndpoint currentUserDueDate } } ``` #### Example Response ```json { "data": { "RusticiLaunchScorm": { "isRusticiCourse": true, "url": "https://cloud.scorm.com/ScormEngineInterface/defaultui/launch.htm?registration=reg_001&redirectOnExitUrl=https%3A%2F%2Facme.thoughtindustries.com", "fullscreenEmbed": false, "height": 768, "width": 1024, "registrationId": "reg_001abc", "courseTitle": "Workplace Safety 101", "registrationCheckerJWT": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.placeholder", "registrationCheckerEndpoint": "https://api.acme.thoughtindustries.com/rustici/registration-status", "currentUserDueDate": "2025-12-31" } }, "isRusticiCourse": false, "url": "example", "fullscreenEmbed": false, "height": 0, "width": 0, "registrationId": "abc-123", "courseTitle": "example", "registrationCheckerJWT": "example", "registrationCheckerEndpoint": "https://example.com", "currentUserDueDate": "2026-01-01T00:00:00Z" } ``` :::note When `isPreview` is `true`, the requesting user must have admin or manager role. Preview launches do not record completion data. ::: --- ### AddResourceToQueue **`Mutation`** | Complexity: **1 pt** — Add resource to current user's queue Adds a content resource (such as a course) to the current user's watch queue so they can return to it later. The user must already have access to the content before it can be queued. Returns `true` on success. Pass an `authToken` header to add content to the queue of a specific user rather than the caller. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `resourceType` | `ContentKind` | No | The type of content that is to be added to the queue of the current user. | | `resourceId` | `ID!` | Yes | The ID for the resource that is to be added to the queue of the current user. For example, this could be a course ID that the user would like to view at a later time. | #### Returns | Field | Type | Description | |-------|------|-------------| | `AddResourceToQueue` | `Boolean!` | Returns `true` if the resource was successfully added to the current user's queue. | #### Example Request ```graphql mutation AddResourceToQueue { AddResourceToQueue( resourceType: course resourceId: "abc123def456" ) } ``` #### Example Response ```json { "data": { "AddResourceToQueue": true } } ``` :::note The user must have been granted access to the content item before it can be added to their queue. Supply an `authToken` request header to target a specific user. ::: --- ### CopyCourse **`Mutation`** | Complexity: **1 pt** — Copy a course to create a new session Creates a new course session by copying an existing course identified by `idToCopy`. The resulting course is a new independent record with the provided title and optional start/end dates. This is useful for scheduling recurring sessions of the same course content without rebuilding the curriculum from scratch. The copied course inherits content structure from the source but is otherwise independent. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `idToCopy` | `UUID!` | Yes | The UUID of the existing course to use as the source for the copy. | | `title` | `String!` | Yes | The display title for the newly created course session. | | `startDate` | `Date` | No | Optional start date to set on the new course session. | | `endDate` | `Date` | No | Optional end date to set on the new course session. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier of the newly created course copy. | | `title` | `String` | Display title of the new course session. | | `status` | `Status` | Publication status of the new course (typically 'draft' upon creation). | | `slug` | `Slug!` | URL-friendly slug for the new course. | | `createdAt` | `Date` | Timestamp when the course copy was created. | | `courseStartDate` | `Date` | The scheduled start date of the new session. | | `courseEndDate` | `Date` | The scheduled end date of the new session. | | `courseGroup` | `CourseGroup` | The course group this session belongs to. | #### Example Request ```graphql mutation CopyCourse($idToCopy: UUID!, $title: String!, $startDate: Date, $endDate: Date) { CopyCourse( idToCopy: $idToCopy title: $title startDate: $startDate endDate: $endDate ) { id title status slug createdAt courseStartDate courseEndDate courseGroup { id title } } } ``` #### Example Response ```json { "data": { "CopyCourse": { "id": "course_00000002-0000-0000-0000-000000000001", "title": "Advanced API Development – Fall 2025", "status": "draft", "slug": "advanced-api-development-fall-2025", "createdAt": "2024-05-15T08:00:00Z", "courseStartDate": "2025-09-01", "courseEndDate": "2025-11-30", "courseGroup": { "id": "coursegroup_001", "title": "Advanced API Development" } } }, "id": "abc-123", "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "slug": "example", "status": "draft", "title": "example", "sessionIsTitled": false, "sampleLesson": null, "sampleLessonId": "abc-123", "sections": null, "appearanceBlock": "abc-123", "appearanceHash": "example", "paywallsBlock": "abc-123", "termsBlock": "abc-123", "confirmationBlock": "abc-123", "courseStartDate": "2026-01-01T00:00:00Z", "courseEndDate": "2026-01-01T00:00:00Z", "enrollmentStartDate": "2026-01-01T00:00:00Z", "enrollmentEndDate": "2026-01-01T00:00:00Z", "gracePeriodEndDate": "2026-01-01T00:00:00Z", "seatsLimit": 0, "waitlistCount": 0, "seatsLimitMet": false, "waitlistingEnabled": false, "waitlistingTriggered": false, "taxable": false, "purchasable": false, "taxCategory": "abc-123", "fulfillmentCenter": "abc-123", "courseGroup": null, "syllabusTitlesEnabled": false, "galleryEnabled": false, "workbookEnabled": false, "linkedWorkbookEnabled": false, "linkedWorkbookSkipEnabled": false, "superquizEnabled": false, "discussionsEnabled": false, "resourcesEnabled": false, "meetingsOverviewEnabled": false, "recurrenceRule": "example", "recurrence": {}, "readableRecurrenceString": "example", "instructorAccessEnabled": false, "instructorEmails": [ "example" ], "freeWithRegistration": false, "priceInCents": 0, "instructorAccessPriceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "showProgress": false, "annotationsEnabled": false, "forceLinearProgress": false, "completionTimePerPage": 0, "askLearnerPassword": false, "roster": false, "topicGroupId": "abc-123", "sku": "example", "prerequisiteCourses": null, "prerequisiteLearningPaths": null, "parentCourseId": "abc-123", "isChild": false, "hasMultipleCurrencies": false, "prices": null, "enrollmentActive": false, "waitlistActive": false, "webinarDates": null, "webinarTimeZone": "example", "meetings": null, "inPersonEventInfo": null, "currentUserHasAccess": false, "location": null, "displayMap": false, "publishDate": "2026-01-01T00:00:00Z", "isActive": false, "futurePublishDate": "2026-01-01T00:00:00Z", "balanceRequirement": 0, "awardClaimingOptions": null, "availabilityStatus": "example", "embeddedEnabled": false, "displayResults": false, "bulkPurchasingEnabled": false, "seatTiers": null, "seatPackages": null, "lastTierPriceInCents": 0, "webinarId": "example", "hasChildren": false, "customFields": {}, "kind": "microCourse", "rosterSubmittedDate": "2026-01-01T00:00:00Z", "finalAssessment": "example", "scormCollectUserDetails": false, "seatsUsedCount": 0, "seatsAllocatedCount": 0, "webinarDetails": null, "showSyllabusWhileUnenrolled": false, "topicGroup": null, "courseGroupTitle": "example", "currentUserDueDate": "2026-01-01T00:00:00Z" } ``` :::note The newly created course starts in draft status. Content structure (sections, lessons, topics) is copied from the source course. Enrollment records and learner progress are not copied. ::: --- ### CreateCourseMeetings **`Mutation`** | Complexity: **1 pt** — Create meetings for a course in-person event This mutation creates one or more meeting sessions for an existing non-recurring in-person event course. Each meeting requires at minimum a `title`, `startDate`, and `endDate` (provided via the `meetings` input array). You can optionally instruct the API to automatically create a meeting completion criterion for the course by passing `createMeetingCompletionCriteria: true`. The mutation returns the full list of newly created `Meeting` objects, each containing scheduling details, location, instructor info, and a reference back to the parent course. The behavior may be influenced by the `authToken` header used to set the acting user. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `courseId` | `UUID!` | Yes | The UUID of the in-person event course to which the meetings will be added. | | `createMeetingCompletionCriteria` | `Boolean` | No | When set to true, automatically creates a completion criterion requiring attendance at this meeting. | | `meetings` | `[MeetingFields!]` | No | All meetings you want to add to the event. `title`, `startDate`, and `endDate` are required for each meeting. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the meeting. | | `title` | `String` | The display title of the meeting. | | `courseId` | `ID!` | The ID of the course (in-person event) this meeting belongs to. | | `course` | `Course!` | The full course object associated with this meeting. | | `locationId` | `ID` | The ID of the location associated with this meeting, if any. | | `location` | `Location` | The physical location details for this meeting. | | `joinUrl` | `String` | A URL attendees can use to join the meeting (e.g. a virtual meeting link). | | `attendeeInfo` | `String` | Additional information provided to attendees about this meeting. | | `instructors` | `[String!]` | A list of instructor email addresses or names associated with this meeting. | | `startDate` | `Date!` | The date and time when the meeting begins. | | `endDate` | `Date!` | The date and time when the meeting ends. | | `timeZone` | `String` | The time zone in which the meeting's start and end dates are expressed. | | `attendees` | `[User!]` | The list of users who have attended or are registered for this meeting. | #### Example Request ```graphql mutation CreateCourseMeetings { CreateCourseMeetings( courseId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" createMeetingCompletionCriteria: true meetings: [ { title: "Day 1 Session" startDate: "2024-09-15T09:00:00Z" endDate: "2024-09-15T17:00:00Z" instructors: ["jane.doe@example.com"] joinUrl: "https://meet.example.com/session-1" } ] ) { id title courseId startDate endDate timeZone instructors joinUrl attendeeInfo location { id name address1 city state country } } } ``` #### Example Response ```json { "data": { "CreateCourseMeetings": [ { "id": "meet-uuid-001", "title": "Day 1 Session", "courseId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "startDate": "2024-09-15T09:00:00Z", "endDate": "2024-09-15T17:00:00Z", "timeZone": "America/Chicago", "instructors": [ "jane.doe@example.com" ], "joinUrl": "https://meet.example.com/session-1", "attendeeInfo": null, "location": null } ] }, "id": "abc-123", "title": "example", "courseId": "abc-123", "course": null, "locationId": "abc-123", "location": null, "joinUrl": "example", "attendeeInfo": "example", "instructors": [ "example" ], "startDate": "2026-01-01T00:00:00Z", "endDate": "2026-01-01T00:00:00Z", "timeZone": "example", "attendees": null } ``` :::note This mutation only works on non-recurring in-person event courses. Attempting to use it on a recurring event will result in an error. The `authToken` header can be used to perform the operation as a specific user. ::: --- ### CreateInPersonEvent **`Mutation`** | Complexity: **1 pt** — Create an in-person event Creates a new in-person event course with the specified title, date range, and one or more meeting sessions. Use this mutation when you need to programmatically provision instructor-led, physical training events. The `meetings` argument defines the individual session schedule. An optional `recurrence` argument accepts an iCalendar RRULE string to generate recurring sessions. The `contentType` argument allows you to classify the event under a specific content type label. An `authToken` header can be used to scope the operation to a particular user. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `title` | `String!` | Yes | The display title of the in-person event. | | `language` | `String` | No | The BCP 47 language code for the event content (e.g., 'en', 'fr'). | | `startDate` | `Date!` | Yes | The start date and time of the event. | | `endDate` | `Date!` | Yes | The end date and time of the event. | | `contentType` | `String` | No | A string identifying the content type label to assign to this event. | | `meetings` | `[MeetingFields!]!` | Yes | An array of meeting session objects defining the schedule of individual sessions within the event. | | `recurrence` | `String` | No | RRULE string in the format defined by the iCalendar RFC. For example, these would be valid strings: `FREQ=DAILY;INTERVAL=2;COUNT=4`, `FREQ=WEEKLY;COUNT=5`, `FREQ=YEARLY;COUNT=10`. To get more information on formatting, see the documentation here [https://datatracker.ietf.org/doc/html/rfc5545] | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the newly created in-person event course. | | `createdAt` | `Date` | Timestamp when the event was created. | | `updatedAt` | `Date` | Timestamp when the event was last updated. | | `slug` | `Slug!` | The URL-friendly slug for the event. | | `status` | `Status` | Publication status of the event (e.g., draft, published). | | `title` | `String` | The display title of the event. | | `courseStartDate` | `Date` | The date and time when the event begins. | | `courseEndDate` | `Date` | The date and time when the event ends. | | `enrollmentStartDate` | `Date` | When enrollment for the event opens. | | `enrollmentEndDate` | `Date` | When enrollment for the event closes. | | `seatsLimit` | `Int` | Maximum number of attendees allowed. | | `meetings` | `[Meeting!]` | The scheduled meeting sessions associated with this event. | | `inPersonEventInfo` | `InPersonEventInfo` | Additional in-person event metadata such as subtitle and hero image. | | `location` | `Location` | The physical location where the event takes place. | | `displayMap` | `Boolean!` | Whether to display a map for the event location. | | `isActive` | `Boolean!` | Whether the event is currently active and accessible. | | `kind` | `CourseGroupKind` | The kind of course group, which will be inPersonEvent or inPersonEventCourse. | | `recurrenceRule` | `String` | The RRULE string defining recurrence for the event. | | `recurrence` | `JSON` | Structured recurrence configuration as a JSON object. | | `readableRecurrenceString` | `String` | A human-readable description of the recurrence pattern. | | `courseGroup` | `CourseGroup` | The parent course group this event session belongs to. | | `purchasable` | `Boolean!` | Whether the event can be purchased by learners. | | `priceInCents` | `Int` | The price of the event in cents. | | `prices` | `[ItemPrice!]!` | Multi-currency pricing options for the event. | | `seatsLimitMet` | `Boolean!` | Whether the event has reached its seat capacity. | | `waitlistingEnabled` | `Boolean!` | Whether waitlisting is enabled for this event. | | `currentUserHasAccess` | `Boolean!` | Whether the currently authenticated user has access to the event. | | `availabilityStatus` | `String` | A string describing the current enrollment availability state. | | `customFields` | `JSON` | Custom field data associated with this event. | #### Example Request ```graphql mutation CreateInPersonEvent { CreateInPersonEvent( title: "Advanced Leadership Workshop" language: "en" startDate: "2024-09-15T09:00:00Z" endDate: "2024-09-15T17:00:00Z" contentType: "in-person-event" meetings: [ { title: "Morning Session" startDate: "2024-09-15T09:00:00Z" endDate: "2024-09-15T12:00:00Z" timeZone: "America/New_York" instructors: ["Dr. Alice Johnson"] attendeeInfo: "Please bring a laptop and your workbook." } ] ) { id title status courseStartDate courseEndDate meetings { id title startDate endDate timeZone instructors } location { id name address1 city state country } kind isActive } } ``` #### Example Response ```json { "data": { "CreateInPersonEvent": { "id": "crs_01HX9Z8Y7W6V5U4T", "title": "Advanced Leadership Workshop", "status": "draft", "courseStartDate": "2024-09-15T09:00:00Z", "courseEndDate": "2024-09-15T17:00:00Z", "meetings": [ { "id": "mtg_01HX9Z8Y7W6V5U4U", "title": "Morning Session", "startDate": "2024-09-15T09:00:00Z", "endDate": "2024-09-15T12:00:00Z", "timeZone": "America/New_York", "instructors": [ "Dr. Alice Johnson" ] } ], "location": null, "kind": "inPersonEvent", "isActive": false } }, "id": "abc-123", "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "slug": "example", "status": "draft", "title": "example", "sessionIsTitled": false, "sampleLesson": null, "sampleLessonId": "abc-123", "sections": null, "appearanceBlock": "abc-123", "appearanceHash": "example", "paywallsBlock": "abc-123", "termsBlock": "abc-123", "confirmationBlock": "abc-123", "courseStartDate": "2026-01-01T00:00:00Z", "courseEndDate": "2026-01-01T00:00:00Z", "enrollmentStartDate": "2026-01-01T00:00:00Z", "enrollmentEndDate": "2026-01-01T00:00:00Z", "gracePeriodEndDate": "2026-01-01T00:00:00Z", "seatsLimit": 0, "waitlistCount": 0, "seatsLimitMet": false, "waitlistingEnabled": false, "waitlistingTriggered": false, "taxable": false, "purchasable": false, "taxCategory": "abc-123", "fulfillmentCenter": "abc-123", "courseGroup": null, "syllabusTitlesEnabled": false, "galleryEnabled": false, "workbookEnabled": false, "linkedWorkbookEnabled": false, "linkedWorkbookSkipEnabled": false, "superquizEnabled": false, "discussionsEnabled": false, "resourcesEnabled": false, "meetingsOverviewEnabled": false, "recurrenceRule": "example", "recurrence": {}, "readableRecurrenceString": "example", "instructorAccessEnabled": false, "instructorEmails": [ "example" ], "freeWithRegistration": false, "priceInCents": 0, "instructorAccessPriceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "showProgress": false, "annotationsEnabled": false, "forceLinearProgress": false, "completionTimePerPage": 0, "askLearnerPassword": false, "roster": false, "topicGroupId": "abc-123", "sku": "example", "prerequisiteCourses": null, "prerequisiteLearningPaths": null, "parentCourseId": "abc-123", "isChild": false, "hasMultipleCurrencies": false, "prices": null, "enrollmentActive": false, "waitlistActive": false, "webinarDates": null, "webinarTimeZone": "example", "meetings": null, "inPersonEventInfo": null, "currentUserHasAccess": false, "location": null, "displayMap": false, "publishDate": "2026-01-01T00:00:00Z", "isActive": false, "futurePublishDate": "2026-01-01T00:00:00Z", "balanceRequirement": 0, "awardClaimingOptions": null, "availabilityStatus": "example", "embeddedEnabled": false, "displayResults": false, "bulkPurchasingEnabled": false, "seatTiers": null, "seatPackages": null, "lastTierPriceInCents": 0, "webinarId": "example", "hasChildren": false, "customFields": {}, "kind": "microCourse", "rosterSubmittedDate": "2026-01-01T00:00:00Z", "finalAssessment": "example", "scormCollectUserDetails": false, "seatsUsedCount": 0, "seatsAllocatedCount": 0, "webinarDetails": null, "showSyllabusWhileUnenrolled": false, "topicGroup": null, "courseGroupTitle": "example", "currentUserDueDate": "2026-01-01T00:00:00Z" } ``` :::note The `recurrence` argument accepts an iCalendar RFC 5545 RRULE string (e.g., `FREQ=WEEKLY;COUNT=5`). The created event starts in `draft` status and must be published separately. An `authToken` header can scope the operation to a specific user. ::: --- ### CreateLocation **`Mutation`** | Complexity: **1 pt** — Create a location for in-person events Creates a new physical location record that can be attached to in-person event (ILT) sessions and meetings. Required fields include the location name, street address, and city. Optional fields cover room/suite, secondary address line, state, zip code, country, and time zone. The time zone should be a valid IANA time zone ID (e.g., `America/Chicago`), and the country should be a two-letter ISO 3166 code. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `name` | `String!` | Yes | The display name for the location, such as a venue or building name. | | `room` | `String` | No | An optional room, suite, or floor designation within the location. | | `address1` | `String!` | Yes | The primary street address for the location. | | `address2` | `String` | No | An optional secondary address line (e.g., suite number or building name). | | `city` | `String!` | Yes | The city in which the location is situated. | | `state` | `String` | No | For US states, 2-letter IANA state code | | `zipCode` | `String` | No | The postal or ZIP code for the location. | | `country` | `String` | No | 2-letter ISO 3166 country code | | `timeZone` | `String` | No | IANA time zone ID (ex. America/New_York) | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier of the newly created location. | | `name` | `String!` | Display name of the location (e.g., venue or building name). | | `room` | `String` | Optional room or suite designation within the building. | | `address1` | `String!` | Primary street address line. | | `address2` | `String` | Secondary street address line (e.g., suite or floor number). | | `city` | `String!` | City where the location resides. | | `state` | `String` | State or province; use two-letter IANA state code for US states. | | `zipCode` | `String` | Postal/ZIP code for the location. | | `country` | `String` | Two-letter ISO 3166 country code. | | `timeZone` | `String` | IANA time zone ID for the location (e.g., America/New_York). | #### Example Request ```graphql mutation CreateLocation { CreateLocation( name: "Downtown Training Center" address1: "123 Main Street" room: "Suite 400" city: "Chicago" state: "IL" zipCode: "60601" country: "US" timeZone: "America/Chicago" ) { id name room address1 address2 city state zipCode country timeZone } } ``` #### Example Response ```json { "data": { "CreateLocation": { "id": "loc_00000001-0000-0000-0000-000000000001", "name": "Downtown Training Center", "room": "Suite 400", "address1": "123 Main Street", "address2": null, "city": "Chicago", "state": "IL", "zipCode": "60601", "country": "US", "timeZone": "America/Chicago" } }, "id": "abc-123", "name": "example", "room": "example", "address1": "example", "address2": "example", "city": "example", "state": "example", "zipCode": "example", "country": "example", "timeZone": "example" } ``` :::note Once created, this location can be referenced by its ID when creating or updating in-person event meetings. The `state` field expects a two-letter IANA code for US states. The `country` field expects a two-letter ISO 3166 code. ::: --- ### CurrentUserResetCourseProgress **`Mutation`** | Complexity: **1 pt** — Reset a user's course progress Resets the course progress for a specified learner on the course identified by its slug. Use this mutation when you need to clear a learner's completion state so they can retake a course from the beginning. The `revokeAccess` flag controls whether the learner's access to the course is also revoked as part of the reset — set it to `true` to fully remove enrollment, or `false` to reset progress while keeping access. If `userId` is omitted, the authenticated user's progress is reset. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `userId` | `ID` | No | The ID of the user whose course progress should be reset. If omitted, the authenticated user's progress is reset. | | `slug` | `String!` | Yes | The slug of the course for which progress should be reset. | | `revokeAccess` | `Boolean!` | Yes | When `true`, the user's access to the course is also revoked. When `false`, progress is cleared but enrollment is preserved. | #### Returns | Field | Type | Description | |-------|------|-------------| | `CurrentUserResetCourseProgress` | `Boolean!` | Returns `true` if the course progress was successfully reset. | #### Example Request ```graphql mutation CurrentUserResetCourseProgress { CurrentUserResetCourseProgress( userId: "user_001" slug: "intro-to-graphql" revokeAccess: false ) } ``` #### Example Response ```json { "data": { "CurrentUserResetCourseProgress": true } } ``` :::note This operation is destructive and irreversible — all progress data for the specified course and user will be cleared. Use with caution in production environments. ::: --- ### DestroyMeeting **`Mutation`** | Complexity: **1 pt** — Delete a meeting from an in-person event course This mutation permanently removes a specific meeting from a non-recurring in-person event course. Provide both the parent `courseId` and the target `meetingId` to identify the meeting to delete. On success, the mutation returns the ID of the deleted meeting. The behavior may be influenced by the `authToken` header used to set the acting user. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `courseId` | `UUID!` | Yes | The UUID of the in-person event course that owns the meeting to be deleted. | | `meetingId` | `UUID!` | Yes | The UUID of the specific meeting to delete. | #### Returns | Field | Type | Description | |-------|------|-------------| | `DestroyMeeting` | `ID` | The ID of the meeting that was deleted, or null if the deletion could not be confirmed. | #### Example Request ```graphql mutation DestroyMeeting { DestroyMeeting( courseId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" meetingId: "b2c3d4e5-f6a7-8901-bcde-f12345678901" ) } ``` #### Example Response ```json { "data": { "DestroyMeeting": "b2c3d4e5-f6a7-8901-bcde-f12345678901" } } ``` :::note This mutation only works on non-recurring in-person event courses. Deletion is permanent and cannot be undone. The `authToken` header can be used to perform the operation as a specific user. ::: --- ### SubmitTestimonial **`Mutation`** | Complexity: **1 pt** — Submit a course group testimonial Allows a learner to submit a testimonial — including a text body and optional star rating — for a specific course group. Testimonials are public-facing reviews that other learners can read on the course detail page. This mutation is typically called after a learner completes a course. The user is determined by the session or the `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `courseGroupId` | `UUID!` | Yes | The ID of the course group. | | `testimonial` | `TestimonialInput!` | Yes | The testimonial input. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID` | Unique identifier of the submitted testimonial. | | `createdAt` | `Date` | Timestamp when the testimonial was submitted. | | `user` | `User` | The user who submitted the testimonial. | | `body` | `String` | The text content of the testimonial. | | `rating` | `Int` | Numeric star rating provided by the learner (e.g., 1–5). | #### Example Request ```graphql mutation SubmitTestimonial($courseGroupId: UUID!, $testimonial: TestimonialInput!) { SubmitTestimonial(courseGroupId: $courseGroupId, testimonial: $testimonial) { id body rating createdAt user { id name email } } } ``` #### Example Response ```json { "data": { "SubmitTestimonial": { "id": "test_00000001-0000-0000-0000-000000000001", "body": "This course completely changed how I approach API design. Highly recommended!", "rating": 5, "createdAt": "2024-05-15T14:22:00Z", "user": { "id": "user_001", "name": "Jane Learner", "email": "learner@example.com" } } }, "id": "abc-123", "createdAt": "2026-01-01T00:00:00Z", "user": null, "body": "example", "rating": 0 } ``` :::note Testimonials are public and may be displayed on the course detail page. The authenticated user (or the user identified by the `authToken` header) is recorded as the testimonial's author. ::: --- ### UpdateInPersonEvent **`Mutation`** | Complexity: **1 pt** — Update an in-person event session Updates the properties of an existing in-person event (ILT) session identified by its UUID. You can modify scheduling dates, enrollment windows, seat limits, price, meetings, custom fields, and more. When updating meetings, you must provide the complete desired list of meetings in the `meetings` array — any meetings not included will be removed. To update non-meeting properties without affecting meetings, simply omit the `meetings` argument. A recurrence rule can be supplied as an iCalendar RFC 5545 RRULE string. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `UUID!` | Yes | The UUID of the in-person event session to update. | | `title` | `String` | No | New display title for the event session. | | `status` | `String` | No | Publication status to set (e.g., 'draft', 'published', 'archived'). | | `sku` | `String` | No | Stock-keeping unit identifier for the event. | | `seatsLimit` | `Int` | No | Maximum number of learner seats available for this session. | | `createMeetingCompletionCriteria` | `Boolean` | No | When true, automatically generates completion criteria based on meeting attendance. | | `assignTo` | `String` | No | Email of user to assign event to. | | `priceInCents` | `Int` | No | Price of the event session in cents. | | `balanceRequirement` | `Int` | No | Credit balance required to enroll in the event. | | `customFields` | `[CustomFieldInput!]` | No | Array of custom field key-value pairs to update on the event. | | `waitlistingEnabled` | `Boolean` | No | Whether to enable a waitlist when the seat limit is reached. | | `courseStartDate` | `Date` | No | The date the event session begins. | | `courseEndDate` | `Date` | No | The date the event session ends. | | `enrollmentStartDate` | `Date` | No | The date enrollment opens for this session. | | `enrollmentEndDate` | `Date` | No | The date enrollment closes for this session. | | `meetings` | `[MeetingFields!]` | No | All meetings you want associated with the event will need to be passed in the meetings array. If you want to update the other properties on the event, omit the meetings field. | | `recurrence` | `String` | No | RRULE string in the format defined by the iCalendar RFC. For example, these would be valid strings: `FREQ=DAILY;INTERVAL=2;COUNT=4`, `FREQ=WEEKLY;COUNT=5`, `FREQ=YEARLY;COUNT=10`. To get more information on formatting, see the documentation here [https://datatracker.ietf.org/doc/html/rfc5545] | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier of the updated course/event session. | | `title` | `String` | Display title of the event session. | | `status` | `Status` | Publication status of the event (e.g., draft, published, archived). | | `seatsLimit` | `Int` | Maximum number of seats available for enrollment. | | `waitlistingEnabled` | `Boolean!` | Whether waitlisting is enabled when seats are full. | | `priceInCents` | `Int` | Price of the event in cents. | | `courseStartDate` | `Date` | Date on which the event begins. | | `courseEndDate` | `Date` | Date on which the event ends. | | `enrollmentStartDate` | `Date` | Date when enrollment opens. | | `enrollmentEndDate` | `Date` | Date when enrollment closes. | | `meetings` | `[Meeting!]` | List of scheduled meeting sessions associated with this event. | | `location` | `Location` | Physical location associated with the event. | | `customFields` | `JSON` | Arbitrary custom field data attached to the event. | #### Example Request ```graphql mutation UpdateInPersonEvent($id: UUID!) { UpdateInPersonEvent( id: $id title: "Spring Workshop 2025" status: "published" seatsLimit: 30 waitlistingEnabled: true priceInCents: 19900 courseStartDate: "2025-03-10" courseEndDate: "2025-03-12" enrollmentStartDate: "2025-01-01" enrollmentEndDate: "2025-03-01" meetings: [ { startDate: "2025-03-10T09:00:00Z" endDate: "2025-03-10T17:00:00Z" timeZone: "America/New_York" locationId: "loc_001" instructors: ["instructor@example.com"] } ] ) { id title status seatsLimit waitlistingEnabled priceInCents courseStartDate courseEndDate enrollmentStartDate enrollmentEndDate meetings { id startDate endDate timeZone location { id name city } } } } ``` #### Example Response ```json { "data": { "UpdateInPersonEvent": { "id": "course_00000001-0000-0000-0000-000000000099", "title": "Spring Workshop 2025", "status": "published", "seatsLimit": 30, "waitlistingEnabled": true, "priceInCents": 19900, "courseStartDate": "2025-03-10", "courseEndDate": "2025-03-12", "enrollmentStartDate": "2025-01-01", "enrollmentEndDate": "2025-03-01", "meetings": [ { "id": "meeting_001", "startDate": "2025-03-10T09:00:00Z", "endDate": "2025-03-10T17:00:00Z", "timeZone": "America/New_York", "location": { "id": "loc_001", "name": "Main Conference Center", "city": "New York" } } ] } }, "id": "abc-123", "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "slug": "example", "status": "draft", "title": "example", "sessionIsTitled": false, "sampleLesson": null, "sampleLessonId": "abc-123", "sections": null, "appearanceBlock": "abc-123", "appearanceHash": "example", "paywallsBlock": "abc-123", "termsBlock": "abc-123", "confirmationBlock": "abc-123", "courseStartDate": "2026-01-01T00:00:00Z", "courseEndDate": "2026-01-01T00:00:00Z", "enrollmentStartDate": "2026-01-01T00:00:00Z", "enrollmentEndDate": "2026-01-01T00:00:00Z", "gracePeriodEndDate": "2026-01-01T00:00:00Z", "seatsLimit": 0, "waitlistCount": 0, "seatsLimitMet": false, "waitlistingEnabled": false, "waitlistingTriggered": false, "taxable": false, "purchasable": false, "taxCategory": "abc-123", "fulfillmentCenter": "abc-123", "courseGroup": null, "syllabusTitlesEnabled": false, "galleryEnabled": false, "workbookEnabled": false, "linkedWorkbookEnabled": false, "linkedWorkbookSkipEnabled": false, "superquizEnabled": false, "discussionsEnabled": false, "resourcesEnabled": false, "meetingsOverviewEnabled": false, "recurrenceRule": "example", "recurrence": {}, "readableRecurrenceString": "example", "instructorAccessEnabled": false, "instructorEmails": [ "example" ], "freeWithRegistration": false, "priceInCents": 0, "instructorAccessPriceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "showProgress": false, "annotationsEnabled": false, "forceLinearProgress": false, "completionTimePerPage": 0, "askLearnerPassword": false, "roster": false, "topicGroupId": "abc-123", "sku": "example", "prerequisiteCourses": null, "prerequisiteLearningPaths": null, "parentCourseId": "abc-123", "isChild": false, "hasMultipleCurrencies": false, "prices": null, "enrollmentActive": false, "waitlistActive": false, "webinarDates": null, "webinarTimeZone": "example", "meetings": null, "inPersonEventInfo": null, "currentUserHasAccess": false, "location": null, "displayMap": false, "publishDate": "2026-01-01T00:00:00Z", "isActive": false, "futurePublishDate": "2026-01-01T00:00:00Z", "balanceRequirement": 0, "awardClaimingOptions": null, "availabilityStatus": "example", "embeddedEnabled": false, "displayResults": false, "bulkPurchasingEnabled": false, "seatTiers": null, "seatPackages": null, "lastTierPriceInCents": 0, "webinarId": "example", "hasChildren": false, "customFields": {}, "kind": "microCourse", "rosterSubmittedDate": "2026-01-01T00:00:00Z", "finalAssessment": "example", "scormCollectUserDetails": false, "seatsUsedCount": 0, "seatsAllocatedCount": 0, "webinarDetails": null, "showSyllabusWhileUnenrolled": false, "topicGroup": null, "courseGroupTitle": "example", "currentUserDueDate": "2026-01-01T00:00:00Z" } ``` :::note When supplying the `meetings` argument, you must pass all desired meetings — this field performs a full replace, not a merge. Omit `meetings` entirely if you only need to update other event properties. The `recurrence` argument accepts an iCalendar RFC 5545 RRULE string. ::: --- ### UpdateMeeting **`Mutation`** | Complexity: **1 pt** — Update a meeting on an in-person event course This mutation updates the details of an existing meeting on a non-recurring in-person event course. All fields except `courseId` and `meetingId` are optional, so you can update just the fields you need to change — such as rescheduling dates, swapping the location, changing instructors, or updating the join URL and attendee information. The mutation returns the full updated `Meeting` object. The behavior may be influenced by the `authToken` header used to set the acting user. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `courseId` | `UUID!` | Yes | The UUID of the in-person event course that owns the meeting to be updated. | | `meetingId` | `UUID!` | Yes | The UUID of the specific meeting to update. | | `title` | `String` | No | The new display title for the meeting. | | `location` | `UUID` | No | The UUID of a location record to associate with this meeting. | | `joinUrl` | `String` | No | A URL that attendees can use to join the meeting virtually. | | `attendeeInfo` | `String` | No | Free-text information to communicate to attendees about the meeting. | | `instructors` | `[String!]` | No | A list of instructor email addresses or identifiers to assign to the meeting. | | `startDate` | `Date` | No | The updated start date and time for the meeting. | | `endDate` | `Date` | No | The updated end date and time for the meeting. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the updated meeting. | | `title` | `String` | The display title of the meeting. | | `courseId` | `ID!` | The ID of the course (in-person event) this meeting belongs to. | | `course` | `Course!` | The full course object associated with this meeting. | | `locationId` | `ID` | The ID of the location associated with this meeting, if any. | | `location` | `Location` | The physical location details for this meeting. | | `joinUrl` | `String` | A URL attendees can use to join the meeting. | | `attendeeInfo` | `String` | Additional information provided to attendees about this meeting. | | `instructors` | `[String!]` | A list of instructor email addresses or names associated with this meeting. | | `startDate` | `Date!` | The date and time when the meeting begins. | | `endDate` | `Date!` | The date and time when the meeting ends. | | `timeZone` | `String` | The time zone in which the meeting's start and end dates are expressed. | | `attendees` | `[User!]` | The list of users who have attended or are registered for this meeting. | #### Example Request ```graphql mutation UpdateMeeting { UpdateMeeting( courseId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" meetingId: "b2c3d4e5-f6a7-8901-bcde-f12345678901" title: "Day 1 Session (Updated)" startDate: "2024-09-15T10:00:00Z" endDate: "2024-09-15T18:00:00Z" instructors: ["john.smith@example.com"] joinUrl: "https://meet.example.com/updated-session" attendeeInfo: "Please bring a laptop." ) { id title startDate endDate instructors joinUrl attendeeInfo location { id name city state } } } ``` #### Example Response ```json { "data": { "UpdateMeeting": { "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "title": "Day 1 Session (Updated)", "startDate": "2024-09-15T10:00:00Z", "endDate": "2024-09-15T18:00:00Z", "instructors": [ "john.smith@example.com" ], "joinUrl": "https://meet.example.com/updated-session", "attendeeInfo": "Please bring a laptop.", "location": { "id": "loc-uuid-001", "name": "Main Conference Center", "city": "Chicago", "state": "IL" } } }, "id": "abc-123", "title": "example", "courseId": "abc-123", "course": null, "locationId": "abc-123", "location": null, "joinUrl": "example", "attendeeInfo": "example", "instructors": [ "example" ], "startDate": "2026-01-01T00:00:00Z", "endDate": "2026-01-01T00:00:00Z", "timeZone": "example", "attendees": null } ``` :::note This mutation only works on non-recurring in-person event courses. The `authToken` header can be used to perform the operation as a specific user. ::: --- --- ## Helium Endpoints Launch, batch-process, deploy, and monitor Helium-powered widgets and assets. ### HeliumBatch **`Query`** | Complexity: **1 pt** — Fetch Helium batch operation result Retrieves the result or status of a batch operation within the Helium deployment system, identified by both a deployment key and a site nickname. This query is typically used to poll or retrieve the outcome of a previously initiated batch process, such as a bulk asset compilation or deployment batch job. The response is a raw string, which may be a status code, JSON payload, or other structured data depending on the batch operation performed. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `key` | `String!` | Yes | The deployment key identifying the Helium session or environment, typically obtained from a prior HeliumLaunchData call. | | `nickname` | `String!` | Yes | The short name or slug identifying the Helium-enabled site or environment associated with the batch operation. | #### Returns | Field | Type | Description | |-------|------|-------------| | `HeliumBatch` | `String!` | A string containing the result or status of the Helium batch operation. The format of this string depends on the batch job type. | #### Example Request ```graphql query HeliumBatch { HeliumBatch(key: "hlm_k9f2x7qzLmNpRtVw", nickname: "my-learning-portal") } ``` #### Example Response ```json { "data": { "HeliumBatch": "{\"status\":\"complete\",\"processedItems\":120,\"failedItems\":0}" } } ``` --- ### HeliumDeploymentLog **`Query`** | Complexity: **1 pt** — Fetch Helium deployment log Retrieves the event log for a specific Helium deployment job, returning a paginated list of log events along with tokens for forward and backward pagination. Each log event includes a timestamp, ingestion time, and a human-readable message. Use this query to debug deployment issues, audit deployment activity, or stream live log output by repeatedly querying with the `nextForwardToken`. The optional `filters` argument allows narrowing results by time range or other criteria. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `jobId` | `ID!` | Yes | The unique identifier of the Helium deployment job whose log should be retrieved. | | `filters` | `HeliumDeploymentLogFiltersInput` | No | Optional filters to narrow the log results, such as restricting events to a specific time range or log level. | #### Returns | Field | Type | Description | |-------|------|-------------| | `events` | `[HeliumDeploymentLogEvent!]!` | An ordered list of log events for the deployment job, each containing a timestamp, ingestion time, and message. | | `nextForwardToken` | `String` | A pagination token used to fetch the next page of log events in chronological order. Pass this value as a filter in subsequent requests. | | `nextBackwardToken` | `String` | A pagination token used to fetch the previous page of log events in reverse chronological order. Pass this value as a filter in subsequent requests. | #### Example Request ```graphql query HeliumDeploymentLog { HeliumDeploymentLog( jobId: "job_7tRmX92kLpQn", filters: { startTime: "2024-03-01T00:00:00Z", endTime: "2024-03-01T23:59:59Z" } ) { events { timestamp message ingestionTime } nextForwardToken nextBackwardToken } } ``` #### Example Response ```json { "data": { "HeliumDeploymentLog": { "events": [ { "timestamp": "2024-03-01T10:15:00Z", "message": "Deployment started for environment my-learning-portal.", "ingestionTime": "2024-03-01T10:15:01Z" }, { "timestamp": "2024-03-01T10:15:45Z", "message": "Assets compiled successfully. Script hash: sha256-abcdef1234567890.", "ingestionTime": "2024-03-01T10:15:46Z" }, { "timestamp": "2024-03-01T10:16:30Z", "message": "Deployment complete.", "ingestionTime": "2024-03-01T10:16:31Z" } ], "nextForwardToken": "fwd_tok_Xk92mNpLqRtVwZy", "nextBackwardToken": "bwd_tok_AaBbCcDdEeFfGgHh" } }, "events": null, "nextForwardToken": "example", "nextBackwardToken": "example" } ``` --- ### HeliumDeploymentStatus **`Query`** | Complexity: **1 pt** — Fetch Helium deployment status Returns the current status of a Helium deployment job as a string. Supply the required `jobId` to identify the deployment, and optionally provide a `key`, `atomsScriptHash`, and/or `atomsStyleHash` to scope or validate the status check against specific deployment artifacts. This query is useful for polling the progress of an ongoing deployment or verifying that a deployment's compiled assets match expected hash values. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `jobId` | `ID!` | Yes | The unique identifier of the Helium deployment job whose status should be retrieved. | | `key` | `String` | No | An optional deployment key used to scope the status check to a specific Helium environment or session. | | `atomsScriptHash` | `String` | No | An optional hash of the compiled Atoms JavaScript bundle, used to verify that the deployed script artifact matches the expected version. | | `atomsStyleHash` | `String` | No | An optional hash of the compiled Atoms CSS stylesheet bundle, used to verify that the deployed style artifact matches the expected version. | #### Returns | Field | Type | Description | |-------|------|-------------| | `HeliumDeploymentStatus` | `String!` | A string representing the current status of the deployment job (e.g., PENDING, IN_PROGRESS, COMPLETE, FAILED). | #### Example Request ```graphql query HeliumDeploymentStatus { HeliumDeploymentStatus( jobId: "job_7tRmX92kLpQn", key: "hlm_k9f2x7qzLmNpRtVw", atomsScriptHash: "sha256-abcdef1234567890", atomsStyleHash: "sha256-09876fedcba54321" ) } ``` #### Example Response ```json { "data": { "HeliumDeploymentStatus": "IN_PROGRESS" } } ``` --- ### HeliumLaunchData **`Query`** | Complexity: **1 pt** — Fetch Helium launch data Retrieves the launch credentials needed to initialize a Helium front-end experience for a given site or environment identified by its nickname. The response includes a signing key and a signed URL that can be used to bootstrap the Helium runtime. Use this query at application startup or when rendering a Helium-powered page to obtain fresh, signed access details. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `nickname` | `String!` | Yes | The short name or slug that identifies the Helium-enabled site or environment to launch. | #### Returns | Field | Type | Description | |-------|------|-------------| | `key` | `String!` | A unique key identifying this Helium deployment or session, used in subsequent Helium API calls. | | `signedUrl` | `AbsoluteOrRelativeURL!` | A signed URL used to bootstrap or launch the Helium front-end experience. May be absolute or relative. | #### Example Request ```graphql query HeliumLaunchData { HeliumLaunchData(nickname: "my-learning-portal") { key signedUrl } } ``` #### Example Response ```json { "data": { "HeliumLaunchData": { "key": "hlm_k9f2x7qzLmNpRtVw", "signedUrl": "https://cdn.example.com/helium/my-learning-portal/launch?sig=abc123xyz&expires=1710000000" } }, "key": "example", "signedUrl": "example" } ``` --- --- ## GraphQL API Reference Browse the available GraphQL queries and mutations by category. | Category | Endpoints | |----------|-----------| | [Bookmark Endpoints](/docs/graphql/endpoints/bookmarks/) | 9 | | [Comments & Forum Endpoints](/docs/graphql/endpoints/comments-forums/) | 13 | | [Content Endpoints](/docs/graphql/endpoints/content/) | 19 | | [Helium Endpoints](/docs/graphql/endpoints/helium/) | 4 | | [Learning Path Endpoints](/docs/graphql/endpoints/learning-paths/) | 15 | | [Other Endpoints](/docs/graphql/endpoints/other/) | 1 | | [Quiz & Assessment Endpoints](/docs/graphql/endpoints/quizzes-assessments/) | 9 | | [User & Auth Endpoints](/docs/graphql/endpoints/users-auth/) | 29 | --- ## Learning Path Endpoints Queries for fetching learning paths and their milestones. ### CurrentUserLearningPathMilestoneCompletionsBySlug **`Query`** | Complexity: **1 pt** — Fetch current user's learning path milestone completions by slug Returns a list of `LearningPathAction` records representing the milestones that the currently authenticated user has completed for the learning path identified by the given slug. Each action record includes the milestone details, a timestamp of completion, the action type and source, and the associated user. This is useful for tracking individual learner progress through a learning path, rendering progress indicators, or auditing completion history. Pass an `authToken` header to set the user context; without it, results will reflect the unauthenticated state. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `slug` | `Slug!` | Yes | The slug of the learning path for which to retrieve the current user's milestone completion records. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier for this learning path action record. | | `learningPath` | `LearningPath!` | The learning path associated with this action. | | `milestone` | `Milestone` | The milestone that was completed as part of this action. | | `type` | `String` | The type of action recorded (e.g., milestone_completed). | | `source` | `String` | The source or trigger that generated this action record. | | `user` | `User` | The user who performed the action. | | `timestamp` | `Date` | The date and time when the action occurred. | #### Example Request ```graphql query GetMilestoneCompletions($slug: Slug!) { CurrentUserLearningPathMilestoneCompletionsBySlug(slug: $slug) { id type source timestamp milestone { id name kind requirement } learningPath { id name slug } user { id email firstName lastName } } } ``` #### Example Response ```json { "data": { "CurrentUserLearningPathMilestoneCompletionsBySlug": [ { "id": "action_001", "type": "milestone_completed", "source": "learner", "timestamp": "2024-04-10T14:22:00Z", "milestone": { "id": "milestone_001", "name": "Module 1: Python Basics", "kind": "content", "requirement": "required" }, "learningPath": { "id": "lp_abc123", "name": "Data Science Professional Certificate", "slug": "data-science-professional-certificate" }, "user": { "id": "user_xyz789", "email": "jane.learner@example.com", "firstName": "Jane", "lastName": "Learner" } } ] }, "id": "abc-123", "learningPath": null, "milestone": null, "type": "example", "source": "example", "user": null, "timestamp": "2026-01-01T00:00:00Z" } ``` :::note This query is user-scoped. Provide an `authToken` header to identify the learner. Without authentication, the result set will be empty or limited. ::: --- ### LearningPathBySlug **`Query`** | Complexity: **1 pt** — Fetch a learning path by slug Retrieves the full details of a single learning path using its unique slug identifier. This is the primary query for displaying learning path detail pages, showing milestones, pricing, enrollment windows, prerequisite requirements, tags, and layout configuration. The returned object includes deeply nested information about the milestones, content items, prerequisite courses and paths, as well as client and license settings. Use this query whenever you need to render or process a learning path by its URL-friendly slug. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `slug` | `Slug!` | Yes | The slug for the Learning Path. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier of the learning path. | | `layoutId` | `ID` | ID of the admin/detail layout used for this learning path. | | `layout` | `Layout` | The detail page layout configuration, including widgets and scripts. | | `learnerLayoutId` | `ID` | ID of the learner-facing layout. | | `learnerLayout` | `Layout` | The learner-facing layout configuration. | | `name` | `String!` | Display name of the learning path. | | `sku` | `String` | Optional stock-keeping unit identifier. | | `ribbon` | `Ribbon` | Promotional ribbon displayed on the learning path card. | | `slug` | `Slug!` | URL-friendly unique identifier. | | `client` | `Client` | The tenant/client that owns this learning path, including licensing and settings. | | `createdAt` | `Date` | Timestamp when the learning path was created. | | `metaTitle` | `String` | SEO meta title. | | `metaDescription` | `String` | SEO meta description. | | `asset` | `URL` | URL of the primary thumbnail/card image. | | `assetAltText` | `String` | Accessibility alt text for the primary image. | | `assetLibraryId` | `ID` | Library reference ID for the primary image asset. | | `detailAsset` | `URL` | URL of the detail page image. | | `detailAssetAltText` | `String` | Alt text for the detail page image. | | `detailAssetLibraryId` | `ID` | Library reference ID for the detail image asset. | | `heroAsset` | `URL` | URL of the hero/banner image. | | `heroAssetAltText` | `String` | Alt text for the hero image. | | `priceInCents` | `Int` | Default price in cents (USD). | | `suggestedRetailPriceInCents` | `Int` | Suggested retail price in cents. | | `alternativePricingType` | `AlternativePricingType` | Alternative pricing model, e.g., membership. | | `alternativePricingRef` | `Int` | Reference identifier for the alternative pricing configuration. | | `accessDays` | `Int` | Number of days of access granted after enrollment. | | `shortDescription` | `String` | Brief marketing description. | | `longDescription` | `String` | Full HTML description. | | `tags` | `[Tag]` | Tags associated with this learning path for categorization and filtering. | | `status` | `Status!` | Publication status (e.g., published, draft, archived). | | `freeWithRegistration` | `Boolean` | Whether the learning path is free upon account registration. | | `hasMultipleCurrencies` | `Boolean!` | Whether multi-currency pricing is configured. | | `prices` | `[ItemPrice!]!` | List of pricing objects for all available currencies and locales. | | `customFields` | `JSON` | Arbitrary custom field data stored as JSON. | | `contentType` | `String` | Content type label for display purposes. | | `confirmationHtml` | `String` | HTML displayed to users upon enrollment confirmation. | | `startDate` | `Date` | The date the learning path content becomes available. | | `endDate` | `Date` | The date after which the learning path content is no longer accessible. | | `enrollmentStartDate` | `Date` | The date enrollment opens. | | `enrollmentEndDate` | `Date` | The date enrollment closes. | | `milestones` | `[Milestone]` | Ordered list of milestones comprising the learning path. | | `availabilityStatus` | `String` | Human-readable availability status string. | | `currentUserStartActionDate` | `Date` | The date the current user started this learning path. | | `isPayAsYouGo` | `Boolean` | Whether pay-as-you-go purchasing is enabled. | | `language` | `String` | Language code for the learning path content. | | `futurePublishDate` | `Date` | Scheduled future publish date, if set. | | `purchasable` | `Boolean!` | Whether the learning path can be purchased. | | `taxable` | `Boolean` | Whether the learning path is subject to tax. | | `fulfillmentCenter` | `FulfillmentCenter` | Fulfillment center used for physical or logistics-related delivery. | | `taxCategory` | `ID` | Tax category identifier. | | `currentUserPendingCertificate` | `Boolean!` | Whether the current user has a certificate pending issuance. | | `currentUserEarnedCertificate` | `Boolean!` | Whether the current user has earned a certificate for this learning path. | | `currentUserDueDate` | `Date` | Due date assigned to the current user for this learning path. | | `bulkPurchasingEnabled` | `Boolean!` | Whether bulk/seat purchasing is enabled. | | `seatTiers` | `[SeatTier!]` | Volume pricing tiers for seat purchases. | | `seatPackages` | `[SeatPackage!]` | Available seat package configurations. | | `lastTierPriceInCents` | `Int` | Price in cents for the highest/last seat tier. | | `prerequisiteCourses` | `[Course]` | Courses that must be completed before enrolling in this learning path. | | `prerequisiteLearningPaths` | `[LearningPath]` | Learning paths that must be completed before enrolling. | | `resourceType` | `String!` | Resource type discriminator string. | | `seatsLimit` | `Int` | Maximum number of enrollments allowed. | | `videoAsset` | `ID` | Library ID of the promotional video asset. | | `relatedQuery` | `String` | Elasticsearch query string for related content suggestions. | | `relatedQuerySignature` | `String` | Signature hash for the related content query. | | `source` | `String` | Source/origin identifier for imported content. | | `authors` | `[String!]` | List of author names associated with this learning path. | | `publishDate` | `Date` | The date the learning path was published. | | `externalDetailUrl` | `String` | External URL to redirect users for detail page viewing. | | `externalConditionalRedirectUrl` | `String` | Conditional external redirect URL for enrollment flows. | | `kind` | `String` | Kind discriminator for the learning path type. | | `tabs` | `LearningPathTabs` | Tab configuration for the learning path detail page. | | `updatedAt` | `Date` | Timestamp of the last update. | | `dueDate` | `Date` | Global due date for the learning path. | | `dueDateDays` | `Int` | Number of days after enrollment before the due date is set. | | `allowUnorderedMilestoneAccess` | `Boolean` | Whether learners can access milestones in any order. | | `authoringClient` | `ID` | ID of the client that originally authored this learning path. | | `isTemplate` | `Boolean` | Whether this learning path is a reusable template. | | `externalPurchaseUrl` | `String` | External URL to redirect users for purchasing. | #### Example Request ```graphql query GetLearningPathBySlug($slug: Slug!) { LearningPathBySlug(slug: $slug) { id name slug status shortDescription longDescription priceInCents purchasable freeWithRegistration availabilityStatus createdAt updatedAt asset milestones { id name kind requirement releaseDate completionCriteriaDescription } tags { id label } prices { id currencyCode unitAmount isDefault } } } ``` #### Example Response ```json { "data": { "LearningPathBySlug": { "id": "lp_abc123", "name": "Data Science Professional Certificate", "slug": "data-science-professional-certificate", "status": "published", "shortDescription": "A comprehensive program covering data analysis, machine learning, and visualization.", "longDescription": "Master data science with our structured, milestone-based program.", "priceInCents": 49900, "purchasable": true, "freeWithRegistration": false, "availabilityStatus": "available", "createdAt": "2023-09-01T00:00:00Z", "updatedAt": "2024-05-15T12:00:00Z", "asset": "https://cdn.example.com/assets/ds-cert-thumbnail.jpg", "milestones": [ { "id": "milestone_001", "name": "Module 1: Python Basics", "kind": "content", "requirement": "required", "releaseDate": null, "completionCriteriaDescription": "Complete all required courses in this milestone." }, { "id": "milestone_002", "name": "Module 2: Data Wrangling", "kind": "content", "requirement": "required", "releaseDate": "2024-02-01T00:00:00Z", "completionCriteriaDescription": "Complete at least 2 of the 3 elective courses." } ], "tags": [ { "id": "tag_001", "label": "Data Science" }, { "id": "tag_002", "label": "Python" } ], "prices": [ { "id": "price_001", "currencyCode": "USD", "unitAmount": 49900, "isDefault": true } ] } }, "id": "abc-123", "layoutId": "abc-123", "layout": null, "learnerLayoutId": "abc-123", "learnerLayout": null, "name": "example", "sku": "example", "ribbon": null, "slug": "example", "client": null, "createdAt": "2026-01-01T00:00:00Z", "metaTitle": "example", "metaDescription": "example", "asset": "https://example.com", "assetAltText": "example", "assetLibraryId": "abc-123", "detailAsset": "https://example.com", "detailAssetAltText": "example", "detailAssetLibraryId": "abc-123", "heroAsset": "https://example.com", "heroAssetAltText": "example", "priceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "shortDescription": "example", "longDescription": "example", "tags": null, "status": "draft", "freeWithRegistration": false, "hasMultipleCurrencies": false, "prices": null, "customFields": {}, "contentType": "example", "confirmationHtml": "example", "startDate": "2026-01-01T00:00:00Z", "endDate": "2026-01-01T00:00:00Z", "enrollmentStartDate": "2026-01-01T00:00:00Z", "enrollmentEndDate": "2026-01-01T00:00:00Z", "milestones": null, "availabilityStatus": "example", "currentUserStartActionDate": "2026-01-01T00:00:00Z", "isPayAsYouGo": false, "language": "example", "futurePublishDate": "2026-01-01T00:00:00Z", "purchasable": false, "taxable": false, "fulfillmentCenter": null, "taxCategory": "abc-123", "currentUserPendingCertificate": false, "currentUserEarnedCertificate": false, "currentUserDueDate": "2026-01-01T00:00:00Z", "bulkPurchasingEnabled": false, "seatTiers": null, "seatPackages": null, "lastTierPriceInCents": 0, "prerequisiteCourses": null, "prerequisiteLearningPaths": null, "resourceType": "example", "seatsLimit": 0, "videoAsset": "abc-123", "relatedQuery": "example", "relatedQuerySignature": "example", "source": "example", "authors": [ "example" ], "publishDate": "2026-01-01T00:00:00Z", "externalDetailUrl": "example", "externalConditionalRedirectUrl": "example", "kind": "example", "tabs": null, "updatedAt": "2026-01-01T00:00:00Z", "dueDate": "2026-01-01T00:00:00Z", "dueDateDays": 0, "allowUnorderedMilestoneAccess": false, "authoringClient": "abc-123", "isTemplate": false, "externalPurchaseUrl": "example" } ``` --- ### Milestone **`Query`** | Complexity: **1 pt** — Fetch a learning path milestone by ID Retrieves a single milestone from a learning path using its unique ID. The response includes the milestone's name, kind (`content` or `competencyAssessment`), requirement level (`required` or `elective`), completion criteria, release scheduling, and the list of content items or competency assessments associated with it. This query is useful for rendering milestone detail views, checking completion criteria before granting access, or programmatically inspecting the structure of a learning path segment. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID!` | Yes | The unique ID of the milestone to retrieve. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier of the milestone. | | `name` | `String!` | Display name of the milestone. | | `learningPath` | `LearningPath!` | The learning path this milestone belongs to. | | `contentItems` | `[Content!]` | Catalog-level content items associated with this milestone. | | `milestoneCourses` | `[MilestoneCourse!]` | Structured course assignments within the milestone, including requirement levels. | | `milestoneCompetencyAssessment` | `MilestoneCompetencyAssessment` | Competency assessment configuration if this is a competency-based milestone. | | `kind` | `MilestoneKind!` | The kind of milestone: either `content` (courses/content items) or `competencyAssessment`. | | `completionCriteriaDescription` | `String` | Human-readable description of what learners must do to complete this milestone. | | `completionCriteria` | `[MilestoneCompletionCriteria!]` | Structured completion criteria objects, including type, threshold, and optional award type requirements. | | `releaseDate` | `Date` | Absolute date on which this milestone becomes accessible to learners. | | `releaseDays` | `Int` | Number of days after enrollment before this milestone is released. | | `requirement` | `MilestoneRequirement!` | Whether this milestone is `required` or an `elective` for learning path completion. | | `createdAt` | `Date` | Timestamp when the milestone was created. | | `updatedAt` | `Date` | Timestamp when the milestone was last updated. | #### Example Request ```graphql query GetMilestone($id: ID!) { Milestone(id: $id) { id name kind requirement releaseDate releaseDays completionCriteriaDescription createdAt updatedAt completionCriteria { id type threshold awardType { id label } } milestoneCourses { id contentId contentTitle requirement contentGrouping } learningPath { id name slug } } } ``` #### Example Response ```json { "data": { "Milestone": { "id": "milestone_001", "name": "Module 1: Python Basics", "kind": "content", "requirement": "required", "releaseDate": null, "releaseDays": null, "completionCriteriaDescription": "Complete all required courses in this module.", "createdAt": "2023-09-01T00:00:00Z", "updatedAt": "2024-03-10T09:00:00Z", "completionCriteria": [ { "id": "criteria_001", "type": "requiredCourses", "threshold": 2, "awardType": null } ], "milestoneCourses": [ { "id": "mc_001", "contentId": "course_abc123", "contentTitle": "Intro to Python", "requirement": "required", "contentGrouping": "courseGroup" }, { "id": "mc_002", "contentId": "course_def456", "contentTitle": "Python for Data Analysis", "requirement": "required", "contentGrouping": "courseGroup" } ], "learningPath": { "id": "lp_abc123", "name": "Data Science Professional Certificate", "slug": "data-science-professional-certificate" } } }, "id": "abc-123", "name": "example", "learningPath": null, "contentItems": null, "milestoneCourses": null, "milestoneCompetencyAssessment": null, "kind": "content", "completionCriteriaDescription": "example", "completionCriteria": null, "releaseDate": "2026-01-01T00:00:00Z", "releaseDays": 0, "requirement": "required", "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z" } ``` --- ### PagesCompletedByCourse **`Query`** | Complexity: **3 pts** — Fetch pages completed by a user in a course Returns the list of pages that the current user has already completed within the specified course. This is useful for tracking learner progress, rendering progress indicators, or conditionally unlocking downstream content. The results are scoped to the authenticated user — provide an `authToken` header to control which user's completion data is returned. The response is a union of typed page objects, so use inline fragments to access page-specific fields. This query carries an additional cost of 3 points. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `courseId` | `ID!` | Yes | The ID of the course. | #### Returns | Field | Type | Description | |-------|------|-------------| | `PagesCompletedByCourse` | `[PageResource!]!` | An array of page resource objects representing all pages the current user has completed in the specified course. Each item resolves to a concrete page type union member such as TextPage, QuizPage, VideoPage, etc. | #### Example Request ```graphql query PagesCompletedByCourse($courseId: ID!) { PagesCompletedByCourse(courseId: $courseId) { __typename ... on TextPage { id title type lessonId updatedAt } ... on QuizPage { id title type lessonId updatedAt minPassingPercent } ... on VideoPage { id title type lessonId updatedAt asset } ... on AssignmentPage { id title type lessonId updatedAt } } } ``` #### Example Response ```json { "data": { "PagesCompletedByCourse": [ { "__typename": "TextPage", "id": "page_text_001", "title": "Course Overview", "type": "text", "lessonId": "lesson_101", "updatedAt": "2024-03-01T09:00:00Z" }, { "__typename": "VideoPage", "id": "page_video_002", "title": "Welcome Video", "type": "video", "lessonId": "lesson_101", "updatedAt": "2024-03-01T09:15:00Z", "asset": "wistia_abc123xyz" }, { "__typename": "QuizPage", "id": "page_quiz_003", "title": "Pre-Assessment", "type": "quiz", "lessonId": "lesson_102", "updatedAt": "2024-03-02T11:00:00Z", "minPassingPercent": 70 } ] }, "members": null } ``` :::note This query has an additional cost of 3 points. Results are user-scoped — supply an `authToken` header to specify the learner whose completions should be returned. Use `__typename` and inline fragments to access type-specific fields from the `PageResource` union. ::: --- ### UserCourseCompletionProgress **`Query`** | Complexity: **5 pts** — Fetch a user's course completion criteria progress Returns the progress of the current user against each completion criteria configured for a specific course. Each entry describes a criterion type (such as percentage viewed, assessment passed, or topic viewed), the set of required item IDs, the set of completed item IDs, and an overall completion percentage. This is useful for building custom progress dashboards or triggering downstream workflows when completion milestones are reached. The user context can be overridden with an `authToken` header. This query carries an additional API cost of 5 points. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID!` | Yes | The ID of the course for which to retrieve the user's completion criteria progress. | #### Returns | Field | Type | Description | |-------|------|-------------| | `type` | `CourseCompletionCriteriaType` | The type of completion criterion (e.g., `coursePercentViewed`, `courseAssessmentPassed`). | | `required` | `[ID]` | List of item IDs (e.g., topic IDs) required to satisfy this criterion. | | `completed` | `[ID]` | List of item IDs that the user has already completed toward this criterion. | | `percent` | `Int!` | The user's percentage completion for this criterion, from 0 to 100. | #### Example Request ```graphql query UserCourseCompletionProgress($id: ID!) { UserCourseCompletionProgress(id: $id) { type required completed percent } } # Variables: # { "id": "course_456" } ``` #### Example Response ```json { "data": { "UserCourseCompletionProgress": [ { "type": "coursePercentViewed", "required": null, "completed": null, "percent": 75 }, { "type": "courseAssessmentPassed", "required": [ "topic_789" ], "completed": [ "topic_789" ], "percent": 100 }, { "type": "courseTopicViewed", "required": [ "topic_101", "topic_102", "topic_103" ], "completed": [ "topic_101", "topic_102" ], "percent": 66 } ] }, "type": "coursePercentViewed", "required": [ "abc-123" ], "completed": [ "abc-123" ], "percent": 0 } ``` :::note This query has an additional cost of 5 API points. ::: --- ### UserCourseProgress **`Query`** | Complexity: **5 pts** — Fetch user course progress Returns aggregate progress metrics for a specific user on a given course, including the total number of content views, total time spent (in seconds), and the percentage of the course completed. The user context is determined by the `authToken` header supplied with the request — omitting or changing this header will affect whose progress data is returned. Use this query to power learner progress dashboards, completion tracking, or reporting workflows. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID!` | Yes | The unique identifier of the course for which to retrieve the user's progress. | #### Returns | Field | Type | Description | |-------|------|-------------| | `totalViews` | `Int` | The total number of content item views the user has recorded in this course. | | `totalTime` | `Int` | The total time the user has spent in the course, expressed in seconds. | | `percentComplete` | `Int` | The percentage of the course the user has completed, as an integer from 0 to 100. | #### Example Request ```graphql query UserCourseProgress { UserCourseProgress(id: "course_abc123") { totalViews totalTime percentComplete } } ``` #### Example Response ```json { "data": { "UserCourseProgress": { "totalViews": 42, "totalTime": 7320, "percentComplete": 75 } }, "totalViews": 0, "totalTime": 0, "percentComplete": 0 } ``` :::note This query has an additional cost of 5 points against your API rate-limit quota. The user whose progress is returned is determined by the `authToken` request header; ensure the correct token is provided to fetch the intended user's data. ::: --- ### ArchiveUserCourse **`Mutation`** | Complexity: **1 pt** — Archive a course for a user Archives a course for the current user, hiding it from their active course list without deleting enrollment or progress data. Archived courses can be restored using the `ReinstateUserCourse` mutation. On success, the mutation returns the ID of the archived course. The acting user can be controlled by passing an `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID!` | Yes | The ID of the course to be archived. | #### Returns | Field | Type | Description | |-------|------|-------------| | `ArchiveUserCourse` | `ID` | The ID of the course that was archived, or null if the operation did not succeed. | #### Example Request ```graphql mutation ArchiveUserCourse($id: ID!) { ArchiveUserCourse(id: $id) } ``` #### Example Response ```json { "data": { "ArchiveUserCourse": "course_789" } } ``` :::note Returns `null` if the course could not be archived. Archiving is a soft operation — user progress is retained and can be restored. The acting user can be overridden via the `authToken` header. ::: --- ### ArchiveUserLearningPath **`Mutation`** | Complexity: **1 pt** — Archive a learning path for a user Archives a learning path for the current user, removing it from their active learning path list without permanently deleting their enrollment or progress data. Archived learning paths can be restored using the `ReinstateUserLearningPath` mutation. On success, the mutation returns the ID of the archived learning path. The acting user can be controlled by passing an `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID!` | Yes | The ID of the learning path to archive for the user. | #### Returns | Field | Type | Description | |-------|------|-------------| | `ArchiveUserLearningPath` | `ID` | The ID of the learning path that was archived, or null if the operation did not succeed. | #### Example Request ```graphql mutation ArchiveUserLearningPath($id: ID!) { ArchiveUserLearningPath(id: $id) } ``` #### Example Response ```json { "data": { "ArchiveUserLearningPath": "lp_456" } } ``` :::note Returns `null` if the learning path could not be archived. Archiving is a soft operation — user progress is retained and can be restored. The acting user can be overridden via the `authToken` header. ::: --- ### CreateLearningPath **`Mutation`** | Complexity: **1 pt** — Create a learning path Creates a new learning path with one or more milestones. A learning path is a curated sequence of content items organized into milestones that learners must complete. Use this mutation to programmatically build structured curricula, such as onboarding tracks or certification programs. The `milestones` argument defines the content groupings and their completion requirements. You can optionally associate the learning path with a specific client (sub-tenant) by providing `clientId`. An `authToken` header can be used to scope the operation to a particular user. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `name` | `String!` | Yes | The display name for the new learning path. | | `language` | `String` | No | The BCP 47 language code for the learning path content (e.g., 'en', 'es'). | | `clientId` | `ID` | No | The ID of the client (sub-tenant) to associate this learning path with. Defaults to the root client if omitted. | | `milestones` | `[MilestoneInput!]!` | Yes | An array of milestone input objects defining the content groupings and completion requirements for this learning path. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the newly created learning path. | | `name` | `String!` | The display name of the learning path. | | `slug` | `Slug!` | The URL-friendly slug for the learning path. | | `status` | `Status!` | The publication status of the learning path. | | `language` | `String` | The BCP 47 language code of the learning path content. | | `createdAt` | `Date` | Timestamp when the learning path was created. | | `updatedAt` | `Date` | Timestamp when the learning path was last updated. | | `milestones` | `[Milestone]` | The ordered list of milestones that make up this learning path. | | `client` | `Client` | The client organization this learning path belongs to. | | `layoutId` | `ID` | ID of the layout template used for the learning path detail page. | | `layout` | `Layout` | The layout template used for the learning path detail page. | | `learnerLayoutId` | `ID` | ID of the layout template used for the learner view. | | `learnerLayout` | `Layout` | The layout template used for the learner view of this path. | | `sku` | `String` | Stock-keeping unit identifier for the learning path. | | `ribbon` | `Ribbon` | Ribbon label displayed on the learning path catalog card. | | `priceInCents` | `Int` | The price of the learning path in cents. | | `prices` | `[ItemPrice!]!` | Multi-currency pricing options for the learning path. | | `purchasable` | `Boolean!` | Whether the learning path can be purchased. | | `freeWithRegistration` | `Boolean` | Whether the learning path is accessible for free upon registration. | | `shortDescription` | `String` | A brief summary of the learning path shown in catalog listings. | | `longDescription` | `String` | A detailed description of the learning path. | | `tags` | `[Tag]` | Tags associated with the learning path for categorization and search. | | `availabilityStatus` | `String` | A string describing the current enrollment availability state. | | `customFields` | `JSON` | Custom field data associated with this learning path. | | `startDate` | `Date` | The date this learning path becomes active. | | `endDate` | `Date` | The date this learning path expires. | | `enrollmentStartDate` | `Date` | When enrollment opens for this learning path. | | `enrollmentEndDate` | `Date` | When enrollment closes for this learning path. | | `dueDate` | `Date` | The due date for completing this learning path. | | `dueDateDays` | `Int` | Number of days after enrollment before the learning path is due. | | `allowUnorderedMilestoneAccess` | `Boolean` | Whether learners can access milestones out of order. | | `isTemplate` | `Boolean` | Whether this learning path is marked as a template. | | `prerequisiteCourses` | `[Course]` | Courses that must be completed before enrolling in this path. | | `prerequisiteLearningPaths` | `[LearningPath]` | Learning paths that must be completed before enrolling in this path. | | `currentUserPendingCertificate` | `Boolean!` | Whether the current user has a pending certificate for this path. | | `currentUserEarnedCertificate` | `Boolean!` | Whether the current user has earned a certificate for this path. | | `bulkPurchasingEnabled` | `Boolean!` | Whether bulk seat purchasing is enabled for this learning path. | | `resourceType` | `String!` | The resource type identifier for this learning path. | | `publishDate` | `Date` | The date the learning path was or will be published. | | `externalPurchaseUrl` | `String` | An external URL for purchasing this learning path outside the platform. | #### Example Request ```graphql mutation CreateLearningPath($milestones: [MilestoneInput!]!) { CreateLearningPath( name: "New Employee Onboarding Track" language: "en" clientId: "clt_9876543210" milestones: $milestones ) { id name slug status language createdAt milestones { id name kind requirement milestoneCourses { id contentId contentTitle requirement } } client { id name } } } # Variables: # { # "milestones": [ # { # "name": "Foundations", # "kind": "content", # "requirement": "required", # "courseIds": ["crs_01ABC", "crs_01DEF"] # } # ] # } ``` #### Example Response ```json { "data": { "CreateLearningPath": { "id": "lp_01HX5T6U7V8W9X0Y", "name": "New Employee Onboarding Track", "slug": "new-employee-onboarding-track", "status": "draft", "language": "en", "createdAt": "2024-05-01T12:00:00Z", "milestones": [ { "id": "ms_01HX5T6U7V8W9X1Z", "name": "Foundations", "kind": "content", "requirement": "required", "milestoneCourses": [ { "id": "mc_01HX5T6U7V8W9X2A", "contentId": "crs_01ABC", "contentTitle": "Company Culture & Values", "requirement": "required" } ] } ], "client": { "id": "clt_9876543210", "name": "Acme Corp" } } }, "id": "abc-123", "layoutId": "abc-123", "layout": null, "learnerLayoutId": "abc-123", "learnerLayout": null, "name": "example", "sku": "example", "ribbon": null, "slug": "example", "client": null, "createdAt": "2026-01-01T00:00:00Z", "metaTitle": "example", "metaDescription": "example", "asset": "https://example.com", "assetAltText": "example", "assetLibraryId": "abc-123", "detailAsset": "https://example.com", "detailAssetAltText": "example", "detailAssetLibraryId": "abc-123", "heroAsset": "https://example.com", "heroAssetAltText": "example", "priceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "accessDays": 0, "shortDescription": "example", "longDescription": "example", "tags": null, "status": "draft", "freeWithRegistration": false, "hasMultipleCurrencies": false, "prices": null, "customFields": {}, "contentType": "example", "confirmationHtml": "example", "startDate": "2026-01-01T00:00:00Z", "endDate": "2026-01-01T00:00:00Z", "enrollmentStartDate": "2026-01-01T00:00:00Z", "enrollmentEndDate": "2026-01-01T00:00:00Z", "milestones": null, "availabilityStatus": "example", "currentUserStartActionDate": "2026-01-01T00:00:00Z", "isPayAsYouGo": false, "language": "example", "futurePublishDate": "2026-01-01T00:00:00Z", "purchasable": false, "taxable": false, "fulfillmentCenter": null, "taxCategory": "abc-123", "currentUserPendingCertificate": false, "currentUserEarnedCertificate": false, "currentUserDueDate": "2026-01-01T00:00:00Z", "bulkPurchasingEnabled": false, "seatTiers": null, "seatPackages": null, "lastTierPriceInCents": 0, "prerequisiteCourses": null, "prerequisiteLearningPaths": null, "resourceType": "example", "seatsLimit": 0, "videoAsset": "abc-123", "relatedQuery": "example", "relatedQuerySignature": "example", "source": "example", "authors": [ "example" ], "publishDate": "2026-01-01T00:00:00Z", "externalDetailUrl": "example", "externalConditionalRedirectUrl": "example", "kind": "example", "tabs": null, "updatedAt": "2026-01-01T00:00:00Z", "dueDate": "2026-01-01T00:00:00Z", "dueDateDays": 0, "allowUnorderedMilestoneAccess": false, "authoringClient": "abc-123", "isTemplate": false, "externalPurchaseUrl": "example" } ``` :::note The learning path is created in `draft` status and must be published separately. An `authToken` header can scope the operation to a specific user. ::: --- ### ReinstateUserCourse **`Mutation`** | Complexity: **1 pt** — Reinstate an archived course for a user Re-adds a course to the current user's active list after they have previously archived it. This is the inverse of the `ArchiveUserCourse` mutation. On success, the mutation returns the ID of the reinstated course. The acting user can be controlled by passing an `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID!` | Yes | The ID of the archived course to reinstate for the user. | #### Returns | Field | Type | Description | |-------|------|-------------| | `ReinstateUserCourse` | `ID` | The ID of the course that was reinstated, or null if the operation did not succeed. | #### Example Request ```graphql mutation ReinstateUserCourse($id: ID!) { ReinstateUserCourse(id: $id) } ``` #### Example Response ```json { "data": { "ReinstateUserCourse": "course_789" } } ``` :::note Returns `null` if the course was not previously archived by the user or the reinstatement could not be completed. The acting user can be overridden via the `authToken` header. ::: --- ### ReinstateUserLearningPath **`Mutation`** | Complexity: **1 pt** — Reinstate an archived learning path for a user Re-adds a learning path to the current user's active list after they have previously archived it. This is the inverse of the `ArchiveUserLearningPath` mutation. On success, the mutation returns the ID of the reinstated learning path. The acting user can be controlled by passing an `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID!` | Yes | The ID of the archived learning path to reinstate for the user. | #### Returns | Field | Type | Description | |-------|------|-------------| | `ReinstateUserLearningPath` | `ID` | The ID of the learning path that was reinstated, or null if the operation did not succeed. | #### Example Request ```graphql mutation ReinstateUserLearningPath($id: ID!) { ReinstateUserLearningPath(id: $id) } ``` #### Example Response ```json { "data": { "ReinstateUserLearningPath": "lp_456" } } ``` :::note Returns `null` if the learning path was not previously archived by the user or the reinstatement could not be completed. The acting user can be overridden via the `authToken` header. ::: --- ### UpdateCourseStatus **`Mutation`** | Complexity: **1 pt** — Update course status for current user Changes the completion status of a course for the currently authenticated user. You must supply exactly one of `id`, `slug`, or `sku` to identify the course, and set `status` to one of `started`, `not-completed`, or `completed`. An optional `statusChangeDate` (ISO 8601) can be provided to backdate the event; it cannot be a future date. If omitted, the current date and time is used automatically. Pass an `authToken` header to perform the update on behalf of a specific user. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID` | No | The unique ID of the course. | | `slug` | `Slug` | No | The slug of the course. | | `sku` | `String` | No | The SKU of the course. | | `status` | `String!` | Yes | The status to set for the course. Must be one of: `started`, `not-completed`, `completed`. | | `statusChangeDate` | `Date` | No | The date and time the course status was changed (ISO 8601 format). If omitted, the current date and time is used. | #### Returns | Field | Type | Description | |-------|------|-------------| | `UpdateCourseStatus` | `Boolean!` | Returns `true` if the course status was successfully updated for the current user. | #### Example Request ```graphql mutation UpdateCourseStatus { UpdateCourseStatus( slug: "intro-to-graphql" status: "completed" statusChangeDate: "2024-03-15T10:30:00Z" ) } ``` #### Example Response ```json { "data": { "UpdateCourseStatus": true } } ``` :::note Exactly one of `id`, `slug`, or `sku` must be provided. The `statusChangeDate` must not be a future date. Supply an `authToken` request header to target a specific user instead of the caller. ::: --- ### UpdateLearningPathAccess **`Mutation`** | Complexity: **1 pt** — Update learning path access status for a user Changes the access status of a learning path for the current user. Accepted values for `status` are `'not-started'`, `'started'`, and `'not-completed'`. This mutation is typically used to reset or re-open a learner's progress on a learning path, or to mark it as started programmatically. Provide an `authToken` header to target a specific user — without it, the operation applies to the authenticated caller. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `slug` | `Slug!` | Yes | The URL slug that uniquely identifies the learning path whose access status should be updated. | | `status` | `String!` | Yes | The new access status to assign to the user for this learning path. Accepted values are `'not-started'`, `'started'`, and `'not-completed'`. | #### Returns | Field | Type | Description | |-------|------|-------------| | `UpdateLearningPathAccess` | `Boolean!` | Returns `true` if the learning path access status was successfully updated, `false` otherwise. | #### Example Request ```graphql mutation UpdateLearningPathAccess($slug: Slug!, $status: String!) { UpdateLearningPathAccess(slug: $slug, status: $status) } ``` #### Example Response ```json { "data": { "UpdateLearningPathAccess": true } } ``` --- ### UpdateTopicAndCourseProgress **`Mutation`** | Complexity: **1 pt** — Update topic and course progress Records a learner's progress on a specific topic (page) within a course. The `progress` value is an integer between 0 and 100, where 100 indicates the topic has been fully completed. This mutation is typically called by a custom front-end or integration layer to synchronize progress from an external system (such as a SCORM player or a custom video player) back into the Thought Industries platform. The operation affects the currently authenticated user unless an `authToken` header is supplied to impersonate a different user. Returns `true` on success. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `topicId` | `ID!` | Yes | The id of the topic. | | `progress` | `Int!` | Yes | A number between 0 - 100 where 100 represents the topic being completed fully. | #### Returns | Field | Type | Description | |-------|------|-------------| | `Boolean!` | `Boolean!` | Returns true if the progress update was applied successfully. | #### Example Request ```graphql mutation UpdateTopicAndCourseProgress { UpdateTopicAndCourseProgress( topicId: "tpc_01HX3Q4R5S6T7U8V" progress: 100 ) } ``` #### Example Response ```json { "data": { "UpdateTopicAndCourseProgress": true } } ``` :::note Setting `progress` to 100 marks the topic as complete, which may trigger course completion logic, certificates, or downstream webhook events. An `authToken` header can scope the update to a specific learner rather than the API caller. ::: --- ### UpdateUserCourseMeetingAttendance **`Mutation`** | Complexity: **1 pt** — Update user course meeting attendance Records or updates whether a specific user attended a given course meeting. Pass `attended: true` to mark the user as having attended, or `false` to remove their attendance record. You can identify the target meeting by providing `meetingId` alone; `courseId` is optional context that may be used to disambiguate meetings. The mutation returns the full updated `User` object, including their `attendedMeetings` list, so you can confirm the change in a single round-trip. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `userId` | `ID!` | Yes | The ID of the user whose meeting attendance should be updated. | | `courseId` | `ID` | No | The ID of the course associated with the meeting. Optional but may be required to uniquely identify the meeting in some configurations. | | `meetingId` | `ID!` | Yes | The ID of the meeting to mark the user as having attended or not attended. | | `attended` | `Boolean!` | Yes | Set to true to record the user as having attended the meeting, or false to remove their attendance record. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the updated user. | | `email` | `String` | The user's email address. | | `firstName` | `String` | The user's first name. | | `lastName` | `String` | The user's last name. | | `name` | `String` | The user's full display name. | | `attendedMeetings` | `[UserAttendedMeeting!]` | List of meetings the user has attended, including attendance dates and meeting details. | | `purchasedCourses` | `[PurchasedCourse!]` | Courses the user has purchased or enrolled in. | | `activeLicense` | `License` | The user's currently active license. | #### Example Request ```graphql mutation UpdateUserCourseMeetingAttendance { UpdateUserCourseMeetingAttendance( userId: "usr_def456" courseId: "crs_789xyz" meetingId: "mtg_111aaa" attended: true ) { id name email attendedMeetings { attendanceDate meeting { id title startDate endDate } } } } ``` #### Example Response ```json { "data": { "UpdateUserCourseMeetingAttendance": { "id": "usr_def456", "name": "Jane Smith", "email": "jane.smith@example.com", "attendedMeetings": [ { "attendanceDate": "2024-03-15", "meeting": { "id": "mtg_111aaa", "title": "Introduction Session", "startDate": "2024-03-15T10:00:00Z", "endDate": "2024-03-15T11:00:00Z" } } ] } }, "id": "abc-123", "email": "example", "firstName": "example", "abbreviatedName": "example", "asset": "example", "lastName": "example", "name": "example", "lastActiveAt": "2026-01-01T00:00:00Z", "bio": "example", "twoFactorEnabled": false, "firstInitial": "example", "lastInitial": "example", "invitedByName": "example", "shouldHighlight": false, "purchasedCourses": null, "purchasedBundles": null, "activeLicense": null, "allocatedLicenses": null, "allocatedLearningPaths": null, "attendedMeetings": null, "waitlistedCourses": null, "adminClients": null, "roleKey": "example", "address1": "example", "address2": "example", "city": "example", "state": "example", "zipCode": "example", "country": "example", "telephone": "example", "stripeCustomerId": "abc-123", "externalCustomerId": "abc-123", "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "sfAccountId": "abc-123", "sfContactId": "abc-123", "shippingName": "example", "client": null, "clientId": "abc-123", "ref1": "example", "ref2": "example", "ref3": "example", "ref4": "example", "ref5": "example", "ref6": "example", "ref7": "example", "ref8": "example", "ref9": "example", "ref10": "example", "lang": "example", "preferredCurrency": "example", "customFields": {}, "recommendedTags": null, "recommendedSlugs": null, "accessedFlows": [ "example" ], "disabled": false, "learnerUserId": "abc-123", "managerUserId": "abc-123", "balance": 0, "mustVerifyEmail": false, "certificatesCount": 0, "collaborationsCount": 0, "availableCoursesCount": 0, "startedCoursesCount": 0, "completedCoursesCount": 0 } ``` --- --- ## Other Endpoints Operations that could not be automatically categorized. ### RssItems **`Query`** | Complexity: **1 pt** — Fetch RSS feed items Retrieves a list of items from an external RSS feed by URL. Use this query to pull syndicated content into your learning instance — for example, to display blog posts, news articles, or announcements sourced from an RSS feed. The API fetches and parses the remote feed at request time, returning each item's metadata including title, author, description, categories, image, link, and publication date. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `feedUrl` | `String!` | Yes | The fully qualified URL of the RSS feed to fetch and parse. Must be a publicly accessible RSS or Atom feed endpoint. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | A unique identifier for the RSS item, typically the item's URL or GUID from the feed. | | `categories` | `[String!]!` | An array of category labels associated with the RSS item as defined in the feed. | | `title` | `String` | The headline or title of the RSS item. | | `link` | `String` | The URL linking to the full content of the RSS item. | | `author` | `String` | The name of the author of the RSS item. | | `description` | `String` | A summary or excerpt of the RSS item's content. | | `imageUrl` | `String` | The URL of the thumbnail or feature image associated with the RSS item. | | `imageAltTitle` | `String` | The alt text for the item's image, used for accessibility. | | `date` | `Date` | The publication date of the RSS item. | #### Example Request ```graphql query RssItems { RssItems(feedUrl: "https://example.com/feed.rss") { id title author description link categories imageUrl imageAltTitle date } } ``` #### Example Response ```json { "data": { "RssItems": [ { "id": "https://example.com/posts/intro-to-graphql", "title": "Introduction to GraphQL", "author": "Jane Smith", "description": "A beginner-friendly overview of GraphQL and how it compares to REST APIs.", "link": "https://example.com/posts/intro-to-graphql", "categories": [ "Technology", "APIs" ], "imageUrl": "https://example.com/images/graphql-intro.jpg", "imageAltTitle": "GraphQL logo on a blue background", "date": "2024-03-15" }, { "id": "https://example.com/posts/learning-best-practices", "title": "eLearning Best Practices for 2024", "author": "John Doe", "description": "Explore the top trends and strategies shaping online learning this year.", "link": "https://example.com/posts/learning-best-practices", "categories": [ "eLearning", "Education" ], "imageUrl": "https://example.com/images/elearning-2024.jpg", "imageAltTitle": "Person learning on a laptop", "date": "2024-02-28" } ] }, "id": "abc-123", "categories": [ "example" ], "title": "example", "link": "example", "author": "example", "description": "example", "imageUrl": "example", "imageAltTitle": "example", "date": "2026-01-01T00:00:00Z" } ``` :::note The feed is fetched from the remote URL at request time. Availability and response time depend on the external feed's uptime and latency. Ensure the feed URL is publicly accessible from the server. ::: --- --- ## Quiz & Assessment Endpoints Load quiz attempts and submit learner answers. ### LoadAssessmentAttempts **`Query`** | Complexity: **1 pt** — Fetch paginated assessment attempts Returns a paginated list of all assessment attempts recorded in the school. You can narrow results by date range, a specific course, or a specific user. This query is useful for reporting dashboards, compliance tracking, and grading workflows. It requires an `authToken` header that identifies a manager-level user — the operation will run in the context of that user. Each attempt includes the associated assessment, individual answers, question details, pass/fail status, grade, and linked course and user records. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `startDate` | `Date` | No | Filters the returned results to the specified start date. | | `endDate` | `Date` | No | Filters the returned results to the specified end date. | | `courseId` | `ID` | No | Filters the returned attempts to those belonging to the specified course. | | `userId` | `ID` | No | Filters the returned attempts to those belonging to the specified user. | | `page` | `Int` | No | The page number to return within the collection. | | `perPage` | `Int` | No | The amount of items to be returned on the page. | #### Returns | Field | Type | Description | |-------|------|-------------| | `pageInfo` | `CursorPageInfo!` | Pagination metadata including total count, cursor, items per page, and whether more pages are available. | | `assessmentAttempts` | `[AssessmentAttempt!]` | The list of assessment attempts for the current page, each containing grade, pass/fail status, answers, associated user, course, and topic details. | #### Example Request ```graphql query LoadAssessmentAttempts( $startDate: Date $endDate: Date $courseId: ID $userId: ID $page: Int $perPage: Int ) { LoadAssessmentAttempts( startDate: $startDate endDate: $endDate courseId: $courseId userId: $userId page: $page perPage: $perPage ) { pageInfo { perPage cursor total hasMore } assessmentAttempts { id status grade passed forgiven createdAt updatedAt timeElapsedInSeconds courseId assessment { id title type minPassingPercent } user { id email firstName lastName } course { id title slug } answers { id question answer correct } } } } ``` #### Example Response ```json { "data": { "LoadAssessmentAttempts": { "pageInfo": { "perPage": 20, "cursor": "eyJpZCI6IjEwMCJ9", "total": 142, "hasMore": true }, "assessmentAttempts": [ { "id": "attempt_001", "status": "finished", "grade": 85, "passed": true, "forgiven": false, "createdAt": "2024-03-15T10:30:00Z", "updatedAt": "2024-03-15T10:45:00Z", "timeElapsedInSeconds": 900, "courseId": "course_abc123", "assessment": { "id": "assessment_xyz789", "title": "Module 3 Quiz", "type": "quiz", "minPassingPercent": 80 }, "user": { "id": "user_def456", "email": "jane.doe@example.com", "firstName": "Jane", "lastName": "Doe" }, "course": { "id": "course_abc123", "title": "Introduction to Data Science", "slug": "intro-to-data-science" }, "answers": [ { "id": "answer_001", "question": "What is a variable?", "answer": "A named storage location", "correct": true } ] } ] } }, "pageInfo": null, "assessmentAttempts": null } ``` :::note This query requires an `authToken` header set to a manager-level user. Without it the operation will fail or return no results. ::: --- ### LoadAssessmentAttemptsByTopicOrCourse **`Query`** | Complexity: **1 pt** — Load assessment attempts by topic or course Returns all assessment attempts made by the current user for a given topic and/or course. This is useful for displaying a learner's attempt history, reviewing past grades, and determining whether a user may retake an assessment. At least one of `topicId` or `courseId` should be provided to filter results. The user context is resolved from the session or an `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `topicId` | `ID` | No | The ID of the topic to filter assessment attempts by. At least one of `topicId` or `courseId` should be provided. | | `courseId` | `ID` | No | The ID of the course to filter assessment attempts by. At least one of `topicId` or `courseId` should be provided. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the assessment attempt. | | `assessment` | `Assessment` | Metadata about the assessment, including title and minimum passing percentage. | | `answers` | `[AssessmentAnswer!]` | The answers submitted by the user in each attempt. | | `course` | `Course` | The course associated with each assessment attempt. | | `courseId` | `ID` | The ID of the course associated with each attempt. | | `user` | `User` | The user who owns each assessment attempt. | | `createdAt` | `Date` | The date and time when each attempt was created. | | `updatedAt` | `Date` | The date and time when each attempt was last updated. | | `timeElapsedInSeconds` | `Int` | The total time elapsed on each attempt in seconds. | | `status` | `AssessmentAttemptStatus!` | The status of each attempt: `started`, `finished`, or `pending`. | | `grade` | `Int` | The grade achieved on each attempt, expressed as a percentage. | | `originalGrade` | `Int` | The original grade before any admin adjustments. | | `passed` | `Boolean!` | Whether the user passed each attempt. | | `forgiven` | `Boolean!` | Whether a failed attempt has been forgiven by an admin. | | `assignmentSubmissionId` | `ID` | The ID of the linked assignment submission, if applicable. | | `questions` | `[QuizQuestion!]` | The questions included in each attempt along with user responses. | | `topicType` | `TopicType` | The type of topic this assessment belongs to. | | `topic` | `Topic` | The topic associated with each attempt. | | `topicId` | `ID` | The ID of the topic associated with each attempt. | | `questionsCount` | `Int` | The total number of questions in each attempt. | | `correctQuestionsCount` | `Int` | The number of correctly answered questions. | | `answeredQuestionsCount` | `Int` | The number of questions answered by the user. | | `unansweredQuestionsCount` | `Int` | The number of questions left unanswered. | | `questionsWithChoicesCount` | `Int` | The number of questions that include selectable answer choices. | | `poolLabelByQuestionId` | `JSON` | A JSON map of question IDs to their pool labels. | | `linkedWorkbook` | `ID` | The ID of any linked workbook associated with each attempt. | | `adminUpdatedGrade` | `Boolean!` | Whether an admin has manually updated the grade for each attempt. | | `securityToken` | `String` | A security token for proctored or secure assessment flows. | #### Example Request ```graphql query LoadAssessmentAttemptsByTopicOrCourse { LoadAssessmentAttemptsByTopicOrCourse( topicId: "topic-abc123" courseId: "course-xyz789" ) { id status grade passed forgiven createdAt updatedAt questionsCount correctQuestionsCount timeElapsedInSeconds assessment { id title type minPassingPercent } topic { id title type } course { id title slug } } } ``` #### Example Response ```json { "data": { "LoadAssessmentAttemptsByTopicOrCourse": [ { "id": "attempt-001", "status": "finished", "grade": 75, "passed": false, "forgiven": false, "createdAt": "2024-03-01T10:00:00Z", "updatedAt": "2024-03-01T10:15:00Z", "questionsCount": 10, "correctQuestionsCount": 7, "timeElapsedInSeconds": 900, "assessment": { "id": "assessment-001", "title": "Module 1 Quiz", "type": "quiz", "minPassingPercent": 80 }, "topic": { "id": "topic-abc123", "title": "Module 1 Quiz", "type": "quiz" }, "course": { "id": "course-xyz789", "title": "Introduction to APIs", "slug": "introduction-to-apis" } }, { "id": "attempt-002", "status": "finished", "grade": 90, "passed": true, "forgiven": false, "createdAt": "2024-03-05T14:00:00Z", "updatedAt": "2024-03-05T14:12:00Z", "questionsCount": 10, "correctQuestionsCount": 9, "timeElapsedInSeconds": 720, "assessment": { "id": "assessment-001", "title": "Module 1 Quiz", "type": "quiz", "minPassingPercent": 80 }, "topic": { "id": "topic-abc123", "title": "Module 1 Quiz", "type": "quiz" }, "course": { "id": "course-xyz789", "title": "Introduction to APIs", "slug": "introduction-to-apis" } } ] }, "id": "abc-123", "assessment": null, "answers": null, "course": null, "courseId": "abc-123", "user": null, "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "timeElapsedInSeconds": 0, "status": "started", "grade": 0, "originalGrade": 0, "passed": false, "assignmentSubmissionId": "abc-123", "questions": null, "topicType": "ad", "forgiven": false, "topic": null, "topicId": "abc-123", "questionsCount": 0, "correctQuestionsCount": 0, "answeredQuestionsCount": 0, "unansweredQuestionsCount": 0, "questionsWithChoicesCount": 0, "poolLabelByQuestionId": {}, "linkedWorkbook": "abc-123", "adminUpdatedGrade": false, "securityToken": "example" } ``` :::note Both `topicId` and `courseId` are optional individually, but providing at least one is recommended to return meaningful results. Results are scoped to the current authenticated user. ::: --- ### LoadAssessmentAttemptWithQuestions **`Query`** | Complexity: **1 pt** — Load assessment attempt with questions Returns an existing or newly created assessment attempt for a given topic, populated with all quiz questions. Call this query when a user starts a new attempt or resumes an in-progress one. If `assessmentAttemptId` is supplied, the matching in-progress attempt is resumed; otherwise, a new attempt is initiated. You can optionally shuffle and subset questions, associate the attempt with a course or linked workbook, or take the assessment on behalf of a learner as an instructor. The user context is resolved from the session or an `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID!` | Yes | The ID of the Topic. | | `shouldShuffleAndSubset` | `Boolean` | No | Flag to randomly order questions and choose a subset of questions to use. | | `courseId` | `ID` | No | The ID of the course. | | `linkedWorkbook` _(deprecated)_ | `ID` | No | The ID of the linked workbook to associate with this assessment attempt. Deprecated in favor of `linkedWorkbookId`. | | `linkedWorkbookId` | `ID` | No | The ID of the linked workbook. | | `instructorAssessmentUser` _(deprecated)_ | `ID` | No | The ID of the user on behalf of whom the instructor is taking the assessment. Deprecated in favor of `instructorAssessmentUserId`. | | `instructorAssessmentUserId` | `ID` | No | The ID of the user on behalf of whom the instructor takes the assessment. | | `topicType` | `AssessmentTopicType!` | Yes | The type of the Topic. | | `assessmentAttemptId` | `ID` | No | The ID of the in-progress assessment attempt to resume from. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the assessment attempt. | | `assessment` | `Assessment` | Metadata about the assessment being attempted, including title and minimum passing percentage. | | `answers` | `[AssessmentAnswer!]` | The answers submitted by the user in this attempt, including correctness details. | | `course` | `Course` | The course associated with this assessment attempt. | | `courseId` | `ID` | The ID of the course associated with this attempt. | | `user` | `User` | The user who owns this assessment attempt. | | `createdAt` | `Date` | The date and time when this attempt was created. | | `updatedAt` | `Date` | The date and time when this attempt was last updated. | | `timeElapsedInSeconds` | `Int` | The total time elapsed on this attempt in seconds. | | `status` | `AssessmentAttemptStatus!` | The current status of the attempt: `started`, `finished`, or `pending`. | | `grade` | `Int` | The grade achieved on this attempt, expressed as a percentage. | | `originalGrade` | `Int` | The original grade before any admin adjustments. | | `passed` | `Boolean!` | Whether the user passed this assessment attempt. | | `forgiven` | `Boolean!` | Whether a failed attempt has been forgiven by an admin. | | `assignmentSubmissionId` | `ID` | The ID of the linked assignment submission, if applicable. | | `questions` | `[QuizQuestion!]` | The list of quiz questions included in this attempt, with answer choices and response data. | | `topicType` | `TopicType` | The type of topic this assessment belongs to (e.g., quiz, survey, test). | | `topic` | `Topic` | The topic associated with this assessment attempt. | | `topicId` | `ID` | The ID of the topic associated with this attempt. | | `questionsCount` | `Int` | The total number of questions in this attempt. | | `correctQuestionsCount` | `Int` | The number of questions answered correctly. | | `answeredQuestionsCount` | `Int` | The number of questions the user has answered so far. | | `unansweredQuestionsCount` | `Int` | The number of questions the user has not yet answered. | | `questionsWithChoicesCount` | `Int` | The number of questions that include answer choices. | | `poolLabelByQuestionId` | `JSON` | A JSON map of question IDs to their question pool labels, used when questions are drawn from pools. | | `linkedWorkbook` | `ID` | The ID of the linked workbook associated with this attempt, if any. | | `adminUpdatedGrade` | `Boolean!` | Whether an admin has manually updated the grade for this attempt. | | `securityToken` | `String` | A security token associated with this attempt, used for proctored or secure assessment flows. | #### Example Request ```graphql query LoadAssessmentAttemptWithQuestions { LoadAssessmentAttemptWithQuestions( id: "topic-abc123" topicType: quiz courseId: "course-xyz789" shouldShuffleAndSubset: true ) { id status grade passed forgiven questionsCount correctQuestionsCount answeredQuestionsCount unansweredQuestionsCount timeElapsedInSeconds assessment { id title type minPassingPercent } questions { questionId body type required choices { choiceId value correct } } topic { id title type } course { id title slug } } } ``` #### Example Response ```json { "data": { "LoadAssessmentAttemptWithQuestions": { "id": "attempt-001", "status": "started", "grade": null, "passed": false, "forgiven": false, "questionsCount": 10, "correctQuestionsCount": 0, "answeredQuestionsCount": 0, "unansweredQuestionsCount": 10, "timeElapsedInSeconds": 0, "assessment": { "id": "assessment-001", "title": "Module 1 Quiz", "type": "quiz", "minPassingPercent": 80 }, "questions": [ { "questionId": "q-001", "body": "What is the primary purpose of GraphQL?", "type": "multipleChoice", "required": true, "choices": [ { "choiceId": "c-001", "value": "Query and manipulate data via a typed schema", "correct": true }, { "choiceId": "c-002", "value": "Style web pages", "correct": false }, { "choiceId": "c-003", "value": "Manage relational databases", "correct": false } ] } ], "topic": { "id": "topic-abc123", "title": "Module 1 Quiz", "type": "quiz" }, "course": { "id": "course-xyz789", "title": "Introduction to APIs", "slug": "introduction-to-apis" } } }, "id": "abc-123", "assessment": null, "answers": null, "course": null, "courseId": "abc-123", "user": null, "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "timeElapsedInSeconds": 0, "status": "started", "grade": 0, "originalGrade": 0, "passed": false, "assignmentSubmissionId": "abc-123", "questions": null, "topicType": "ad", "forgiven": false, "topic": null, "topicId": "abc-123", "questionsCount": 0, "correctQuestionsCount": 0, "answeredQuestionsCount": 0, "unansweredQuestionsCount": 0, "questionsWithChoicesCount": 0, "poolLabelByQuestionId": {}, "linkedWorkbook": "abc-123", "adminUpdatedGrade": false, "securityToken": "example" } ``` :::note Either a new attempt is created or an existing in-progress attempt is resumed depending on whether `assessmentAttemptId` is provided. Pass `shouldShuffleAndSubset: true` to randomize and limit the question set presented to the learner. ::: --- ### LoadSuperQuizInfo **`Query`** | Complexity: **1 pt** — Load super quiz questions and settings Retrieves the combined question set and configuration data for one or more quizzes that make up a super quiz experience. This is typically used to assemble a single, unified quiz session that spans multiple underlying quiz topics within a course. You can optionally pass a `courseId` to scope the request and control whether previously correct answers are included. If an `authToken` header is provided, the response may be personalized to reflect the authenticated user's previous answers and progress. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `courseId` | `ID` | No | The ID of the course that contains the quizzes, used to scope the query context. | | `includeCorrectAnswers` | `Boolean` | No | Flag to include questions the user previously got correct. | | `quizzes` | `[ID!]!` | Yes | The IDs of the Quizzes user selects. | #### Returns | Field | Type | Description | |-------|------|-------------| | `quizzes` | `[ID!]` | The IDs of the quizzes included in this super quiz session. | | `questions` | `[QuizQuestion!]` | The aggregated list of questions drawn from all specified quizzes. | | `totalTimeInSeconds` | `Int` | The total time limit for the entire super quiz session in seconds, if a timer is enabled. | | `questionSkipEnabled` | `Boolean` | Whether the learner is allowed to skip questions and return to them later. | | `timerEnabled` | `Boolean` | Whether a countdown timer is active for this super quiz. | | `navigationDisabled` | `Boolean` | Whether backward navigation between questions is disabled. | | `displayAllHints` | `Boolean` | Whether hints are shown for all questions by default. | | `timePerQuestionInSeconds` | `Int` | Per-question time limit in seconds, if applicable. | #### Example Request ```graphql query LoadSuperQuizInfo( $courseId: ID $includeCorrectAnswers: Boolean $quizzes: [ID!]! ) { LoadSuperQuizInfo( courseId: $courseId includeCorrectAnswers: $includeCorrectAnswers quizzes: $quizzes ) { quizzes totalTimeInSeconds timerEnabled navigationDisabled questionSkipEnabled displayAllHints timePerQuestionInSeconds questions { questionId body type required isMultipleChoice isOpenEnded choices { choiceId value correct } } } } ``` #### Example Response ```json { "data": { "LoadSuperQuizInfo": { "quizzes": [ "quiz_001", "quiz_002" ], "totalTimeInSeconds": 1800, "timerEnabled": true, "navigationDisabled": false, "questionSkipEnabled": true, "displayAllHints": false, "timePerQuestionInSeconds": null, "questions": [ { "questionId": "q_abc123", "body": "Which of the following is a supervised learning algorithm?", "type": "multipleChoice", "required": true, "isMultipleChoice": true, "isOpenEnded": false, "choices": [ { "choiceId": "choice_1", "value": "Linear Regression", "correct": true }, { "choiceId": "choice_2", "value": "K-Means Clustering", "correct": false } ] } ] } }, "quizzes": [ "abc-123" ], "questions": null, "totalTimeInSeconds": 0, "questionSkipEnabled": false, "timerEnabled": false, "navigationDisabled": false, "displayAllHints": false, "timePerQuestionInSeconds": 0 } ``` --- ### CreateAssessmentAttempt **`Mutation`** | Complexity: **1 pt** — Create an assessment attempt Creates a new assessment attempt for a learner on a specific course topic. This mutation is primarily used to initiate a super quiz attempt, where the caller supplies the set of questions the learner has selected. An optional `userId` allows an admin or integration to create the attempt on behalf of a specific user; if omitted, the authenticated user is used. A `grade` value can also be provided directly to set the attempt's score at creation time. The acting user can be overridden by supplying an `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `topicId` | `ID!` | Yes | The ID of the topic. | | `courseId` | `ID!` | Yes | The ID of the course. | | `questions` | `[QuestionInput!]!` | Yes | The questions user selected for super quiz. | | `userId` | `ID` | No | The ID of the user to take the assessment. | | `grade` | `Int` | No | The grade of the assessment attempt. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the newly created assessment attempt. | | `assessment` | `Assessment` | The assessment definition linked to this attempt. | | `answers` | `[AssessmentAnswer!]` | The individual answers recorded for this attempt. | | `course` | `Course` | The course associated with this attempt. | | `courseId` | `ID` | The ID of the associated course. | | `user` | `User` | The learner who owns this attempt. | | `createdAt` | `Date` | Timestamp when the attempt was created. | | `updatedAt` | `Date` | Timestamp when the attempt was last updated. | | `timeElapsedInSeconds` | `Int` | Time spent on the attempt, in seconds. | | `status` | `AssessmentAttemptStatus!` | Current status of the attempt: started, finished, or pending. | | `grade` | `Int` | The grade assigned to this attempt as a percentage. | | `originalGrade` | `Int` | The grade before any administrative overrides. | | `passed` | `Boolean!` | Whether the learner passed the assessment. | | `assignmentSubmissionId` | `ID` | ID of a related assignment submission, if applicable. | | `questions` | `[QuizQuestion!]` | The quiz questions included in this attempt. | | `topicType` | `TopicType` | The topic type (e.g. quiz, test, survey) for this attempt. | | `forgiven` | `Boolean!` | Whether the attempt has been administratively forgiven. | | `topic` | `Topic` | The topic resource this attempt is associated with. | | `topicId` | `ID` | The ID of the topic. | | `questionsCount` | `Int` | Total number of questions in the attempt. | | `correctQuestionsCount` | `Int` | Number of correctly answered questions. | | `answeredQuestionsCount` | `Int` | Number of questions that received an answer. | | `unansweredQuestionsCount` | `Int` | Number of questions that were not answered. | | `questionsWithChoicesCount` | `Int` | Number of questions that offered selectable choices. | | `poolLabelByQuestionId` | `JSON` | Map of question IDs to their question pool labels. | | `linkedWorkbook` | `ID` | ID of a linked workbook, if any. | | `adminUpdatedGrade` | `Boolean!` | Whether the grade was manually overridden by an administrator. | | `securityToken` | `String` | A security token associated with this attempt. | #### Example Request ```graphql mutation CreateAssessmentAttempt { CreateAssessmentAttempt( topicId: "topic_quiz_001" courseId: "course_abc123" questions: [ { questionId: "q1", response: "Paris" } { questionId: "q2", response: "choice_b" } ] userId: "user_001" ) { id status grade passed forgiven adminUpdatedGrade createdAt updatedAt timeElapsedInSeconds assessment { id title type minPassingPercent } questionsCount correctQuestionsCount answeredQuestionsCount unansweredQuestionsCount topic { id title type } user { id email firstName lastName } } } ``` #### Example Response ```json { "data": { "CreateAssessmentAttempt": { "id": "attempt_new_def456", "status": "finished", "grade": 90, "passed": true, "forgiven": false, "adminUpdatedGrade": false, "createdAt": "2024-03-15T09:00:00Z", "updatedAt": "2024-03-15T09:12:00Z", "timeElapsedInSeconds": 720, "assessment": { "id": "assessment_002", "title": "Super Quiz - Chapter 5", "type": "quiz", "minPassingPercent": 80 }, "questionsCount": 10, "correctQuestionsCount": 9, "answeredQuestionsCount": 10, "unansweredQuestionsCount": 0, "topic": { "id": "topic_quiz_001", "title": "Chapter 5 Quiz", "type": "quiz" }, "user": { "id": "user_001", "email": "learner@example.com", "firstName": "Jane", "lastName": "Doe" } } }, "id": "abc-123", "assessment": null, "answers": null, "course": null, "courseId": "abc-123", "user": null, "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "timeElapsedInSeconds": 0, "status": "started", "grade": 0, "originalGrade": 0, "passed": false, "assignmentSubmissionId": "abc-123", "questions": null, "topicType": "ad", "forgiven": false, "topic": null, "topicId": "abc-123", "questionsCount": 0, "correctQuestionsCount": 0, "answeredQuestionsCount": 0, "unansweredQuestionsCount": 0, "questionsWithChoicesCount": 0, "poolLabelByQuestionId": {}, "linkedWorkbook": "abc-123", "adminUpdatedGrade": false, "securityToken": "example" } ``` :::note The `questions` argument accepts a list of `QuestionInput` objects representing the learner's selected questions for a super quiz. If `userId` is omitted, the attempt is created for the authenticated user. ::: --- ### CreateAssignmentSubmission **`Mutation`** | Complexity: **1 pt** — Create an assignment submission Creates a new assignment submission for the current user within a specified course. This mutation is used when a learner completes a manually graded assignment — which may include written text, an uploaded file, a video, or a linked quiz attempt. When a quiz attempt is associated, it ties the assessment answers to the submission for instructor review. The acting user can be overridden by supplying an `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `body` | `String` | No | The body of the assignment submission. | | `videoAsset` | `String` | No | The URL of the video asset. | | `asset` | `String` | No | The URL of the asset. | | `quizAttempt` | `ID` | No | The ID of the assessment attempt. | | `assignment` | `ID` | No | The ID of the assignment. | | `course` | `ID!` | Yes | The ID of the course. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier of the assignment submission. | | `assignment` | `Assignment` | The assignment this submission belongs to. | | `courseId` | `ID` | The ID of the course in which the submission was made. | | `course` | `Course` | The full course object associated with this submission. | | `user` | `User` | The user who created the submission. | | `body` | `String` | The written text body of the submission. | | `asset` | `URL` | URL of an uploaded file asset attached to the submission. | | `additionalAsset` | `URL` | URL of a secondary uploaded file asset, if any. | | `videoAsset` | `String` | Identifier or URL of a video asset attached to the submission. | | `grade` | `AssignmentSubmissionGrade` | The grade applied to the submission: completed or incomplete. | | `numericGrade` | `Float` | A numeric grade value assigned to the submission by an instructor. | | `gradeFeedback` | `String` | Written feedback from the instructor about the submission's grade. | | `commentsCount` | `Int!` | The number of comments attached to this submission. | | `adminUpdatedGrade` | `Boolean!` | Indicates whether an administrator has manually updated the grade. | | `quizAttempt` | `AssessmentAttempt` | An associated assessment attempt linked to this assignment submission, if applicable. | | `license` | `License` | The license under which this submission was made, if applicable. | | `createdAt` | `Date` | Timestamp when the submission was created. | | `updatedAt` | `Date` | Timestamp when the submission was last updated. | #### Example Request ```graphql mutation CreateAssignmentSubmission { CreateAssignmentSubmission( course: "course_abc123" assignment: "assignment_def456" body: "My completed assignment response goes here." asset: "https://cdn.example.com/uploads/submission_file.pdf" videoAsset: null quizAttempt: null ) { id body grade numericGrade gradeFeedback commentsCount adminUpdatedGrade createdAt updatedAt assignment { id title description } course { id title slug } user { id email name } asset videoAsset } } ``` #### Example Response ```json { "data": { "CreateAssignmentSubmission": { "id": "submission_ghi789", "body": "My completed assignment response goes here.", "grade": "completed", "numericGrade": null, "gradeFeedback": null, "commentsCount": 0, "adminUpdatedGrade": false, "createdAt": "2024-06-01T10:30:00Z", "updatedAt": "2024-06-01T10:30:00Z", "assignment": { "id": "assignment_def456", "title": "Final Project Essay", "description": "Write a 500-word essay on the topic covered in Module 5." }, "course": { "id": "course_abc123", "title": "Introduction to Geography", "slug": "intro-to-geography" }, "user": { "id": "user_456", "email": "learner@example.com", "name": "Jane Doe" }, "asset": "https://cdn.example.com/uploads/submission_file.pdf", "videoAsset": null } }, "id": "abc-123", "assignment": null, "courseId": "abc-123", "course": null, "user": null, "asset": "https://example.com", "additionalAsset": "https://example.com", "videoAsset": "example", "body": "example", "commentsCount": 0, "numericGrade": 0, "grade": "completed", "gradeFeedback": "example", "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "quizAttempt": null, "adminUpdatedGrade": false, "license": null } ``` --- ### CreateUnansweredAssessmentAttempt **`Mutation`** | Complexity: **1 pt** — Create an assessment attempt for unanswered questions Creates a new assessment attempt that contains only the questions the user left unanswered in a prior completed attempt. This is used in assessment flows where question-skipping is enabled and the learner chooses to go back and answer skipped questions after completing the rest of the assessment. You must provide the topic ID, course ID, and the ID of the previously completed attempt to derive which questions were unanswered. The acting user can be overridden by supplying an `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `topicId` | `ID!` | Yes | The ID of the topic. | | `courseId` | `ID!` | Yes | The ID of the course. | | `completedAssessmentAttemptId` | `ID!` | Yes | The ID of the last completed assessment attempt. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier of the newly created unanswered-questions attempt. | | `assessment` | `Assessment` | The assessment this attempt belongs to. | | `answers` | `[AssessmentAnswer!]` | Recorded answers for this attempt (initially empty until questions are answered). | | `course` | `Course` | The course containing the assessment topic. | | `courseId` | `ID` | The ID of the course for this attempt. | | `user` | `User` | The user this attempt belongs to. | | `status` | `AssessmentAttemptStatus!` | Current status of the attempt (started, finished, or pending). | | `grade` | `Int` | The percentage grade for this attempt once completed. | | `passed` | `Boolean!` | Whether the user passed this attempt. | | `forgiven` | `Boolean!` | Whether this attempt has been administratively forgiven. | | `adminUpdatedGrade` | `Boolean!` | Whether an administrator has manually overridden the grade. | | `questions` | `[QuizQuestion!]` | The unanswered questions from the prior attempt, now presented for answering. | | `topic` | `Topic` | The topic that contains the assessment. | | `topicId` | `ID` | The ID of the topic for this attempt. | | `topicType` | `TopicType` | The type of topic (quiz, test, survey, etc.). | | `questionsCount` | `Int` | Total number of questions in this unanswered attempt. | | `unansweredQuestionsCount` | `Int` | Number of questions still unanswered in this attempt. | | `answeredQuestionsCount` | `Int` | Number of questions answered so far in this attempt. | | `correctQuestionsCount` | `Int` | Number of questions answered correctly. | | `assignmentSubmissionId` | `ID` | ID of a linked assignment submission, if applicable. | | `securityToken` | `String` | Security token for proctored assessment validation. | | `createdAt` | `Date` | Timestamp when this attempt was created. | | `updatedAt` | `Date` | Timestamp when this attempt was last updated. | #### Example Request ```graphql mutation CreateUnansweredAssessmentAttempt { CreateUnansweredAssessmentAttempt( topicId: "topic_pqr678" courseId: "course_abc123" completedAssessmentAttemptId: "attempt_xyz789" ) { id status grade passed questionsCount unansweredQuestionsCount answeredQuestionsCount forgiven adminUpdatedGrade createdAt assessment { id title minPassingPercent } questions { questionId body type choices { choiceId value correct } } course { id title } user { id email } } } ``` #### Example Response ```json { "data": { "CreateUnansweredAssessmentAttempt": { "id": "attempt_stu901", "status": "started", "grade": null, "passed": false, "questionsCount": 3, "unansweredQuestionsCount": 3, "answeredQuestionsCount": 0, "forgiven": false, "adminUpdatedGrade": false, "createdAt": "2024-06-01T12:00:00Z", "assessment": { "id": "assessment_001", "title": "Module 3 Quiz", "minPassingPercent": 80 }, "questions": [ { "questionId": "q_003", "body": "What is the largest ocean on Earth?", "type": "multipleChoice", "choices": [ { "choiceId": "c_001", "value": "Pacific Ocean", "correct": true }, { "choiceId": "c_002", "value": "Atlantic Ocean", "correct": false } ] } ], "course": { "id": "course_abc123", "title": "Introduction to Geography" }, "user": { "id": "user_456", "email": "learner@example.com" } } }, "id": "abc-123", "assessment": null, "answers": null, "course": null, "courseId": "abc-123", "user": null, "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "timeElapsedInSeconds": 0, "status": "started", "grade": 0, "originalGrade": 0, "passed": false, "assignmentSubmissionId": "abc-123", "questions": null, "topicType": "ad", "forgiven": false, "topic": null, "topicId": "abc-123", "questionsCount": 0, "correctQuestionsCount": 0, "answeredQuestionsCount": 0, "unansweredQuestionsCount": 0, "questionsWithChoicesCount": 0, "poolLabelByQuestionId": {}, "linkedWorkbook": "abc-123", "adminUpdatedGrade": false, "securityToken": "example" } ``` :::note This mutation is intended for assessments where `questionSkipEnabled` is true on the topic. It requires a previously completed attempt ID to determine which questions were skipped. ::: --- ### MergeAssessmentAttemptIntoComplete **`Mutation`** | Complexity: **1 pt** — Merge assessment attempt into completed attempt Merges an in-progress assessment attempt into the most recently completed assessment attempt. This mutation is intended for use after a learner finishes reviewing unanswered questions in a resumed session — the answers from the current attempt are folded into the last completed attempt, producing a single unified result. The acting user can be overridden by supplying an `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `completedAssessmentAttemptId` | `ID!` | Yes | The ID of the last completed assessment attempt. | | `assessmentAttemptId` | `ID!` | Yes | The ID of the assessment attempt. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the merged assessment attempt. | | `assessment` | `Assessment` | The assessment definition associated with this attempt, including title, type, and passing threshold. | | `answers` | `[AssessmentAnswer!]` | The list of individual answers recorded in this attempt. | | `course` | `Course` | The course to which this assessment attempt belongs. | | `courseId` | `ID` | The ID of the associated course. | | `user` | `User` | The learner who submitted this assessment attempt. | | `createdAt` | `Date` | Timestamp when the attempt was first created. | | `updatedAt` | `Date` | Timestamp when the attempt was last updated. | | `timeElapsedInSeconds` | `Int` | Total time the learner spent on the attempt, in seconds. | | `status` | `AssessmentAttemptStatus!` | Current status of the attempt: started, finished, or pending. | | `grade` | `Int` | The final grade (as a percentage) assigned to this attempt. | | `originalGrade` | `Int` | The grade before any administrative overrides. | | `passed` | `Boolean!` | Whether the learner passed the assessment. | | `assignmentSubmissionId` | `ID` | ID of the associated assignment submission, if applicable. | | `questions` | `[QuizQuestion!]` | The list of quiz questions included in this attempt. | | `topicType` | `TopicType` | The type of topic this assessment belongs to (e.g. quiz, test, survey). | | `forgiven` | `Boolean!` | Whether this attempt has been forgiven by an admin. | | `topic` | `Topic` | The topic resource this assessment attempt is linked to. | | `topicId` | `ID` | The ID of the associated topic. | | `questionsCount` | `Int` | Total number of questions in the attempt. | | `correctQuestionsCount` | `Int` | Number of questions answered correctly. | | `answeredQuestionsCount` | `Int` | Number of questions that were answered. | | `unansweredQuestionsCount` | `Int` | Number of questions left unanswered. | | `questionsWithChoicesCount` | `Int` | Number of questions that included selectable answer choices. | | `poolLabelByQuestionId` | `JSON` | A JSON map of question IDs to their question pool labels. | | `linkedWorkbook` | `ID` | ID of a linked workbook associated with this attempt, if any. | | `adminUpdatedGrade` | `Boolean!` | Whether an administrator has manually overridden the grade. | | `securityToken` | `String` | A security token associated with this assessment attempt. | #### Example Request ```graphql mutation MergeAssessmentAttemptIntoComplete { MergeAssessmentAttemptIntoComplete( completedAssessmentAttemptId: "attempt_completed_abc123" assessmentAttemptId: "attempt_current_xyz789" ) { id status grade passed forgiven adminUpdatedGrade createdAt updatedAt timeElapsedInSeconds assessment { id title type minPassingPercent } answers { id question answer correct } questionsCount correctQuestionsCount answeredQuestionsCount unansweredQuestionsCount user { id email firstName lastName } } } ``` #### Example Response ```json { "data": { "MergeAssessmentAttemptIntoComplete": { "id": "attempt_completed_abc123", "status": "finished", "grade": 85, "passed": true, "forgiven": false, "adminUpdatedGrade": false, "createdAt": "2024-03-10T14:22:00Z", "updatedAt": "2024-03-10T14:35:00Z", "timeElapsedInSeconds": 780, "assessment": { "id": "assessment_001", "title": "Module 3 Final Quiz", "type": "test", "minPassingPercent": 75 }, "answers": [ { "id": "answer_001", "question": "What is the capital of France?", "answer": "Paris", "correct": true }, { "id": "answer_002", "question": "What is 2 + 2?", "answer": "4", "correct": true } ], "questionsCount": 20, "correctQuestionsCount": 17, "answeredQuestionsCount": 20, "unansweredQuestionsCount": 0, "user": { "id": "user_001", "email": "learner@example.com", "firstName": "Jane", "lastName": "Doe" } } }, "id": "abc-123", "assessment": null, "answers": null, "course": null, "courseId": "abc-123", "user": null, "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "timeElapsedInSeconds": 0, "status": "started", "grade": 0, "originalGrade": 0, "passed": false, "assignmentSubmissionId": "abc-123", "questions": null, "topicType": "ad", "forgiven": false, "topic": null, "topicId": "abc-123", "questionsCount": 0, "correctQuestionsCount": 0, "answeredQuestionsCount": 0, "unansweredQuestionsCount": 0, "questionsWithChoicesCount": 0, "poolLabelByQuestionId": {}, "linkedWorkbook": "abc-123", "adminUpdatedGrade": false, "securityToken": "example" } ``` :::note The `authToken` request header determines which user's attempt is acted upon. Ensure `assessmentAttemptId` refers to the current in-progress attempt and `completedAssessmentAttemptId` refers to the previously finished attempt for the same assessment. ::: --- ### UpdateAssessmentAttempt **`Mutation`** | Complexity: **1 pt** — Update an assessment attempt Updates an in-progress assessment attempt for the current user. This mutation is called repeatedly during an assessment session — once per question to record the user's selected answer, and again when the user submits the assessment to mark it as finished. The returned `AssessmentAttempt` includes grading details, question-level data, and associated course and user information. The acting user can be overridden by supplying an `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `activeQuestion` | `QuestionInput` | No | The active question with user's selected choice(s). | | `assessmentAttempt` | `AssessmentAttemptInput` | No | The assessment attempt to be updated. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier of the assessment attempt. | | `assessment` | `Assessment` | The assessment associated with this attempt, including its title, type, and minimum passing percentage. | | `answers` | `[AssessmentAnswer!]` | List of recorded answers for this attempt, including correctness indicators. | | `course` | `Course` | The course in which this assessment attempt was made. | | `courseId` | `ID` | The ID of the course associated with this attempt. | | `user` | `User` | The user who made this assessment attempt. | | `status` | `AssessmentAttemptStatus!` | Current status of the attempt: started, finished, or pending. | | `grade` | `Int` | The calculated percentage grade for this attempt. | | `originalGrade` | `Int` | The original grade before any administrative overrides. | | `passed` | `Boolean!` | Whether the user passed the assessment based on the minimum passing percentage. | | `forgiven` | `Boolean!` | Whether this attempt has been forgiven (overridden to passing) by an administrator. | | `adminUpdatedGrade` | `Boolean!` | Indicates whether an administrator has manually updated the grade for this attempt. | | `questions` | `[QuizQuestion!]` | The list of quiz questions included in this attempt, with response and grading details. | | `topic` | `Topic` | The topic (lesson page) that contains the assessment. | | `topicId` | `ID` | The ID of the topic containing the assessment. | | `topicType` | `TopicType` | The type of topic (e.g., quiz, test, survey) for this attempt. | | `questionsCount` | `Int` | Total number of questions in the assessment. | | `correctQuestionsCount` | `Int` | Number of questions answered correctly. | | `answeredQuestionsCount` | `Int` | Number of questions the user has answered. | | `unansweredQuestionsCount` | `Int` | Number of questions left unanswered. | | `questionsWithChoicesCount` | `Int` | Number of questions that have predefined answer choices. | | `timeElapsedInSeconds` | `Int` | Total time the user has spent on this attempt, in seconds. | | `assignmentSubmissionId` | `ID` | If this attempt is linked to an assignment submission, the ID of that submission. | | `linkedWorkbook` | `ID` | The ID of a linked workbook associated with this attempt, if any. | | `poolLabelByQuestionId` | `JSON` | A JSON map of question IDs to their question pool labels, used for pooled assessments. | | `securityToken` | `String` | A security token associated with this attempt, used for proctored assessments. | | `createdAt` | `Date` | Timestamp when the attempt was created. | | `updatedAt` | `Date` | Timestamp when the attempt was last updated. | #### Example Request ```graphql mutation UpdateAssessmentAttempt( $activeQuestion: QuestionInput $assessmentAttempt: AssessmentAttemptInput ) { UpdateAssessmentAttempt( activeQuestion: $activeQuestion assessmentAttempt: $assessmentAttempt ) { id status grade passed questionsCount correctQuestionsCount answeredQuestionsCount unansweredQuestionsCount timeElapsedInSeconds forgiven adminUpdatedGrade assessment { id title type minPassingPercent } answers { id question answer correct } course { id title slug } user { id email name } } } ``` #### Example Response ```json { "data": { "UpdateAssessmentAttempt": { "id": "attempt_xyz789", "status": "finished", "grade": 85, "passed": true, "questionsCount": 10, "correctQuestionsCount": 8, "answeredQuestionsCount": 10, "unansweredQuestionsCount": 0, "timeElapsedInSeconds": 342, "forgiven": false, "adminUpdatedGrade": false, "assessment": { "id": "assessment_001", "title": "Module 3 Quiz", "type": "quiz", "minPassingPercent": 80 }, "answers": [ { "id": "answer_001", "question": "What is the capital of France?", "answer": "Paris", "correct": true } ], "course": { "id": "course_abc123", "title": "Introduction to Geography", "slug": "intro-to-geography" }, "user": { "id": "user_456", "email": "learner@example.com", "name": "Jane Doe" } } }, "id": "abc-123", "assessment": null, "answers": null, "course": null, "courseId": "abc-123", "user": null, "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "timeElapsedInSeconds": 0, "status": "started", "grade": 0, "originalGrade": 0, "passed": false, "assignmentSubmissionId": "abc-123", "questions": null, "topicType": "ad", "forgiven": false, "topic": null, "topicId": "abc-123", "questionsCount": 0, "correctQuestionsCount": 0, "answeredQuestionsCount": 0, "unansweredQuestionsCount": 0, "questionsWithChoicesCount": 0, "poolLabelByQuestionId": {}, "linkedWorkbook": "abc-123", "adminUpdatedGrade": false, "securityToken": "example" } ``` --- --- ## User & Auth Endpoints Authenticate learners and read the current session user. ### BadgeLeaderboard **`Query`** | Complexity: **1 pt** — Fetch badge leaderboard Returns the badge leaderboard for the learning instance, showing the top-ranked learners by badge count along with the award type used for ranking. This is useful for displaying competitive gamification widgets. It also returns the current user's own position on the leaderboard, even if they are not in the top results. The user context and leaderboard personalization can be set via an `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `limit` | `Int` | No | The maximum number of leaders to return in the leaderboard. If omitted, the server's default limit applies. | #### Returns | Field | Type | Description | |-------|------|-------------| | `awardType` | `AwardType` | The award type used as the basis for ranking learners on this leaderboard. | | `leaders` | `[BadgeLeader]` | An ordered list of top-ranked learners, each with their rank, badge total, and user profile information. | | `currentUserPosition` | `BadgeLeader` | The current user's rank and badge total on the leaderboard, returned regardless of whether they appear in the top leaders list. | #### Example Request ```graphql query BadgeLeaderboard { BadgeLeaderboard(limit: 10) { awardType { id label pluralLabel includeOnDashboard icon } leaders { rank total user { id firstName lastName email name } } currentUserPosition { rank total user { id firstName lastName email } } } } ``` #### Example Response ```json { "data": { "BadgeLeaderboard": { "awardType": { "id": "award-type-001", "label": "Course Completion", "pluralLabel": "Course Completions", "includeOnDashboard": true, "icon": "star" }, "leaders": [ { "rank": 1, "total": 42, "user": { "id": "user-456", "firstName": "Alice", "lastName": "Smith", "email": "alice.smith@example.com", "name": "Alice Smith" } }, { "rank": 2, "total": 35, "user": { "id": "user-789", "firstName": "Bob", "lastName": "Jones", "email": "bob.jones@example.com", "name": "Bob Jones" } } ], "currentUserPosition": { "rank": 14, "total": 18, "user": { "id": "user-123", "firstName": "Jane", "lastName": "Doe", "email": "jane.doe@example.com" } } } }, "awardType": null, "leaders": null, "currentUserPosition": null } ``` --- ### CompanyDetails **`Query`** | Complexity: **1 pt** — Fetch company details Returns high-level information about a Thought Industries learning instance, including the company name, subdomain, and appearance settings such as logo, brand colors, fonts, favicon, custom CSS, and global navigation links. Optionally accepts a user ID to scope the response to a specific user's context. Use this query to bootstrap a branded UI or retrieve instance-level configuration. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `user` | `ID` | No | Optional ID of a user to scope the company details response to that user's context. | #### Returns | Field | Type | Description | |-------|------|-------------| | `name` | `String` | The display name of the company or learning instance. | | `subdomain` | `String` | The subdomain identifier for the learning instance. | | `settings` | `AppearanceSettings` | Appearance and branding configuration including logo, colors, fonts, favicon, custom CSS, and navigation links. | #### Example Request ```graphql query CompanyDetails { CompanyDetails(user: "user_abc123") { name subdomain settings { id logoAsset accentColor secondaryColor linkColor font altFont favicon backgroundAsset backgroundAssetTiled retinaLogo customCss globalNavigationLinks { label href } } } } ``` #### Example Response ```json { "data": { "CompanyDetails": { "name": "Acme Learning Co.", "subdomain": "acme", "settings": { "id": "settings_001", "logoAsset": "https://cdn.acme.com/logo.png", "accentColor": "#FF5733", "secondaryColor": "#3498DB", "linkColor": "#2980B9", "font": "Inter", "altFont": "Merriweather", "favicon": "https://cdn.acme.com/favicon.ico", "backgroundAsset": "https://cdn.acme.com/bg.jpg", "backgroundAssetTiled": false, "retinaLogo": true, "customCss": ".header { font-weight: bold; }", "globalNavigationLinks": [ { "label": "Home", "href": "/" }, { "label": "Catalog", "href": "/catalog" } ] } } }, "name": "example", "subdomain": "example", "settings": null } ``` --- ### CompanyTranslations **`Query`** | Complexity: **1 pt** — Fetch company translations by namespace Returns all configured translation strings for the learning instance, grouped by language, within a specified namespace. Use `lms` as the namespace to retrieve platform UI translations, or `emails` to retrieve email template translations. Each entry in the result includes the language code and a JSON object of key-value translation pairs. This is useful for building multilingual interfaces or auditing the translation configuration of your instance. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `namespace` | `TranslationNamespace!` | Yes | The namespace is used to specify the content category for the translations. Accepts values of `lms` for platform content and `emails` for email content. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | A unique identifier for the translation set entry. | | `lang` | `String!` | The BCP 47 language code for this set of translations (e.g., `en`, `fr`, `de`). | | `translations` | `JSON!` | A JSON object containing translation key-value pairs for the specified namespace and language. | #### Example Request ```graphql query CompanyTranslations { CompanyTranslations(namespace: lms) { id lang translations } } ``` #### Example Response ```json { "data": { "CompanyTranslations": [ { "id": "trans_en_lms_001", "lang": "en", "translations": { "welcome_message": "Welcome to the Learning Portal", "start_course": "Start Course", "my_courses": "My Courses" } }, { "id": "trans_fr_lms_001", "lang": "fr", "translations": { "welcome_message": "Bienvenue sur le portail d'apprentissage", "start_course": "Commencer le cours", "my_courses": "Mes cours" } } ] }, "id": "abc-123", "lang": "example", "translations": {} } ``` --- ### CurrentUser **`Query`** | Complexity: **1 pt** — Fetch the currently authenticated user Returns the full User object for the currently authenticated user, including profile details (name, email, bio), purchased courses and bundles, allocated licenses, learning path allocations, waitlisted courses, attended meetings, client associations, custom reference fields, and aggregate statistics such as certificate and completed course counts. Returns null if no user is authenticated. This is the primary query for bootstrapping a learner or admin session. The user context can be overridden by passing an `authToken` header. #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier of the user. | | `email` | `String` | User's email address. | | `firstName` | `String` | User's first name. | | `lastName` | `String` | User's last name. | | `name` | `String` | User's full display name. | | `roleKey` | `String` | The user's role within the platform. | | `purchasedCourses` | `[PurchasedCourse!]` | Courses the user has purchased, including status and certificate information. | | `purchasedBundles` | `[PurchasedBundle!]` | Bundles the user has purchased. | | `activeLicense` | `License` | The user's currently active license. | | `allocatedLicenses` | `[AllocatedLicense!]` | Licenses that have been allocated to this user. | | `allocatedLearningPaths` | `[AllocatedLearningPath!]` | Learning paths allocated to this user. | | `attendedMeetings` | `[UserAttendedMeeting!]` | Meetings the user has attended. | | `waitlistedCourses` | `[WaitlistedCourse!]` | Courses the user is currently waitlisted for. | | `adminClients` | `[Client]` | Clients for which this user has admin access. | | `client` | `Client` | The primary client/organization this user belongs to. | | `disabled` | `Boolean!` | Whether the user account is disabled. | | `mustVerifyEmail` | `Boolean!` | Whether the user must verify their email before proceeding. | | `certificatesCount` | `Int!` | Total number of certificates earned by the user. | | `completedCoursesCount` | `Int!` | Number of courses the user has completed. | | `startedCoursesCount` | `Int!` | Number of courses the user has started. | | `availableCoursesCount` | `Int!` | Number of courses currently available to the user. | | `balance` | `Float` | The user's current credit balance. | | `customFields` | `JSON` | Arbitrary custom field data attached to the user. | | `recommendedTags` | `[UserRecommendedTag!]` | Tags recommended for this user, with boost weights for personalization. | | `recommendedSlugs` | `[UserRecommendedSlug!]` | Content slugs recommended for this user, with boost weights. | #### Example Request ```graphql query CurrentUser { CurrentUser { id email firstName lastName name roleKey lang preferredCurrency disabled mustVerifyEmail certificatesCount completedCoursesCount startedCoursesCount availableCoursesCount balance purchasedCourses { courseId status certificateIssuedAt } activeLicense { id name } client { id name } } } ``` #### Example Response ```json { "data": { "CurrentUser": { "id": "usr_abc123", "email": "jane.doe@example.com", "firstName": "Jane", "lastName": "Doe", "name": "Jane Doe", "roleKey": "learner", "lang": "en", "preferredCurrency": "USD", "disabled": false, "mustVerifyEmail": false, "certificatesCount": 4, "completedCoursesCount": 8, "startedCoursesCount": 11, "availableCoursesCount": 15, "balance": 50, "purchasedCourses": [ { "courseId": "crs_xyz789", "status": "active", "certificateIssuedAt": "2024-02-20T00:00:00.000Z" } ], "activeLicense": { "id": "lic_001", "name": "Enterprise License" }, "client": { "id": "cli_001", "name": "Acme Corp" } } }, "id": "abc-123", "email": "example", "firstName": "example", "abbreviatedName": "example", "asset": "example", "lastName": "example", "name": "example", "lastActiveAt": "2026-01-01T00:00:00Z", "bio": "example", "twoFactorEnabled": false, "firstInitial": "example", "lastInitial": "example", "invitedByName": "example", "shouldHighlight": false, "purchasedCourses": null, "purchasedBundles": null, "activeLicense": null, "allocatedLicenses": null, "allocatedLearningPaths": null, "attendedMeetings": null, "waitlistedCourses": null, "adminClients": null, "roleKey": "example", "address1": "example", "address2": "example", "city": "example", "state": "example", "zipCode": "example", "country": "example", "telephone": "example", "stripeCustomerId": "abc-123", "externalCustomerId": "abc-123", "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "sfAccountId": "abc-123", "sfContactId": "abc-123", "shippingName": "example", "client": null, "clientId": "abc-123", "ref1": "example", "ref2": "example", "ref3": "example", "ref4": "example", "ref5": "example", "ref6": "example", "ref7": "example", "ref8": "example", "ref9": "example", "ref10": "example", "lang": "example", "preferredCurrency": "example", "customFields": {}, "recommendedTags": null, "recommendedSlugs": null, "accessedFlows": [ "example" ], "disabled": false, "learnerUserId": "abc-123", "managerUserId": "abc-123", "balance": 0, "mustVerifyEmail": false, "certificatesCount": 0, "collaborationsCount": 0, "availableCoursesCount": 0, "startedCoursesCount": 0, "completedCoursesCount": 0 } ``` :::note Returns null when no authenticated user session exists. This is a high-cost query due to the depth of nested objects available — request only the fields you need. ::: --- ### Languages **`Query`** | Complexity: **1 pt** — Fetch supported languages Returns the list of languages enabled on the learning instance. Each language entry includes a display label, a language code, an optional selector label (used in language-picker UI elements), and a flag indicating whether the language is a custom addition. Use this query to populate language selectors, validate supported locales, or understand the multilingual configuration of your instance. #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID` | A unique identifier for the language record. | | `label` | `String!` | The human-readable display name of the language (e.g., "English", "French"). | | `code` | `String!` | The BCP 47 language code for the language (e.g., `en`, `fr`). | | `selectorLabel` | `String` | An optional label used in language-picker UI components, which may differ from the standard label. | | `isCustom` | `Boolean` | Indicates whether this language was added as a custom language rather than being a built-in supported language. | #### Example Request ```graphql query Languages { Languages { id label code selectorLabel isCustom } } ``` #### Example Response ```json { "data": { "Languages": [ { "id": "lang_001", "label": "English", "code": "en", "selectorLabel": "English (US)", "isCustom": false }, { "id": "lang_002", "label": "French", "code": "fr", "selectorLabel": "Français", "isCustom": false }, { "id": "lang_003", "label": "Acme Corporate Speak", "code": "en-acme", "selectorLabel": "Acme English", "isCustom": true } ] }, "id": "abc-123", "label": "example", "code": "example", "selectorLabel": "example", "isCustom": false } ``` --- ### LearningPathCurrentUserAwardTotals **`Query`** | Complexity: **1 pt** — Fetch current user's award totals for a learning path Returns the aggregated award totals that the currently authenticated learner has earned across all resources (courses or learning paths) within a specified learning path, identified by its slug. Each entry in the result describes the award type, the resource it was earned from, and the total amount accumulated. This is useful for building progress dashboards, award summaries, or gamification displays. The results are scoped to the current user — pass an `authToken` header to set the user context. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `slug` | `Slug!` | Yes | The slug identifier of the learning path for which to retrieve the current user's award totals. | #### Returns | Field | Type | Description | |-------|------|-------------| | `resource` | `AwardResource` | The resource (Course or LearningPath) from which the awards were earned. Use inline fragments to access type-specific fields. | | `resourceId` | `ID!` | The ID of the resource from which awards were earned. | | `awardType` | `AwardType!` | The type of award, including its label, icon, and dashboard visibility settings. | | `total` | `Float!` | The total quantity of this award type earned by the current user for the given resource. | #### Example Request ```graphql query GetLearningPathAwardTotals($slug: Slug!) { LearningPathCurrentUserAwardTotals(slug: $slug) { resourceId total awardType { id label pluralLabel icon includeOnDashboard } resource { ... on Course { id title slug } ... on LearningPath { id name slug } } } } ``` #### Example Response ```json { "data": { "LearningPathCurrentUserAwardTotals": [ { "resourceId": "course_abc123", "total": 3, "awardType": { "id": "award_type_001", "label": "Credit", "pluralLabel": "Credits", "icon": "star", "includeOnDashboard": true }, "resource": { "id": "course_abc123", "title": "Foundations of Leadership", "slug": "foundations-of-leadership" } }, { "resourceId": "course_def456", "total": 1.5, "awardType": { "id": "award_type_001", "label": "Credit", "pluralLabel": "Credits", "icon": "star", "includeOnDashboard": true }, "resource": { "id": "course_def456", "title": "Advanced Communication", "slug": "advanced-communication" } } ] }, "resource": null, "resourceId": "abc-123", "awardType": null, "total": 0 } ``` :::note This query is user-scoped. You must provide an `authToken` header to identify the learner; without it, results may be empty or reflect an unauthenticated context. ::: --- ### TermsAndConditions **`Query`** | Complexity: **1 pt** — Fetch terms and conditions Returns the global terms and conditions text configured for the learning instance. Use this query to display the platform's terms to users during registration, enrollment, or other consent workflows. This query takes no arguments and requires no user context. #### Returns | Field | Type | Description | |-------|------|-------------| | `globalTerms` | `String` | The full text of the global terms and conditions for the learning instance. | #### Example Request ```graphql query TermsAndConditions { TermsAndConditions { globalTerms } } ``` #### Example Response ```json { "data": { "TermsAndConditions": { "globalTerms": "By accessing this platform, you agree to abide by the terms and conditions set forth herein. All content is provided for educational purposes only. Unauthorized redistribution is prohibited." } }, "globalTerms": "example" } ``` --- ### UserArchivedContent **`Query`** | Complexity: **1 pt** — Fetch full content details for the current user's archived items Returns a non-nullable list of full Content objects representing the content that the currently authenticated user has archived. Unlike `UserArchives`, which returns lightweight archive metadata, this query returns the complete Content type — including pricing, tags, badge information, and availability details — for each archived item. Use this when you need to render rich content cards in an archived items view. #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier of the content item. | | `title` | `String` | Display title of the content item. | | `slug` | `Slug!` | URL-friendly identifier for the content item. | | `kind` | `ContentKind` | The type of content (e.g. course, webinar, learningPath). | | `status` | `Status` | Publication status of the content. | | `description` | `String` | Full description of the content item. | | `asset` | `String` | URL of the content's thumbnail or hero image. | | `priceInCents` | `Int` | Price of the content in cents. | | `authors` | `[String]` | List of author names associated with this content. | | `tags` | `[Tag]` | Tags associated with the content item. | | `prices` | `[ItemPrice!]` | Multi-currency pricing details. | | `waitlistingEnabled` | `Boolean!` | Whether waitlisting is enabled. | | `isActive` | `Boolean!` | Whether the content is currently active. | | `url` | `AbsoluteOrRelativeURL` | URL to the content detail page. | | `badgeUrl` | `String` | URL to the badge image earned for this content. | #### Example Request ```graphql query UserArchivedContent { UserArchivedContent { id title slug kind status description asset priceInCents authors tags { id label } waitlistingEnabled isActive url } } ``` #### Example Response ```json { "data": { "UserArchivedContent": [ { "id": "crs_abc001", "title": "Data Science Fundamentals", "slug": "data-science-fundamentals", "kind": "course", "status": "archived", "description": "A beginner-friendly introduction to data science concepts.", "asset": "https://cdn.example.com/images/data-science.jpg", "priceInCents": 4999, "authors": [ "Jane Smith" ], "tags": [ { "id": "tag_001", "label": "Data Science" }, { "id": "tag_002", "label": "Beginner" } ], "waitlistingEnabled": false, "isActive": true, "url": "/courses/data-science-fundamentals" } ] }, "id": "abc-123", "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "expiresAt": "2026-01-01T00:00:00Z", "courseGroup": "example", "title": "example", "sessionTitle": "example", "ribbon": null, "slug": "example", "kind": "courseGroup", "metaTitle": "example", "language": "example", "metaDescription": "example", "displayCourse": "abc-123", "displayCourseSlug": "example", "displayDate": "2026-01-01T00:00:00Z", "availabilityStatus": "example", "asset": "example", "assetAltText": "example", "freeWithRegistration": false, "priceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "rating": 0, "description": "example", "sku": "example", "hasChildren": false, "tags": null, "status": "draft", "customFields": {}, "authors": [ "example" ], "authorsAndInstructors": [ "example" ], "contentTypeLabel": "example", "contentTypeAssetAspectRatio": "example", "waitlistCount": 0, "seatsLimit": 0, "enrollmentCount": 0, "source": "example", "publishDate": "2026-01-01T00:00:00Z", "courseStartDate": "2026-01-01T00:00:00Z", "courseEndDate": "2026-01-01T00:00:00Z", "enrollmentStartDate": "2026-01-01T00:00:00Z", "enrollmentEndDate": "2026-01-01T00:00:00Z", "timeZone": "example", "meetingStartDate": "2026-01-01T00:00:00Z", "coursePresold": false, "courseGracePeriodEnded": false, "currentUserMayReschedule": false, "isActive": false, "embeddedEnabled": false, "altDescriptionBody": "example", "hideCourseDescription": false, "annotationsEnabled": false, "currentUserUnmetCoursePrerequisites": [ "abc-123" ], "currentUserUnmetLearningPathPrerequisites": [ "abc-123" ], "currentUserDueDate": "2026-01-01T00:00:00Z", "url": "example", "waitlistingEnabled": false, "waitlistingTriggered": false, "canAddToQueue": false, "bulkPurchasingEnabled": false, "prices": null, "location": null, "state": "example", "acceptBadgeUrl": "example", "issuedAt": "2026-01-01T00:00:00Z", "badgeUrl": "example", "credlyBadgeExpiresAt": "2026-01-01T00:00:00Z", "imageUrl": "example", "badgeName": "example", "completedPassedDueDate": false } ``` :::note This query always returns a non-null list (`[Content!]!`). The user is identified by the `authToken` header. ::: --- ### UserArchives **`Query`** | Complexity: **1 pt** — Fetch the current user's archived content records Returns a list of ArchivedContent records representing content that the currently authenticated user has archived. Each record includes metadata about the archived resource such as its name, archival date, status, and whether it can be reinstated. This is helpful for building an archived content management view in a learner portal. #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier of the archived content record. | | `name` | `String` | Display name of the archived content. | | `resourceType` | `String` | The type of resource that was archived (e.g., Course). | | `status` | `String` | Current status of the archived record. | | `archivedAt` | `Date` | Timestamp when the content was archived. | | `reinstatable` | `Boolean!` | Whether the user can reinstate this archived content. | | `waitlistActive` | `Boolean!` | Whether a waitlist is currently active for this content. | | `user` | `ID` | ID of the user who archived the content. | | `resource` | `ID` | ID of the archived content resource. | | `company` | `Company` | The company/tenant context associated with this archived record. | #### Example Request ```graphql query UserArchives { UserArchives { id name resourceType status archivedAt reinstatable waitlistActive user resource } } ``` #### Example Response ```json { "data": { "UserArchives": [ { "id": "arc_xyz789", "name": "Introduction to Machine Learning", "resourceType": "Course", "status": "archived", "archivedAt": "2024-03-15T10:30:00.000Z", "reinstatable": true, "waitlistActive": false, "user": "usr_def456", "resource": "crs_ghi123" } ] }, "id": "abc-123", "company": null, "user": "abc-123", "resource": "abc-123", "resourceType": "example", "status": "example", "archivedAt": "2026-01-01T00:00:00Z", "name": "example", "reinstatable": false, "waitlistActive": false } ``` :::note The user context is set via the `authToken` request header. ::: --- ### UserCertificateFields **`Query`** | Complexity: **1 pt** — Fetch user certificate fields Returns the list of certificate fields configured for the learning instance. Certificate fields define what information appears on learner certificates and transcripts — for example, learner name, course name, completion date, award type, custom field values, and unique identifiers. Each field specifies its type, display label, and whether it should be included on the transcript or external certificate. Providing an `authToken` header scopes the response to the context of a specific user, which may influence which fields are surfaced. #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the certificate field. | | `type` | `CertificateFieldType!` | The type of data this field represents on the certificate (e.g., `nameLabel`, `contentCompletionDate`, `awardLabel`, `identifier`). | | `label` | `String!` | The display label shown for this field on the certificate or transcript. | | `includeOnTranscript` | `Boolean!` | Whether this field is included when generating a learner transcript. | | `includeOnExternalCertificate` | `Boolean!` | Whether this field is included on externally shareable certificates. | | `text` | `String` | Static text content for fields of type `textLabel`. | | `awardType` | `AwardType` | The award type object associated with this field, populated when the field type is `awardLabel`. | | `awardTypeId` | `ID` | The ID of the associated award type for `awardLabel` fields. | | `customField` | `CustomField` | The content custom field object associated with this certificate field, for `contentFieldLabel` types. | | `customFieldSlug` | `Slug` | The slug of the associated content custom field. | | `sessionCustomContentFieldSlug` | `Slug` | The slug of the session-level custom content field associated with this certificate field. | | `userCustomField` | `UserCustomField` | The user custom field object associated with this certificate field, for `userFieldLabel` types. | | `userCustomFieldSlug` | `Slug` | The slug of the associated user custom field. | | `identifierPrefix` | `String` | An optional prefix string prepended to generated certificate identifier values. | | `identifierBodyLength` | `Int` | The character length of the randomly generated body portion of a certificate identifier. | | `hasIdentifierPrefix` | `Boolean` | Indicates whether this identifier field uses a prefix. | | `dateFormat` | `CertificateDateFormatType` | The format used to display dates on the certificate (e.g., `usLong`, `iso8601`, `euroShort`). | #### Example Request ```graphql query UserCertificateFields { UserCertificateFields { id type label includeOnTranscript includeOnExternalCertificate text dateFormat identifierPrefix identifierBodyLength hasIdentifierPrefix awardTypeId awardType { id label pluralLabel icon includeOnDashboard deleted } customFieldSlug customField { id label slug indexed multiple } userCustomFieldSlug userCustomField { id label slug type required } sessionCustomContentFieldSlug } } ``` #### Example Response ```json { "data": { "UserCertificateFields": [ { "id": "certfield_001", "type": "nameLabel", "label": "Learner Name", "includeOnTranscript": true, "includeOnExternalCertificate": true, "text": null, "dateFormat": null, "identifierPrefix": null, "identifierBodyLength": null, "hasIdentifierPrefix": false, "awardTypeId": null, "awardType": null, "customFieldSlug": null, "customField": null, "userCustomFieldSlug": null, "userCustomField": null, "sessionCustomContentFieldSlug": null }, { "id": "certfield_002", "type": "contentCompletionDate", "label": "Completion Date", "includeOnTranscript": true, "includeOnExternalCertificate": true, "text": null, "dateFormat": "usLong", "identifierPrefix": null, "identifierBodyLength": null, "hasIdentifierPrefix": false, "awardTypeId": null, "awardType": null, "customFieldSlug": null, "customField": null, "userCustomFieldSlug": null, "userCustomField": null, "sessionCustomContentFieldSlug": null }, { "id": "certfield_003", "type": "awardLabel", "label": "Credits Earned", "includeOnTranscript": true, "includeOnExternalCertificate": false, "text": null, "dateFormat": null, "identifierPrefix": null, "identifierBodyLength": null, "hasIdentifierPrefix": false, "awardTypeId": "award_type_007", "awardType": { "id": "award_type_007", "label": "Credit", "pluralLabel": "Credits", "icon": "star", "includeOnDashboard": true, "deleted": false }, "customFieldSlug": null, "customField": null, "userCustomFieldSlug": null, "userCustomField": null, "sessionCustomContentFieldSlug": null }, { "id": "certfield_004", "type": "identifier", "label": "Certificate ID", "includeOnTranscript": true, "includeOnExternalCertificate": true, "text": null, "dateFormat": null, "identifierPrefix": "CERT-", "identifierBodyLength": 8, "hasIdentifierPrefix": true, "awardTypeId": null, "awardType": null, "customFieldSlug": null, "customField": null, "userCustomFieldSlug": null, "userCustomField": null, "sessionCustomContentFieldSlug": null } ] }, "id": "abc-123", "type": "nameLabel", "label": "example", "includeOnTranscript": false, "includeOnExternalCertificate": false, "text": "example", "awardType": null, "awardTypeId": "abc-123", "customField": null, "customFieldSlug": "example", "sessionCustomContentFieldSlug": "example", "userCustomField": null, "userCustomFieldSlug": "example", "identifierPrefix": "example", "identifierBodyLength": 0, "hasIdentifierPrefix": false, "dateFormat": "iso8601" } ``` :::note The response can be user-scoped by including an `authToken` header in the request. Without it, the query returns instance-level certificate field configuration. This query returns `null` if no certificate fields have been configured. ::: --- ### UserCertificates **`Query`** | Complexity: **1 pt** — Fetch certificates earned by a user Returns certificates that the current user has earned or been granted, including details about the certificate template, the associated course or learning path resource, issuance and expiration dates, and downloadable URLs. Optionally filter by a search query string or include expired certificates in the results. The user context can be overridden by supplying an `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `query` | `String` | No | An optional search string to filter certificates, for example by resource title. | | `includeExpiredCertificates` | `Boolean` | No | When set to `true`, expired certificates are included in the results. Defaults to `false`. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier of the certificate. | | `certificateTemplate` | `CertificateTemplate` | The template used to generate this certificate, including design and expiration configuration. | | `user` | `User!` | The user who earned or was granted this certificate. | | `issuedAt` | `Date!` | The date and time the certificate was issued. | | `url` | `AbsoluteOrRelativeURL!` | The URL where the certificate can be viewed. | | `pdfAsset` | `AbsoluteOrRelativeURL` | A direct URL to the PDF version of the certificate, if available. | | `resource` | `CertificateResource` | The course or learning path this certificate was issued for. | | `resourceType` | `CertificateResourceType` | Indicates whether the resource is a `course` or `learningPath`. | | `resourceTypeLabel` | `String` | A human-readable label for the resource type. | | `resourceId` | `ID` | The ID of the course or learning path this certificate is associated with. | | `progressWasReset` | `Boolean!` | Whether the user's progress was reset as part of a recertification cycle. | | `expirationDate` | `Date` | The date on which this certificate expires, if applicable. | | `didExpire` | `Boolean!` | Whether the certificate has passed its expiration date. | | `deleted` | `Boolean!` | Whether the certificate record has been soft-deleted. | | `recertificationDate` | `Date` | The date by which the user should recertify, if applicable. | | `isExpired` | `Boolean!` | Whether the certificate is currently expired. | | `source` | `String` | The source system or integration that generated the certificate. | | `contentItem` | `Content` | The content item associated with this certificate, providing catalog-level metadata. | | `isExternal` | `Boolean!` | Whether the certificate was issued by an external system. | | `externalResourceTitle` | `String` | The title of the external resource associated with the certificate, if applicable. | | `identifier` | `String` | An optional human-readable certificate identifier or number. | #### Example Request ```graphql query UserCertificates($query: String, $includeExpiredCertificates: Boolean) { UserCertificates(query: $query, includeExpiredCertificates: $includeExpiredCertificates) { id issuedAt expirationDate didExpire isExpired progressWasReset deleted url pdfAsset resourceType resourceTypeLabel resourceId identifier isExternal externalResourceTitle certificateTemplate { id title asset isExpired } user { id email firstName lastName } } } # Variables: # { "includeExpiredCertificates": false } ``` #### Example Response ```json { "data": { "UserCertificates": [ { "id": "cert_001", "issuedAt": "2024-01-20T14:00:00Z", "expirationDate": "2025-01-20T14:00:00Z", "didExpire": false, "isExpired": false, "progressWasReset": false, "deleted": false, "url": "/certificates/cert_001", "pdfAsset": "https://cdn.example.com/certificates/cert_001.pdf", "resourceType": "course", "resourceTypeLabel": "Course", "resourceId": "course_456", "identifier": "CERT-2024-001", "isExternal": false, "externalResourceTitle": null, "certificateTemplate": { "id": "tmpl_01", "title": "Course Completion Certificate", "asset": "https://cdn.example.com/templates/completion.png", "isExpired": false }, "user": { "id": "user_123", "email": "jane.doe@example.com", "firstName": "Jane", "lastName": "Doe" } } ] }, "id": "abc-123", "certificateTemplate": null, "user": null, "issuedAt": "2026-01-01T00:00:00Z", "url": "example", "pdfAsset": "example", "resource": null, "resourceType": "course", "resourceTypeLabel": "example", "resourceId": "abc-123", "progressWasReset": false, "expirationDate": "2026-01-01T00:00:00Z", "didExpire": false, "deleted": false, "recertificationDate": "2026-01-01T00:00:00Z", "isExpired": false, "source": "example", "contentItem": null, "isExternal": false, "externalResourceTitle": "example", "identifier": "example" } ``` --- ### UserContentGroups **`Query`** | Complexity: **1 pt** — Fetch content group counts for the current user Returns a list of ContentGroup objects representing the counts of various content groupings available to the current user — such as the number of active content items, learning paths, completed items, certificates, bookmark folders, and waitlisted courses. This is ideal for rendering summary statistics or navigation badges in a learner dashboard. You can optionally filter the results by passing a search query string, and control whether expired certificates are included in the certificate count. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `query` | `String` | No | This query argument allows you to filter content on various fields such as tags, authors, and custom content fields. Some example queries are: `tags:"Premium Package"`, `difficulty:Easy`, `authors:"Jack"`. See documentation here: https://support.thoughtindustries.com/hc/en-us/articles/360046307253-Writing-a-Search-Query | | `includeExpiredCertificates` | `Boolean` | No | When true, expired certificates are included in the certificates group count. Defaults to false. | #### Returns | Field | Type | Description | |-------|------|-------------| | `kind` | `ContentGroupKind!` | The category of content group (e.g., contentItems, certificates, learningPaths). | | `count` | `Int!` | The number of items the current user has in this content group. | #### Example Request ```graphql query UserContentGroups { UserContentGroups(query: "tags:\"Premium Package\"", includeExpiredCertificates: false) { kind count } } ``` #### Example Response ```json { "data": { "UserContentGroups": [ { "kind": "contentItems", "count": 12 }, { "kind": "completedItems", "count": 5 }, { "kind": "certificates", "count": 3 }, { "kind": "learningPaths", "count": 2 }, { "kind": "bookmarkFolders", "count": 1 }, { "kind": "waitlistedCourses", "count": 1 }, { "kind": "archivedContentItems", "count": 4 } ] }, "kind": "contentItems", "count": 0 } ``` --- ### UserContentItems **`Query`** | Complexity: **3 pts** — Fetch content items accessible to a user Returns a list of content items that the current user has access to, such as enrolled or purchased courses, learning paths, and other content types. Results can be filtered by a search query string, narrowed to specific content kinds, and sorted in various ways. Supplying an `authToken` header changes the user context for this operation. This query carries an additional API cost of 3 points. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `query` | `String` | No | This query argument allows you to filter content on various fields such as tags, authors, and custom content fields. Some example queries are: `tags:"Premium Package"`, `difficulty:Easy`, `authors:"Jack"`. See documentation here: https://support.thoughtindustries.com/hc/en-us/articles/360046307253-Writing-a-Search-Query | | `kind` | `[ContentKind!]` | No | Filters results to only include content of the specified kinds (e.g., `course`, `learningPath`). | | `sortColumn` | `SortColumn` | No | The field by which results should be sorted. | | `sortDirection` | `SortDirection` | No | The direction of the sort, either ascending or descending. | | `sort` _(deprecated)_ | `String` | No | A raw sort string for advanced sorting scenarios. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier of the content item. | | `title` | `String` | Display title of the content item. | | `slug` | `Slug!` | URL-friendly slug for the content item. | | `kind` | `ContentKind` | The type of content (e.g., course, learningPath, webinar). | | `status` | `Status` | Publication status of the content item. | | `availabilityStatus` | `String` | A human-readable string describing the content's current availability. | | `asset` | `String` | URL of the content item's thumbnail or cover image. | | `priceInCents` | `Int` | Price of the content item in the smallest currency unit (e.g., cents). | | `isActive` | `Boolean!` | Whether the content item is currently active and accessible. | | `tags` | `[Tag]` | Tags associated with this content item. | | `currentUserDueDate` | `Date` | The due date for the current user to complete this content, if set. | #### Example Request ```graphql query UserContentItems($query: String, $kind: [ContentKind!], $sortColumn: SortColumn, $sortDirection: SortDirection) { UserContentItems(query: $query, kind: $kind, sortColumn: $sortColumn, sortDirection: $sortDirection) { id title slug kind status availabilityStatus asset priceInCents isActive courseStartDate courseEndDate currentUserDueDate tags { id label } authors } } # Variables: # { # "query": "tags:\"Premium Package\"", # "kind": ["course", "learningPath"], # "sortColumn": "title", # "sortDirection": "asc" # } ``` #### Example Response ```json { "data": { "UserContentItems": [ { "id": "cg_001", "title": "Advanced Data Analytics", "slug": "advanced-data-analytics", "kind": "course", "status": "published", "availabilityStatus": "available", "asset": "https://cdn.example.com/images/data-analytics.jpg", "priceInCents": 4999, "isActive": true, "courseStartDate": null, "courseEndDate": null, "currentUserDueDate": "2024-12-31T00:00:00Z", "tags": [ { "id": "tag_101", "label": "Premium Package" } ], "authors": [ "Dr. Alice Smith" ] }, { "id": "lp_002", "title": "Data Science Learning Path", "slug": "data-science-learning-path", "kind": "learningPath", "status": "published", "availabilityStatus": "available", "asset": "https://cdn.example.com/images/data-science-lp.jpg", "priceInCents": 0, "isActive": true, "courseStartDate": null, "courseEndDate": null, "currentUserDueDate": null, "tags": [ { "id": "tag_101", "label": "Premium Package" } ], "authors": [] } ] }, "id": "abc-123", "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "expiresAt": "2026-01-01T00:00:00Z", "courseGroup": "example", "title": "example", "sessionTitle": "example", "ribbon": null, "slug": "example", "kind": "courseGroup", "metaTitle": "example", "language": "example", "metaDescription": "example", "displayCourse": "abc-123", "displayCourseSlug": "example", "displayDate": "2026-01-01T00:00:00Z", "availabilityStatus": "example", "asset": "example", "assetAltText": "example", "freeWithRegistration": false, "priceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "rating": 0, "description": "example", "sku": "example", "hasChildren": false, "tags": null, "status": "draft", "customFields": {}, "authors": [ "example" ], "authorsAndInstructors": [ "example" ], "contentTypeLabel": "example", "contentTypeAssetAspectRatio": "example", "waitlistCount": 0, "seatsLimit": 0, "enrollmentCount": 0, "source": "example", "publishDate": "2026-01-01T00:00:00Z", "courseStartDate": "2026-01-01T00:00:00Z", "courseEndDate": "2026-01-01T00:00:00Z", "enrollmentStartDate": "2026-01-01T00:00:00Z", "enrollmentEndDate": "2026-01-01T00:00:00Z", "timeZone": "example", "meetingStartDate": "2026-01-01T00:00:00Z", "coursePresold": false, "courseGracePeriodEnded": false, "currentUserMayReschedule": false, "isActive": false, "embeddedEnabled": false, "altDescriptionBody": "example", "hideCourseDescription": false, "annotationsEnabled": false, "currentUserUnmetCoursePrerequisites": [ "abc-123" ], "currentUserUnmetLearningPathPrerequisites": [ "abc-123" ], "currentUserDueDate": "2026-01-01T00:00:00Z", "url": "example", "waitlistingEnabled": false, "waitlistingTriggered": false, "canAddToQueue": false, "bulkPurchasingEnabled": false, "prices": null, "location": null, "state": "example", "acceptBadgeUrl": "example", "issuedAt": "2026-01-01T00:00:00Z", "badgeUrl": "example", "credlyBadgeExpiresAt": "2026-01-01T00:00:00Z", "imageUrl": "example", "badgeName": "example", "completedPassedDueDate": false } ``` :::note This query has an additional cost of 3 API points. ::: --- ### UserCourseAwardCounts **`Query`** | Complexity: **1 pt** — Fetch award counts for the current user on a course Returns a list of award types and the number of each award the current user has earned for a specific course. Awards are configured per-course and may represent continuing education credits, completion badges, or other achievement types. The authenticated user is determined by the `authToken` header — without it, this query returns counts for the anonymous/default user context. Use this query to display a learner's earned credentials or credits on a course completion screen or dashboard. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `courseId` | `ID!` | Yes | The unique ID of the course for which to retrieve the current user's award counts. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the award type. | | `label` | `String` | The display name of the award type (e.g., 'CPE Credits', 'Certificate'). | | `icon` | `AwardTypeIcon` | The icon identifier used to visually represent this award type. | | `count` | `Int` | The number of this award type the current user has earned for the course. | #### Example Request ```graphql query GetUserCourseAwardCounts { UserCourseAwardCounts(courseId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890") { id label icon count } } ``` #### Example Response ```json { "data": { "UserCourseAwardCounts": [ { "id": "award-type-001", "label": "CPE Credits", "icon": "star", "count": 4 }, { "id": "award-type-002", "label": "Completion Badge", "icon": "check", "count": 1 } ] }, "id": "abc-123", "label": "example", "icon": "fullStar", "count": 0 } ``` :::note Results are scoped to the currently authenticated user. Pass an `authToken` request header to identify the learner. Without an auth token, results reflect the anonymous user context and will typically return empty counts. ::: --- ### UserCourseCollaborations **`Query`** | Complexity: **1 pt** — Fetch the current user's collaboration count for a course Returns the total number of discussions and comment threads the currently authenticated user has contributed to within a specific course. This integer count is useful for gamification displays, learner dashboards, or analytics surfaces that track community engagement. The user context is set via the `authToken` request header — without it, the count reflects the anonymous user and will typically be zero. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `courseId` | `ID!` | Yes | The unique ID of the course for which to retrieve the current user's collaboration count. | #### Returns | Field | Type | Description | |-------|------|-------------| | `UserCourseCollaborations` | `Int!` | The total number of comments and discussion threads the current user has made in the specified course. | #### Example Request ```graphql query GetUserCourseCollaborations { UserCourseCollaborations(courseId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890") } ``` #### Example Response ```json { "data": { "UserCourseCollaborations": 7 } } ``` :::note Results are scoped to the currently authenticated user. Pass an `authToken` request header to identify the learner. The count includes all comments and threaded replies made by the user within the course's discussion areas. ::: --- ### UserEarnedBadgeBoard **`Query`** | Complexity: **1 pt** — Fetch user earned and unearned badge board Returns a board showing all badges that the current user has earned as well as all badges they have not yet earned. This is useful for displaying a learner's badge progress and gamification status on a profile or dashboard. The user context is determined by the authenticated session or by an `authToken` header passed with the request. #### Returns | Field | Type | Description | |-------|------|-------------| | `earnedBadges` | `[EarnedBadge]` | A list of badges the current user has already earned, each including details about the badge definition and the associated user. | | `unearnedBadges` | `[Badge]` | A list of badges available on the instance that the current user has not yet earned, including award type and threshold information. | #### Example Request ```graphql query UserEarnedBadgeBoard { UserEarnedBadgeBoard { earnedBadges { id deleted user { id email firstName lastName name } badge { id label asset awardThreshold awardType { id label } } } unearnedBadges { id label asset awardThreshold awardType { id label pluralLabel includeOnDashboard icon } } } } ``` #### Example Response ```json { "data": { "UserEarnedBadgeBoard": { "earnedBadges": [ { "id": [ "badge-earn-001" ], "deleted": false, "user": { "id": "user-123", "email": "jane.doe@example.com", "firstName": "Jane", "lastName": "Doe", "name": "Jane Doe" }, "badge": { "id": "badge-001", "label": "Quick Learner", "asset": "https://cdn.example.com/badges/quick-learner.png", "awardThreshold": 5, "awardType": { "id": "award-type-001", "label": "Course Completion" } } } ], "unearnedBadges": [ { "id": "badge-002", "label": "Expert", "asset": "https://cdn.example.com/badges/expert.png", "awardThreshold": 20, "awardType": { "id": "award-type-001", "label": "Course Completion", "pluralLabel": "Course Completions", "includeOnDashboard": true, "icon": "star" } } ] } }, "earnedBadges": null, "unearnedBadges": null } ``` :::note The user context is resolved from the active session. Pass an `authToken` header to scope the results to a specific user. ::: --- ### UserRecentContent **`Query`** | Complexity: **4 pts** — Fetch recently viewed content for a user Returns a list of content items that the current user has most recently accessed, ordered by recency. A maximum of 25 items is returned regardless of the limit argument. Use this to power "Continue learning" or "Recently viewed" UI sections. The user context can be changed by providing an `authToken` header. This query carries an additional API cost of 4 points. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `limit` | `Int` | No | The maximum number of recently viewed content items to return. Regardless of this value, at most 25 items will be returned. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier of the content item. | | `title` | `String` | Display title of the content item. | | `slug` | `Slug!` | URL-friendly slug for the content item. | | `kind` | `ContentKind` | The type of content (e.g., course, learningPath, webinar). | | `asset` | `String` | URL of the content item's thumbnail or cover image. | | `availabilityStatus` | `String` | A human-readable string describing the content's current availability. | | `isActive` | `Boolean!` | Whether the content item is currently active and accessible. | | `url` | `AbsoluteOrRelativeURL` | The URL to navigate to this content item. | | `currentUserDueDate` | `Date` | The due date for the current user on this content item, if set. | | `tags` | `[Tag]` | Tags categorizing this content item. | #### Example Request ```graphql query UserRecentContent($limit: Int) { UserRecentContent(limit: $limit) { id title slug kind asset assetAltText availabilityStatus isActive url currentUserDueDate tags { id label } } } # Variables: # { "limit": 5 } ``` #### Example Response ```json { "data": { "UserRecentContent": [ { "id": "cg_101", "title": "Introduction to Python", "slug": "introduction-to-python", "kind": "course", "asset": "https://cdn.example.com/images/python-intro.jpg", "assetAltText": "Python programming course thumbnail", "availabilityStatus": "available", "isActive": true, "url": "/learn/introduction-to-python", "currentUserDueDate": null, "tags": [ { "id": "tag_50", "label": "Programming" } ] }, { "id": "lp_202", "title": "Cloud Engineering Path", "slug": "cloud-engineering-path", "kind": "learningPath", "asset": "https://cdn.example.com/images/cloud-engineering.jpg", "assetAltText": "Cloud engineering learning path thumbnail", "availabilityStatus": "available", "isActive": true, "url": "/learn/cloud-engineering-path", "currentUserDueDate": "2024-09-30T00:00:00Z", "tags": [ { "id": "tag_51", "label": "Cloud" } ] } ] }, "id": "abc-123", "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "expiresAt": "2026-01-01T00:00:00Z", "courseGroup": "example", "title": "example", "sessionTitle": "example", "ribbon": null, "slug": "example", "kind": "courseGroup", "metaTitle": "example", "language": "example", "metaDescription": "example", "displayCourse": "abc-123", "displayCourseSlug": "example", "displayDate": "2026-01-01T00:00:00Z", "availabilityStatus": "example", "asset": "example", "assetAltText": "example", "freeWithRegistration": false, "priceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "rating": 0, "description": "example", "sku": "example", "hasChildren": false, "tags": null, "status": "draft", "customFields": {}, "authors": [ "example" ], "authorsAndInstructors": [ "example" ], "contentTypeLabel": "example", "contentTypeAssetAspectRatio": "example", "waitlistCount": 0, "seatsLimit": 0, "enrollmentCount": 0, "source": "example", "publishDate": "2026-01-01T00:00:00Z", "courseStartDate": "2026-01-01T00:00:00Z", "courseEndDate": "2026-01-01T00:00:00Z", "enrollmentStartDate": "2026-01-01T00:00:00Z", "enrollmentEndDate": "2026-01-01T00:00:00Z", "timeZone": "example", "meetingStartDate": "2026-01-01T00:00:00Z", "coursePresold": false, "courseGracePeriodEnded": false, "currentUserMayReschedule": false, "isActive": false, "embeddedEnabled": false, "altDescriptionBody": "example", "hideCourseDescription": false, "annotationsEnabled": false, "currentUserUnmetCoursePrerequisites": [ "abc-123" ], "currentUserUnmetLearningPathPrerequisites": [ "abc-123" ], "currentUserDueDate": "2026-01-01T00:00:00Z", "url": "example", "waitlistingEnabled": false, "waitlistingTriggered": false, "canAddToQueue": false, "bulkPurchasingEnabled": false, "prices": null, "location": null, "state": "example", "acceptBadgeUrl": "example", "issuedAt": "2026-01-01T00:00:00Z", "badgeUrl": "example", "credlyBadgeExpiresAt": "2026-01-01T00:00:00Z", "imageUrl": "example", "badgeName": "example", "completedPassedDueDate": false } ``` :::note Regardless of the `limit` argument, this query returns a maximum of 25 items. This query has an additional cost of 4 API points. ::: --- ### UserWaitlist **`Query`** | Complexity: **1 pt** — Fetch content on the current user's waitlist Returns a list of Content items that the currently authenticated user is on the waitlist for. This is useful for building a learner dashboard that surfaces waitlisted courses or events so users can track their position. The user context is determined by the `authToken` header — if no authenticated user is present the result will be empty or null depending on server behavior. #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier of the content item. | | `title` | `String` | Display title of the content item. | | `slug` | `Slug!` | URL-friendly identifier for the content item. | | `kind` | `ContentKind` | The type of content (e.g. course, webinar, learningPath). | | `waitlistCount` | `Int` | Number of users currently on the waitlist for this content. | | `seatsLimit` | `Int` | Maximum number of seats available for this content. | | `waitlistingEnabled` | `Boolean!` | Whether waitlisting is enabled for this content. | | `waitlistingTriggered` | `Boolean!` | Whether the waitlist has been activated (i.e., seats are full). | | `status` | `Status` | Publication status of the content (e.g. published, draft). | | `url` | `AbsoluteOrRelativeURL` | URL to the content detail page. | | `courseStartDate` | `Date` | Date on which the course begins. | | `enrollmentStartDate` | `Date` | Date on which enrollment opens. | | `ribbon` | `Ribbon` | Optional ribbon label and color displayed on the content card. | | `prices` | `[ItemPrice!]` | Pricing information for the content across currencies. | | `location` | `Location` | Physical location details for in-person events. | #### Example Request ```graphql query UserWaitlist { UserWaitlist { id title slug kind waitlistCount seatsLimit waitlistingEnabled waitlistingTriggered courseStartDate enrollmentStartDate status url } } ``` #### Example Response ```json { "data": { "UserWaitlist": [ { "id": "crs_abc123", "title": "Advanced JavaScript Patterns", "slug": "advanced-javascript-patterns", "kind": "course", "waitlistCount": 14, "seatsLimit": 30, "waitlistingEnabled": true, "waitlistingTriggered": true, "courseStartDate": "2024-09-01T00:00:00.000Z", "enrollmentStartDate": "2024-08-01T00:00:00.000Z", "status": "published", "url": "/courses/advanced-javascript-patterns" } ] }, "id": "abc-123", "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "expiresAt": "2026-01-01T00:00:00Z", "courseGroup": "example", "title": "example", "sessionTitle": "example", "ribbon": null, "slug": "example", "kind": "courseGroup", "metaTitle": "example", "language": "example", "metaDescription": "example", "displayCourse": "abc-123", "displayCourseSlug": "example", "displayDate": "2026-01-01T00:00:00Z", "availabilityStatus": "example", "asset": "example", "assetAltText": "example", "freeWithRegistration": false, "priceInCents": 0, "suggestedRetailPriceInCents": 0, "alternativePricingType": "membership", "alternativePricingRef": 0, "rating": 0, "description": "example", "sku": "example", "hasChildren": false, "tags": null, "status": "draft", "customFields": {}, "authors": [ "example" ], "authorsAndInstructors": [ "example" ], "contentTypeLabel": "example", "contentTypeAssetAspectRatio": "example", "waitlistCount": 0, "seatsLimit": 0, "enrollmentCount": 0, "source": "example", "publishDate": "2026-01-01T00:00:00Z", "courseStartDate": "2026-01-01T00:00:00Z", "courseEndDate": "2026-01-01T00:00:00Z", "enrollmentStartDate": "2026-01-01T00:00:00Z", "enrollmentEndDate": "2026-01-01T00:00:00Z", "timeZone": "example", "meetingStartDate": "2026-01-01T00:00:00Z", "coursePresold": false, "courseGracePeriodEnded": false, "currentUserMayReschedule": false, "isActive": false, "embeddedEnabled": false, "altDescriptionBody": "example", "hideCourseDescription": false, "annotationsEnabled": false, "currentUserUnmetCoursePrerequisites": [ "abc-123" ], "currentUserUnmetLearningPathPrerequisites": [ "abc-123" ], "currentUserDueDate": "2026-01-01T00:00:00Z", "url": "example", "waitlistingEnabled": false, "waitlistingTriggered": false, "canAddToQueue": false, "bulkPurchasingEnabled": false, "prices": null, "location": null, "state": "example", "acceptBadgeUrl": "example", "issuedAt": "2026-01-01T00:00:00Z", "badgeUrl": "example", "credlyBadgeExpiresAt": "2026-01-01T00:00:00Z", "imageUrl": "example", "badgeName": "example", "completedPassedDueDate": false } ``` :::note The user is identified via the `authToken` request header. Without a valid auth token this query returns an empty list. ::: --- ### CreateCertificateFromUpload **`Mutation`** | Complexity: **1 pt** — Create certificate from uploaded image Creates a new certificate record for a user by supplying the URL of a previously uploaded image asset along with optional field mappings. This is useful when you want to issue a certificate based on a custom image upload rather than a template rendered by the platform. The operation returns a fully populated `Certificate` object, including the associated user, certificate template details, resource information, expiration state, and a direct URL to the certificate. The acting user can be controlled by passing an `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `asset` | `URL!` | Yes | The publicly accessible URL of the uploaded image to use as the certificate asset. | | `certificateUploadFields` | `[CertificateUploadField!]` | No | An optional list of field mappings that define additional metadata or label values to associate with the uploaded certificate image. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | Unique identifier for the newly created certificate. | | `certificateTemplate` | `CertificateTemplate` | The certificate template associated with this certificate, including layout, expiration, and recertification settings. | | `user` | `User!` | The user to whom the certificate was issued. | | `issuedAt` | `Date!` | The date on which the certificate was issued. | | `url` | `AbsoluteOrRelativeURL!` | The URL where the certificate can be viewed. | | `pdfAsset` | `AbsoluteOrRelativeURL` | URL to the PDF version of the certificate, if available. | | `resource` | `CertificateResource` | The course or learning path resource associated with this certificate. | | `resourceType` | `CertificateResourceType` | Whether the certificate is associated with a `course` or `learningPath`. | | `resourceTypeLabel` | `String` | Human-readable label for the resource type. | | `resourceId` | `ID` | The ID of the associated course or learning path resource. | | `progressWasReset` | `Boolean!` | Indicates whether the user's progress was reset as part of recertification. | | `expirationDate` | `Date` | The date on which this certificate expires, if applicable. | | `didExpire` | `Boolean!` | Indicates whether the certificate has already expired. | | `deleted` | `Boolean!` | Indicates whether the certificate has been deleted. | | `recertificationDate` | `Date` | The date by which the user must recertify, if applicable. | | `isExpired` | `Boolean!` | Indicates whether the certificate is currently in an expired state. | | `source` | `String` | The source or origin of the certificate (e.g., 'upload'). | | `contentItem` | `Content` | The content catalog item associated with this certificate. | | `isExternal` | `Boolean!` | Indicates whether the certificate was issued for an external resource. | | `externalResourceTitle` | `String` | Title of the external resource, when `isExternal` is true. | | `identifier` | `String` | A human-readable identifier or reference code for the certificate. | #### Example Request ```graphql mutation CreateCertificateFromUpload( $asset: URL! $certificateUploadFields: [CertificateUploadField!] ) { CreateCertificateFromUpload( asset: $asset certificateUploadFields: $certificateUploadFields ) { id issuedAt url pdfAsset isExpired expirationDate didExpire deleted progressWasReset resourceType resourceTypeLabel resourceId recertificationDate source isExternal externalResourceTitle identifier user { id email firstName lastName name } certificateTemplate { id title resourceType asset expirationDays isExpired } resource { ... on Course { id title slug } ... on LearningPath { id name slug } } contentItem { id title slug kind } } } ``` #### Example Response ```json { "data": { "CreateCertificateFromUpload": { "id": "cert_abc123", "issuedAt": "2024-06-15", "url": "/certificates/cert_abc123", "pdfAsset": "/certificates/cert_abc123.pdf", "isExpired": false, "expirationDate": "2025-06-15", "didExpire": false, "deleted": false, "progressWasReset": false, "resourceType": "course", "resourceTypeLabel": "Course", "resourceId": "course_789", "recertificationDate": null, "source": "upload", "isExternal": false, "externalResourceTitle": null, "identifier": "CERT-2024-00123", "user": { "id": "user_456", "email": "jane.doe@example.com", "firstName": "Jane", "lastName": "Doe", "name": "Jane Doe" }, "certificateTemplate": { "id": "tmpl_001", "title": "Course Completion Certificate", "resourceType": "course", "asset": "https://assets.example.com/cert-template.png", "expirationDays": 365, "isExpired": false }, "resource": { "id": "course_789", "title": "Introduction to GraphQL", "slug": "intro-to-graphql" }, "contentItem": { "id": "content_321", "title": "Introduction to GraphQL", "slug": "intro-to-graphql", "kind": "course" } } }, "id": "abc-123", "certificateTemplate": null, "user": null, "issuedAt": "2026-01-01T00:00:00Z", "url": "example", "pdfAsset": "example", "resource": null, "resourceType": "course", "resourceTypeLabel": "example", "resourceId": "abc-123", "progressWasReset": false, "expirationDate": "2026-01-01T00:00:00Z", "didExpire": false, "deleted": false, "recertificationDate": "2026-01-01T00:00:00Z", "isExpired": false, "source": "example", "contentItem": null, "isExternal": false, "externalResourceTitle": "example", "identifier": "example" } ``` :::note The acting user for this mutation can be overridden by passing an `authToken` header. Without it, the certificate is issued to the currently authenticated user. ::: --- ### CreateLearner **`Mutation`** | Complexity: **1 pt** — Create a learner Creates a new learner account within the learning instance. You must provide the learner's email address, first name, and last name. Optionally, you can associate the new user with a specific client by passing `clientId`, and pre-assign them to one or more licenses by providing a list of `licenseIds`. The mutation returns a full `User` object representing the newly created learner. The acting user context (used for permission checks) is resolved from the `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `email` | `String!` | Yes | The email address for the new learner account. Must be unique within the learning instance. | | `firstName` | `String!` | Yes | The learner's first name. | | `lastName` | `String!` | Yes | The learner's last name. | | `clientId` | `ID` | No | The ID of the client (tenant) to associate the new learner with. If omitted, the learner is created under the default client. | | `licenseIds` | `[ID!]` | No | An optional list of license IDs to assign to the learner upon creation. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier of the newly created user. | | `email` | `String` | The learner's email address. | | `firstName` | `String` | The learner's first name. | | `lastName` | `String` | The learner's last name. | | `name` | `String` | The learner's full display name. | | `abbreviatedName` | `String` | A shortened version of the learner's name. | | `asset` | `String` | URL of the learner's profile image. | | `bio` | `String` | The learner's biography or profile description. | | `createdAt` | `Date` | Timestamp when the learner account was created. | | `updatedAt` | `Date` | Timestamp when the learner account was last updated. | | `disabled` | `Boolean!` | Whether the learner's account is currently disabled. | | `mustVerifyEmail` | `Boolean!` | Whether the learner must verify their email before accessing content. | | `client` | `Client` | The client (tenant) the learner is associated with. | | `clientId` | `ID` | The ID of the client the learner belongs to. | | `activeLicense` | `License` | The learner's currently active license. | | `allocatedLicenses` | `[AllocatedLicense!]` | All licenses allocated to this learner, including their role and status. | | `purchasedCourses` | `[PurchasedCourse!]` | Courses the learner has purchased or been enrolled in. | | `purchasedBundles` | `[PurchasedBundle!]` | Content bundles the learner has purchased. | | `allocatedLearningPaths` | `[AllocatedLearningPath!]` | Learning paths allocated to this learner. | | `certificatesCount` | `Int!` | Total number of certificates earned by the learner. | | `completedCoursesCount` | `Int!` | Number of courses the learner has completed. | | `startedCoursesCount` | `Int!` | Number of courses the learner has started. | | `availableCoursesCount` | `Int!` | Number of courses currently available to the learner. | | `collaborationsCount` | `Int!` | Number of collaborations the learner is part of. | | `roleKey` | `String` | The learner's role key within the platform. | | `customFields` | `JSON` | Any custom metadata fields associated with the learner. | | `lang` | `String` | The learner's preferred language code. | | `preferredCurrency` | `String` | The learner's preferred currency for purchases. | #### Example Request ```graphql mutation CreateLearner { CreateLearner( email: "alex.johnson@example.com", firstName: "Alex", lastName: "Johnson", clientId: "client_001", licenseIds: ["license_101", "license_202"] ) { id email firstName lastName name createdAt disabled mustVerifyEmail certificatesCount completedCoursesCount availableCoursesCount client { id name } activeLicense { id name } } } ``` #### Example Response ```json { "data": { "CreateLearner": { "id": "user_new789", "email": "alex.johnson@example.com", "firstName": "Alex", "lastName": "Johnson", "name": "Alex Johnson", "createdAt": "2024-03-22T09:00:00Z", "disabled": false, "mustVerifyEmail": false, "certificatesCount": 0, "completedCoursesCount": 0, "availableCoursesCount": 5, "client": { "id": "client_001", "name": "Acme Corp" }, "activeLicense": { "id": "license_101", "name": "Standard Learner License" } } }, "id": "abc-123", "email": "example", "firstName": "example", "abbreviatedName": "example", "asset": "example", "lastName": "example", "name": "example", "lastActiveAt": "2026-01-01T00:00:00Z", "bio": "example", "twoFactorEnabled": false, "firstInitial": "example", "lastInitial": "example", "invitedByName": "example", "shouldHighlight": false, "purchasedCourses": null, "purchasedBundles": null, "activeLicense": null, "allocatedLicenses": null, "allocatedLearningPaths": null, "attendedMeetings": null, "waitlistedCourses": null, "adminClients": null, "roleKey": "example", "address1": "example", "address2": "example", "city": "example", "state": "example", "zipCode": "example", "country": "example", "telephone": "example", "stripeCustomerId": "abc-123", "externalCustomerId": "abc-123", "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "sfAccountId": "abc-123", "sfContactId": "abc-123", "shippingName": "example", "client": null, "clientId": "abc-123", "ref1": "example", "ref2": "example", "ref3": "example", "ref4": "example", "ref5": "example", "ref6": "example", "ref7": "example", "ref8": "example", "ref9": "example", "ref10": "example", "lang": "example", "preferredCurrency": "example", "customFields": {}, "recommendedTags": null, "recommendedSlugs": null, "accessedFlows": [ "example" ], "disabled": false, "learnerUserId": "abc-123", "managerUserId": "abc-123", "balance": 0, "mustVerifyEmail": false, "certificatesCount": 0, "collaborationsCount": 0, "availableCoursesCount": 0, "startedCoursesCount": 0, "completedCoursesCount": 0 } ``` :::note If a user with the supplied email already exists, this mutation will return an error. Ensure `licenseIds` reference licenses belonging to the specified `clientId` to avoid allocation errors. ::: --- ### CreateLearners **`Mutation`** | Complexity: **1 pt** — Create multiple learners Creates one or more learner accounts within the learning instance in a single operation. You pass an array of `LearnerInput` objects, and the API returns the newly created `User` records with their full profile data. This is the right mutation to use when bulk-provisioning users from an external system, such as an HR platform or an SSO directory sync. The behavior of the mutation can be scoped to a specific user by passing an `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `learners` | `[LearnerInput!]!` | Yes | An array of learner input objects containing the profile data for each user to be created. | #### Returns | Field | Type | Description | |-------|------|-------------| | `id` | `ID!` | The unique identifier for the newly created user. | | `email` | `String` | The user's email address. | | `firstName` | `String` | The user's first name. | | `lastName` | `String` | The user's last name. | | `name` | `String` | The user's full display name. | | `abbreviatedName` | `String` | An abbreviated form of the user's name. | | `asset` | `String` | URL of the user's profile avatar image. | | `lastActiveAt` | `Date` | Timestamp of the user's most recent activity. | | `bio` | `String` | The user's biographical text. | | `twoFactorEnabled` | `Boolean` | Whether two-factor authentication is enabled for the user. | | `firstInitial` | `String` | The first initial of the user's first name. | | `lastInitial` | `String` | The first initial of the user's last name. | | `invitedByName` | `String` | Name of the user who invited this learner. | | `shouldHighlight` | `Boolean!` | Whether the user should be visually highlighted in listings. | | `purchasedCourses` | `[PurchasedCourse!]` | List of courses the user has purchased, including enrollment status and certificate info. | | `purchasedBundles` | `[PurchasedBundle!]` | List of bundles the user has purchased. | | `activeLicense` | `License` | The license currently active for this user. | | `allocatedLicenses` | `[AllocatedLicense!]` | All licenses allocated to this user and their roles. | | `allocatedLearningPaths` | `[AllocatedLearningPath!]` | Learning paths that have been allocated to this user. | | `attendedMeetings` | `[UserAttendedMeeting!]` | Meetings the user has attended, with dates and meeting details. | | `waitlistedCourses` | `[WaitlistedCourse!]` | Courses the user is currently waitlisted for. | | `adminClients` | `[Client]` | Client organizations this user administers. | | `roleKey` | `String` | The key identifying the user's role within the system. | | `address1` | `String` | Primary street address line. | | `address2` | `String` | Secondary street address line. | | `city` | `String` | City of the user's address. | | `state` | `String` | State or province of the user's address. | | `zipCode` | `String` | Postal/ZIP code of the user's address. | | `country` | `String` | Country of the user's address. | | `telephone` | `String` | The user's phone number. | | `stripeCustomerId` | `ID` | The Stripe customer ID associated with this user. | | `externalCustomerId` | `ID` | An external system customer ID linked to this user. | | `createdAt` | `Date` | Timestamp when the user account was created. | | `updatedAt` | `Date` | Timestamp when the user account was last updated. | | `sfAccountId` | `ID` | Salesforce Account ID linked to this user. | | `sfContactId` | `ID` | Salesforce Contact ID linked to this user. | | `shippingName` | `String` | Name to use for physical shipping. | | `client` | `Client` | The client organization this user belongs to. | | `clientId` | `ID` | The ID of the client organization this user belongs to. | | `ref1` | `String` | Custom reference field 1 for external system mapping. | | `ref2` | `String` | Custom reference field 2 for external system mapping. | | `ref3` | `String` | Custom reference field 3 for external system mapping. | | `ref4` | `String` | Custom reference field 4 for external system mapping. | | `ref5` | `String` | Custom reference field 5 for external system mapping. | | `ref6` | `String` | Custom reference field 6 for external system mapping. | | `ref7` | `String` | Custom reference field 7 for external system mapping. | | `ref8` | `String` | Custom reference field 8 for external system mapping. | | `ref9` | `String` | Custom reference field 9 for external system mapping. | | `ref10` | `String` | Custom reference field 10 for external system mapping. | | `lang` | `String` | The user's preferred interface language code. | | `preferredCurrency` | `String` | The user's preferred currency code for pricing. | | `customFields` | `JSON` | Arbitrary custom field data associated with the user. | | `recommendedTags` | `[UserRecommendedTag!]` | Content tags recommended for this user with boost weights. | | `recommendedSlugs` | `[UserRecommendedSlug!]` | Content slugs recommended for this user with boost weights. | | `accessedFlows` | `[String!]` | List of onboarding or experience flows the user has accessed. | | `disabled` | `Boolean!` | Whether the user account is currently disabled. | | `learnerUserId` | `ID` | ID of the associated learner-role user record. | | `managerUserId` | `ID` | ID of the associated manager-role user record. | | `balance` | `Float` | The user's current credit balance. | | `mustVerifyEmail` | `Boolean!` | Whether the user must verify their email before proceeding. | | `certificatesCount` | `Int!` | Total number of certificates the user has earned. | | `collaborationsCount` | `Int!` | Total number of collaborations the user is part of. | | `availableCoursesCount` | `Int!` | Number of courses available to the user. | | `startedCoursesCount` | `Int!` | Number of courses the user has started. | | `completedCoursesCount` | `Int!` | Number of courses the user has completed. | #### Example Request ```graphql mutation CreateLearners($learners: [LearnerInput!]!) { CreateLearners(learners: $learners) { id email firstName lastName name createdAt disabled clientId ref1 ref2 } } # Variables: # { # "learners": [ # { # "email": "jane.doe@example.com", # "firstName": "Jane", # "lastName": "Doe", # "ref1": "dept-engineering" # }, # { # "email": "john.smith@example.com", # "firstName": "John", # "lastName": "Smith" # } # ] # } ``` #### Example Response ```json { "data": { "CreateLearners": [ { "id": "usr_01HX1A2B3C4D5E6F", "email": "jane.doe@example.com", "firstName": "Jane", "lastName": "Doe", "name": "Jane Doe", "createdAt": "2024-05-01T10:00:00Z", "disabled": false, "clientId": "clt_9876543210", "ref1": "dept-engineering", "ref2": null }, { "id": "usr_01HX1A2B3C4D5E7G", "email": "john.smith@example.com", "firstName": "John", "lastName": "Smith", "name": "John Smith", "createdAt": "2024-05-01T10:00:01Z", "disabled": false, "clientId": "clt_9876543210", "ref1": null, "ref2": null } ] }, "id": "abc-123", "email": "example", "firstName": "example", "abbreviatedName": "example", "asset": "example", "lastName": "example", "name": "example", "lastActiveAt": "2026-01-01T00:00:00Z", "bio": "example", "twoFactorEnabled": false, "firstInitial": "example", "lastInitial": "example", "invitedByName": "example", "shouldHighlight": false, "purchasedCourses": null, "purchasedBundles": null, "activeLicense": null, "allocatedLicenses": null, "allocatedLearningPaths": null, "attendedMeetings": null, "waitlistedCourses": null, "adminClients": null, "roleKey": "example", "address1": "example", "address2": "example", "city": "example", "state": "example", "zipCode": "example", "country": "example", "telephone": "example", "stripeCustomerId": "abc-123", "externalCustomerId": "abc-123", "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z", "sfAccountId": "abc-123", "sfContactId": "abc-123", "shippingName": "example", "client": null, "clientId": "abc-123", "ref1": "example", "ref2": "example", "ref3": "example", "ref4": "example", "ref5": "example", "ref6": "example", "ref7": "example", "ref8": "example", "ref9": "example", "ref10": "example", "lang": "example", "preferredCurrency": "example", "customFields": {}, "recommendedTags": null, "recommendedSlugs": null, "accessedFlows": [ "example" ], "disabled": false, "learnerUserId": "abc-123", "managerUserId": "abc-123", "balance": 0, "mustVerifyEmail": false, "certificatesCount": 0, "collaborationsCount": 0, "availableCoursesCount": 0, "startedCoursesCount": 0, "completedCoursesCount": 0 } ``` :::note This mutation creates users in bulk. If an `authToken` header is provided, the operation is performed in the context of that user. Duplicate email addresses may cause errors depending on platform configuration. ::: --- ### Login **`Mutation`** | Complexity: **1 pt** — Log in a user Authenticates a user with their email address and password. The mutation validates credentials, checks email verification status, and performs other account state checks. On success, it returns an authentication token (authToken) string that can be passed as the `authToken` header in subsequent API requests to authenticate as that user. On failure, an appropriate error is returned describing the reason (e.g. invalid credentials, unverified email). #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `email` | `String!` | Yes | The email of the Current User attempting to log in. | | `password` | `String!` | Yes | The password of the Current User attempting to log in. | #### Returns | Field | Type | Description | |-------|------|-------------| | `Login` | `String!` | The authentication token to use in the `authToken` header for subsequent authenticated requests. | #### Example Request ```graphql mutation Login { Login( email: "learner@example.com" password: "s3cur3P@ssword!" ) } ``` #### Example Response ```json { "data": { "Login": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJ1c2VyXzAwMSIsImlhdCI6MTcxMDAwMDAwMH0.placeholder_signature" } } ``` :::note The returned token should be supplied as the value of the `authToken` header — not as a URL parameter — in all subsequent API calls that require authentication. ::: --- ### Logout **`Mutation`** | Complexity: **1 pt** — Log out the current user Logs out the currently authenticated user and immediately invalidates their authentication token. After this mutation succeeds, any further requests using the same token will be rejected. Use this mutation when ending a user session or rotating credentials. The user to log out is determined by the `authToken` header supplied with the request. #### Returns | Field | Type | Description | |-------|------|-------------| | `Logout` | `Boolean!` | Returns `true` if the logout was successful and the token has been invalidated. | #### Example Request ```graphql mutation Logout { Logout } ``` #### Example Response ```json { "data": { "Logout": true } } ``` :::note The `authToken` header must be present and valid for this mutation to succeed. Once invalidated, the token cannot be reused. ::: --- ### RedeemBalance **`Mutation`** | Complexity: **1 pt** — Redeem user balance for content This mutation allows a user to spend their credit balance to access a piece of content. It adds the content to the user's purchased courses, deducts the appropriate amount from their balance, and records a credit redemption action event. Currently, only the `courses` content type is supported via the `BalanceContentType` enum. The return value is a URL — for articles and videos it is the direct content URL, while for all other content types (including courses) it is the learner dashboard URL. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `contentType` | `BalanceContentType!` | Yes | Currently only available for `courses` | | `contentId` | `UUID!` | Yes | The UUID of the content item to redeem the user's balance against. | #### Returns | Field | Type | Description | |-------|------|-------------| | `RedeemBalance` | `AbsoluteOrRelativeURL` | A URL to redirect the user to after redemption. For articles and videos this is the content URL; for all other content types this is the learner dashboard URL. | #### Example Request ```graphql mutation RedeemBalance { RedeemBalance( contentType: courses contentId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" ) } ``` #### Example Response ```json { "data": { "RedeemBalance": "/dashboard" } } ``` :::note Currently only `courses` is a valid value for `contentType`. The mutation operates in the context of the authenticated user; ensure the correct user token is provided. ::: --- ### RedeemRegistrationAndRedemptionCodes **`Mutation`** | Complexity: **1 pt** — Redeem registration and redemption codes Redeems one or more pre-validated registration or redemption codes on behalf of a user. Pass an array of code strings that have already been validated. The outcome of this mutation is user-scoped — provide an `authToken` header to associate the redemption with a specific user. Returns a boolean indicating whether the redemption was successful. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `validatedRedemptionCodes` | `[String!]!` | Yes | A non-empty list of redemption or registration code strings to redeem. These codes should have been pre-validated using `ValidateRedemptionCode` or `ValidateRegistrationCode` before being passed here. | #### Returns | Field | Type | Description | |-------|------|-------------| | `redeemed` | `Boolean!` | Indicates whether all provided codes were successfully redeemed. | #### Example Request ```graphql mutation RedeemRegistrationAndRedemptionCodes { RedeemRegistrationAndRedemptionCodes( validatedRedemptionCodes: ["WELCOME2024", "COURSE-ABC-XYZ"] ) { redeemed } } ``` #### Example Response ```json { "data": { "RedeemRegistrationAndRedemptionCodes": { "redeemed": true } }, "redeemed": false } ``` :::note Codes should be validated before calling this mutation. Pass an `authToken` header to bind the redemption to a specific user; otherwise the currently authenticated session user is used. ::: --- ### SelectCurrentUserActiveLicense **`Mutation`** | Complexity: **1 pt** — Select active license for current user Changes which license is currently active for the authenticated user. When a user belongs to multiple licenses, this mutation lets you switch their active context to a specific license by ID. The active license affects what content and branding the user sees. Because this mutation operates on the "current user," it respects the `authToken` header — supplying that header lets you perform the switch on behalf of a specific user. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `licenseId` | `ID!` | Yes | The ID of the License that should be set to 'active' for the CurrentUser. | #### Returns | Field | Type | Description | |-------|------|-------------| | `SelectCurrentUserActiveLicense` | `Boolean!` | Returns true if the active license was successfully updated for the current user. | #### Example Request ```graphql mutation SelectCurrentUserActiveLicense { SelectCurrentUserActiveLicense(licenseId: "lic_abc123") } ``` #### Example Response ```json { "data": { "SelectCurrentUserActiveLicense": true } } ``` --- ### UnenrollFromWaitlist **`Mutation`** | Complexity: **1 pt** — Unenroll current user from a waitlist Removes the currently authenticated user from the waitlist for a specific content item. This is useful when a learner no longer wants to be notified or enrolled when a seat becomes available. The operation targets the content item identified by the provided ID, and the acting user can be overridden by supplying an `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `id` | `ID!` | Yes | The ID of the content item (e.g., a course) from whose waitlist the current user should be removed. | #### Returns | Field | Type | Description | |-------|------|-------------| | `Boolean` | `Boolean!` | Returns true if the user was successfully removed from the waitlist, false otherwise. | #### Example Request ```graphql mutation UnenrollFromWaitlist { UnenrollFromWaitlist(id: "course_abc123") } ``` #### Example Response ```json { "data": { "UnenrollFromWaitlist": true } } ``` --- ### ValidateRedemptionCode **`Mutation`** | Complexity: **1 pt** — Validate a redemption code Checks the status of a single redemption code before attempting to redeem it. The mutation returns three boolean flags: whether the code is currently valid, whether it has already been redeemed, and whether it has expired. Use this mutation as a pre-flight check before calling `RedeemRegistrationAndRedemptionCodes`. The result can be user-scoped by providing an `authToken` header. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `code` | `String!` | Yes | The redemption code string to validate. | #### Returns | Field | Type | Description | |-------|------|-------------| | `valid` | `Boolean!` | Whether the redemption code is currently valid and eligible to be redeemed. | | `alreadyRedeemed` | `Boolean!` | Whether the redemption code has already been used. | | `codeExpired` | `Boolean!` | Whether the redemption code has passed its expiration date. | #### Example Request ```graphql mutation ValidateRedemptionCode { ValidateRedemptionCode(code: "COURSE-ABC-XYZ") { valid alreadyRedeemed codeExpired } } ``` #### Example Response ```json { "data": { "ValidateRedemptionCode": { "valid": true, "alreadyRedeemed": false, "codeExpired": false } }, "valid": false, "alreadyRedeemed": false, "codeExpired": false } ``` :::note This mutation does not consume the code — it only checks its status. A code may be invalid due to either already being redeemed or being expired; check all three flags independently. ::: --- ### ValidateRegistrationCode **`Mutation`** | Complexity: **1 pt** — Validate a registration code Checks whether a registration code is valid and available for use. Unlike the redemption code validator, this mutation does not include an expiry check — it returns only whether the code is valid and whether it has already been redeemed. Use this as a pre-flight check before calling `RedeemRegistrationAndRedemptionCodes`. Provide an `authToken` header to scope the validation to a specific user. #### Arguments | Name | Type | Required | Description | |------|------|----------|-------------| | `code` | `String!` | Yes | The registration code string to validate. | #### Returns | Field | Type | Description | |-------|------|-------------| | `valid` | `Boolean!` | Whether the registration code is currently valid and eligible to be redeemed. | | `alreadyRedeemed` | `Boolean!` | Whether the registration code has already been used. | #### Example Request ```graphql mutation ValidateRegistrationCode { ValidateRegistrationCode(code: "WELCOME2024") { valid alreadyRedeemed } } ``` #### Example Response ```json { "data": { "ValidateRegistrationCode": { "valid": true, "alreadyRedeemed": false } }, "valid": false, "alreadyRedeemed": false } ``` :::note Registration codes do not have expiry dates exposed through this mutation; use `ValidateRedemptionCode` if expiry checking is needed. This mutation does not consume the code. ::: --- --- ## Explorer & Postman Tools for writing, testing, and validating GraphQL queries before you ship them. ## GraphiQL Explorer Helium ships with **GraphiQL**, an in-browser GraphQL explorer and playground. Start your local Helium web server, then visit `http://localhost:3000/graphiql`. GraphiQL is the fastest way to write, test, and validate queries before pasting them into your application code. It auto-completes against the live schema and surfaces validation errors inline. - Schema-aware auto-completion for queries, mutations, fields, and arguments - One-click introspection of every available type - Inline validation and helpful error messages - History of recently run queries ## Postman Collection Prefer Postman? Explore the GraphQL API through our official collection. You'll need to define two collection variables: | Variable | Description | |----------|-------------| | `URL` | Your learning instance endpoint, e.g. `https://your-instance.com/helium` | | `API Key` | Your GraphQL API access token, passed as the `apiKey` query param | [Run in Postman →](https://www.postman.com/red-robot-526965/workspace/thought-industries-apis/collection/43262118-e371791f-6385-4371-bce2-ad793d501938?action=share&creator=43262118) ## Authenticating Requests When calling GraphQL from an outside client like Postman, pass the learner Auth Token (returned by the `Login` mutation) in the request header with the key `Auth Token`. Inside a Helium app, this is handled for you via `pageContext`. --- ## Fetching Content with GraphQL Patterns for retrieving Courses, Videos, Blogs, ILTs, and Learning Paths. Thought Industries offers several content types — Courses, Videos, Blogs, SCORMs, Articles, and more. While they appear differently in the admin, many share the same underlying structure. Understanding that structure is key to fetching the right data. ## Courses Courses are one of the most complex content types. Each course belongs to a `courseGroup`. Courses have sections, sections have lessons, and lessons have topics (quizzes, tests, text pages, etc.). ### The `CatalogContent` Query Use this to get the IDs of courses in your learning instance — typically when building a Catalog Page. ```graphql CatalogContent(page: 1) { contentItems { title displayCourse # pass this ID to CourseById } } ``` ### The `CourseById` Query Use IDs from `CatalogContent` to fetch sections, lessons, and topics for a specific course. ```graphql CourseById(id: "sample-id") { Sections { Lessons { Topics { ...on ArticlePage { title id type } ...on QuizPage { title id type } } } } } ``` ### The `Pages` Query With topic IDs in hand, call `Pages` for richer data — e.g. quiz questions, answers, and timing. ```graphql Pages(identifiers: [String!]!) { ... on QuizPage { title questions { body choices { value correct } } } } ``` ## Videos, Blogs, Articles Under the hood, Videos, Blogs, and Articles are also stored as course objects with a single topic. Follow the same pattern: `CatalogContent` → `CourseById` → `Pages`. All three are exposed through the `ArticlePage` type. ```graphql query Pages($identifiers: [String!]!) { Pages(identifiers: $identifiers) { ... on ArticlePage { videoAsset languages { language label title subtitle body copyright } } } } ``` ## ILTs Instructor Led Training is stored as `courseGroup` objects. Most ILT data lives at the `CourseById` level — no `Pages` query needed. One ILT can have multiple sessions: a parent `courseGroup` with child courses for each session. ```graphql query CourseById($id: ID!) { CourseById(id: $id) { title slug courseGroup { description asset courses { title courseStartDate courseEndDate } } } } ``` ## Learning Paths ### The `CatalogContent` Query Fetch every Learning Path in your instance with basic info like title and description. ```graphql CatalogContent(page: 1) { contentItems { title slug # pass this to LearningPathBySlug } } ``` ### The `LearningPathBySlug` Query Fetch general and milestone-level info for a specific Learning Path. ```graphql query LearningPathBySlug($slug: Slug!) { LearningPathBySlug(slug: $slug) { milestones { name id contentItems { id title kind } } } } ``` ### The `Milestone` Query Fetch info about a single Milestone rather than all of them at once. ```graphql query Milestone($id: ID!) { Milestone(id: $id) { name id contentItems { id title kind } } } ``` --- ## GraphQL API Flexible, efficient data access for Thought Industries. ## Authentication & Endpoint GraphQL queries and mutations are executed by sending `POST` HTTP requests to: ``` https://{learning_instance}.com/helium?apiKey=YOUR_API_KEY ``` ## Rate Limits | Metric | Limit | |--------|-------| | Max query complexity | 1,000 pts | | Points per minute | 100,000 pts | | Requests per minute | 2,500 | ## Calculating Complexity Each requested property costs **1 point** by default. Certain query types have additional performance costs. ### Simple query — 2 points ```graphql query { CurrentUser { firstName lastName } } // 1 (firstName) + 1 (lastName) = a 2 point query ``` ### With performance cost — 6 points ```graphql query { CatalogContent { id title slug } } // 1(id) + 1(title) + 1(slug) + 3(performance cost) = a 6 point query ``` ## Rate Limit Headers | Header | Description | |--------|-------------| | `X-RateLimit-Remaining` | Points remaining in the current window | | `X-RateLimit-Reset` | Timestamp when points reset | | `Retry-After` | Seconds to wait (only after exceeding limit) | ## Auth Tokens A user's Auth Token is generated by calling the `Login` mutation. Inside a Helium app, auth tokens are handled automatically with `pageContext`. ```graphql mutation { Login(email: "user@example.com", password: "****") { token user { id firstName lastName } } } ``` ## Example Queries Try these queries in the GraphiQL explorer at `localhost:3000/graphiql`. ### List Courses ```graphql query GetCourses { CourseList(first: 10) { edges { node { id title slug courseGroup { title } } } } } ``` ## GraphQL Explorer & Postman **GraphiQL Explorer** — Start your local Helium server and visit `localhost:3000/graphiql` to write, test, and validate queries. **Postman Collection** — Explore the full GraphQL API through our [Postman collection](https://www.postman.com/red-robot-526965/workspace/thought-industries-apis/collection/43262118-e371791f-6385-4371-bce2-ad793d501938) with pre-built queries. --- ## Working with the Quiz Object Quizzes have more moving pieces than a typical content page — you fetch data, then accept submissions back from the learner. ## 1. Fetching the Quiz Questions Use the `Pages` query to load the question and choice data so you can render the quiz UI. ```graphql query Pages($identifiers: [String!]!) { Pages(identifiers: $identifiers) { ... on QuizPage { title questions { body choices { value correct } } } } } ``` ## 2. Creating an Assessment Attempt Use `LoadAssessmentAttemptWithQuestions` to create a new attempt for the learner. Given Helium's headless nature, you decide when — many apps create the attempt as soon as the learner lands on the quiz page. ```graphql query { LoadAssessmentAttemptWithQuestions( id: $id courseId: $courseId topicType: $topicType ) { id status grade } } ``` ## 3. Updating the Assessment Attempt Each time the learner answers a question, call `UpdateAssessmentAttempt` with the active question. ```graphql mutation UpdateAssessmentAttempt( $activeQuestion: QuestionInput, $assessmentAttempt: AssessmentAttemptInput ) { UpdateAssessmentAttempt( activeQuestion: $activeQuestion, assessmentAttempt: $assessmentAttempt ) { id grade } } // SAMPLE VARIABLES { "assessmentAttempt": { "id": "{{ id from LoadAssessmentAttemptWithQuestions }}", "status": "started" }, "activeQuestion": { "body": "Who is the current quarterback for the Eagles?", "mustSelectAllCorrectChoices": true, "selectedChoice": { "value": "Jalen Hurts", "correct": true } } } ``` ## 4. Submitting the Assessment Attempt When the learner clicks submit, call `UpdateAssessmentAttempt` one more time with status `finished`. ```graphql mutation UpdateAssessmentAttempt( $assessmentAttempt: AssessmentAttemptInput ) { UpdateAssessmentAttempt( assessmentAttempt: $assessmentAttempt ) { id grade } } // SAMPLE VARIABLES { "assessmentAttempt": { "id": "{{ id from LoadAssessmentAttemptWithQuestions }}", "status": "finished" } } ``` --- ## Rate Limits & Complexity Understand how the GraphQL API meters and throttles requests. ## Rate Limits | Metric | Limit | |--------|-------| | Max query complexity | 1,000 pts | | Points per minute | 100,000 pts | | Requests per minute | 2,500 | ## Calculating Complexity Each requested property costs **1 point** by default. Certain query types add a performance cost on top. ### Simple query — 2 points ```graphql query { CurrentUser { firstName lastName } } // 1 (firstName) + 1 (lastName) = a 2 point query ``` ### With performance cost — 6 points ```graphql query { CatalogContent { id title slug } } // 1(id) + 1(title) + 1(slug) + 3(performance cost) = a 6 point query ``` ## Rate Limit Headers | Header | Description | |--------|-------------| | `X-RateLimit-Remaining` | Points remaining in the current window | | `X-RateLimit-Reset` | Timestamp when points reset | | `Retry-After` | Seconds to wait (only after exceeding limit) | --- ## Best Practices ## Packages and Dependencies Because Cloudflare Workers don't run in traditional containerized environments with full Node.js runtimes, certain Node-specific modules—such as `fs` or `crypto`—may not function as expected. In some cases, their usage can even prevent your project from deploying successfully. However, many packages that rely on bundlers like Webpack (which provide browser-compatible polyfills) can still run without issues in the Worker environment. ## Deployment Tips When setting up your instance for the first time, add both your sandbox and production instances when running `npm run authenticate`. Deploy to sandbox first to preview changes, then deploy to production when confirmed. You should deploy early and often to your sandbox. Building a headless LMS and integrating with 3rd parties can introduce complexities if not done carefully. :::tip[Adding instances later] If you didn't add both instances when you ran `npm run authenticate`, you can add them directly to your `ti-config.json` file. ::: ## 3rd Party Libraries Because Helium is fully open source and headless, you can easily incorporate third-party libraries. However, keep in mind that Helium runs on Cloudflare Workers, which imposes certain compatibility constraints. The simplest way to verify compatibility is to install the library and attempt a deployment to your Helium sandbox. If the deployment succeeds, the library is likely safe to use. ## Writing GraphQL Queries GraphQL enables a "get exactly what you need" approach to data fetching. Each component should ideally query only the data it needs. While it's technically possible to write a large query in a root component and pass data down through props, this pattern can lead to bloated components, reduced flexibility, and slower performance. ## Querying CurrentUser Due to [Cloudflare Worker Request Header Size](https://developers.cloudflare.com/workers/platform/limits/#request-limits) limitations, we recommend querying the `CurrentUser` object directly rather than accessing it through `PageProps`. `CurrentUser` includes several large array fields—such as `allocatedLicenses`, `purchasedCourses`, `purchasedBundles`, and others. When passed through headers, these fields may be truncated. By querying `CurrentUser` directly for the properties you need, you ensure complete data. --- ## Helpful Commands ## init Creates a Helium app. | npm | yarn | |-----|------| | `npm init helium-app` | `yarn create helium-app` | ## authenticate Connects your Helium app to your learning instance and generates a ti-config.json file. | npm | yarn | |-----|------| | `npm run authenticate` | `yarn authenticate` | ## dev Runs a Helium app in a local environment. | npm | yarn | |-----|------| | `npm run dev` | `yarn dev` | ## update-translations Syncs the translations.json file with translations from the connected learning instance. | npm | yarn | |-----|------| | `npm run update-translations` | `yarn update-translations` | --- ## Cart The `Cart` component takes a checkout URL to handle the checkout redirection, and optional properties to customize price formatting. It provides access to both UI behavior (like toggling the cart modal) and callback functions to manage a local cart object. [✨ View Component in Storybook](https://thoughtindustries.github.io/helium/?path=/story/packages-cart--base) ## Installation ```bash npm install @thoughtindustries/cart ``` ## Example Code ```tsx export function MyComponent() { return {/* Your JSX */}; } ``` ## Props | Name | Required | Type | Description | |------|----------|------|-------------| | `children` | Yes | `ReactNode` | Any ReactNode elements. | | `checkoutUrl` | Yes | `string` | The URL for the checkout for this cart. | | `priceFormat` | No | `(priceInCents: number) => string` | Callback to format monetary values. Defaults to Intl.NumberFormat. | | `companyDefaultLocale` | No | `string` | Locale for price formatting. Default: en-US. | | `currencyCode` | No | `string` | Currency code for price formatting. Default: USD. | ## Sub-Components ### CartButton Displays the current item count and toggles the cart modal. Must be a descendant of Cart. ### AddToCartButton Adds a purchasable item to the cart. Can optionally open the cart modal or take the user to checkout. Must be a descendant of CartUIProvider. ```tsx Add to Cart ``` ### CartCheckoutButton Redirects to the checkout URL. Must be a descendant of CartProvider. --- ## Catalog The Catalog allows learners to search and browse for content. [✨ View Component in Storybook](https://thoughtindustries.github.io/helium/?path=/story/packages-catalog--list) ## Installation ```bash npm install @thoughtindustries/catalog ``` ## Props ### CatalogProvider | Name | Required | Type | Description | |------|----------|------|-------------| | `config` | Yes | `CatalogProviderConfig` | Config object controlling various aspects of the Catalog | ### Catalog | Name | Required | Type | Description | |------|----------|------|-------------| | `onAddedToQueue` | Yes | `function` | Handles "Add to Queue" button clicks | | `priceFormat` | No | `function` | Custom price display formatting | ## Examples ### With Search Params and Custom Pagination ```tsx const config = { parsedUrl: { pathname: '/catalog', searchString: '?query=test' } } Promise.resolve()} pagination={({page, pageSize, total, getPageLink}) => <>...} /> ``` --- ## Content Detail Header The `ContentHeader` component displays the title, description, and image of a Course or Learning Path. All content types except learning paths can also display rating and ratingsCount information. [✨ View Component in Storybook](https://thoughtindustries.github.io/helium/?path=/story/packages-content-header--base) ## Installation ```bash npm install @thoughtindustries/content ``` ## Props | Name | Required | Type | Description | |------|----------|------|-------------| | `contentKind` | Yes | `string` | The contentKind (e.g. learningPath, course) | | `slug` | Yes | `slug` | The slug of the content to display | | `showStars` | No | `boolean` | Controls whether rating is displayed | | `showImage` | No | `boolean` | Controls whether detail image is displayed | ## Example Code ```tsx export function MyComponent() { return ( ); } ``` --- ## Dashboard Stats The `DashboardStats` component displays a high-level view of how many courses the user has access to, how many threads/comments ("collaborations") they have participated in, and how many certificates they have earned. [✨ View Component in Storybook](https://thoughtindustries.github.io/helium/?path=/story/packages-dashboard-stats--dashboard) ## Installation ```bash npm install @thoughtindustries/dashboard-stats ``` ## Props None ## Example Code ```tsx export function MyComponent() { return ; } ``` --- ## Featured Content Featured Content components add real power to your platform page creation. These widgets let you display courses, microlearning, and products in neat blocks. Configure them to show static content or dynamically exhibit content based on search queries or tags. [✨ View Component in Storybook](https://thoughtindustries.github.io/helium/?path=/story/packages-featured-content-base--tile-standard-layout) ## Installation ```bash npm install @thoughtindustries/featured-content ``` ## Props ### FeaturedContent | Name | Required | Type | Description | |------|----------|------|-------------| | `sidebar` | No | `ReactNode` | Optional sidebar | | `sidebarPosition` | No | `SidebarPosition` | Left or Right | | `children` | No | `ReactNode` | The actual content tiles | ## Layout Examples ### Standard Layout ```tsx Promise.resolve()} desktopColumnCount={2}> ``` ### Descriptive Layout ```tsx Promise.resolve()}> ``` ### Carousel ```tsx ``` --- ## Components Helium provides a set of pre-built, open-source React components that you can use to build your learning experience. Each component is installable as a separate npm package under the `@thoughtindustries` scope. | Component | Description | |-----------|-------------| | [Cart](./cart) | Handles checkout redirection with customizable price formatting and cart UI management. | | [Catalog](./catalog) | Lets learners search and browse for content with configurable pagination and price formatting. | | [Featured Content](./featured-content) | Display courses, microlearning, and products in configurable tile layouts and carousels. | | [Login](./login) | Renders a sign-in form for email and password authentication with forgot password link. | | [Registration](./registration) | Renders a registration form for new learners with optional redirect URL. | | [Content Detail Header](./content-header) | Displays the title, description, image, and rating of a course or learning path. | | [Dashboard Stats](./dashboard-stats) | Shows course access count, collaboration threads, and certificates earned. | :::tip[Storybook] All components are available in [Storybook](https://thoughtindustries.github.io/helium) for interactive previews. ::: --- ## Login The `Login` component renders a form for learners to enter email address and password to sign in, as well as a link to retrieve password. Once the learner signs in successfully, they will be redirected to the dashboard page. [✨ View Component in Storybook](https://thoughtindustries.github.io/helium/?path=/story/packages-user-login--login-form) ## Installation ```bash npm install @thoughtindustries/user ``` ## Props None ## Example Code ```tsx export function MyComponent() { return ; } ``` --- ## Registration The `Registration` component renders a form for learners to register for your site and gain access to the learner dashboard. [✨ View Component in Storybook](https://thoughtindustries.github.io/helium/?path=/story/packages-user-registration--registration-form) ## Installation ```bash npm init helium-app@latest ``` ## Props | Name | Required | Type | Description | |------|----------|------|-------------| | `redirectUrl` | No | `String` | The URL to redirect the new learner once they sign up (e.g. /learn/dashboard) | | `currentUser` | No | `CurrentUser` | The Current User for registration and redemption functionality | ## Example Code ```tsx export function MyComponent() { return ; } ``` --- ## Contributing Guidelines # Contribution and Coding Guidelines ## Use Volta The deployment lambda uses specific versions of node and npm. The root `package.json` specifies: ```json "volta": { "node": "22.22.0", "npm": "10.9.4" } ``` After [installing Volta](https://docs.volta.sh/guide/getting-started) (and restarting your session), your terminal will automatically use the correct versions. ## Use npm Workspaces The Helium repo uses npm workspaces to link dependencies. Most npm commands run from the root directory. ```bash npm install example-dependency -w ./packages/example-component ``` Adding a new component workspace: ```bash npm init -w ./packages/new-example-component ``` - UI packages should be developed under `/packages/` - Package name should be prefixed with `@thoughtindustries/`, license: MIT ## GraphQL Codegen Use GraphQL Codegen to generate hooks for queries and mutations. The repo points to the remote GraphQL endpoint for schema introspection. ```bash npm run generate -w ./ ``` ## Storybook and Testing Each package should include a `Readme.md` and a Storybook story. Run from root: ```bash npm run storybook ``` Storybook runs at `localhost:6006` and is published at [thoughtindustries.github.io/helium](https://thoughtindustries.github.io/helium). Tests run with: ```bash jest --watch ``` ## Coding Standards - **PascalCase** for components, type names, enum values - **camelCase** for properties and local variables - **lowercase-hyphenated** for filenames - `.generated.*` files are auto-generated — do not hand-edit - Use arrow functions, 2-space indentation, double quotes - Use `.map()`, `.filter()` instead of loops - Review [Node.js compatibility](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) for Cloudflare Workers constraints --- ## Installing The Helium App ## Requirements - A Thought Industries Enterprise Learning Cloud account and API key - [Node.js](https://nodejs.org) and [npm](https://docs.npmjs.com) installed. Use a version manager like [Volta](https://volta.sh). - **Helium 3.0 requires Node.js 18.20.7** (check: `node -v`) - **npm 8.19.2 or higher** (check: `npm -v`) :::tip[Video tutorial] There's also a [video tutorial](https://www.youtube.com/watch?v=Gzywq6JahmY&t=33s) of the Getting Started guide. ::: ## Step 1: Create a Helium App Run the following command in your terminal: ```bash npm init helium-app@latest ``` This will prompt you to install the `@thoughtindustries/helium` package if you don't have it installed. ## Step 2: Choose a Theme Select a theme to get started: | Theme | Description | |-------|-------------| | **Starter App** | Contains Home, Catalog, and Dashboard pages with all components. Best for getting up and running quickly. | | **Hello World** | A single blank page. Better if you want to build from scratch. | ## Step 3: Install Packages `cd` into your app directory and install dependencies: ```bash npm install ``` ## Step 4: Connect to a Learning Instance Connect your learning instance with the authenticate command: ```bash npm run authenticate ``` You'll be prompted to enter: - The URL of your learning instance - Your API key (Settings → Security → API Key) - A nickname for this instance (e.g. sandbox, production) :::info[Configuration file] This generates a `ti-config.json` file in your root directory. If it didn't generate, re-run the authenticate command. ::: ## Step 5: Sync Translations Sync translations from your instance to local development: ```bash npm run update-translations ``` ## Step 6: Start Development Server ```bash npm run dev ``` Visit `localhost:3000` to see your changes in real-time. No restart needed! ## Step 7: Deploy Deploy your Helium app to Cloudflare workers managed by Thought Industries: ```bash npm run deploy [nickname] ``` :::tip[Best Practice] Add multiple instances to your `ti-config.json`. Deploy and test in sandbox before production. ::: --- ## Migration 2.x → 3.0 This guide provides step-by-step instructions for migrating your Helium application from version 2.x to 3.0. ## What Changed and Why | Change | Description | |--------|-------------| | **vite-plugin-ssr → Vike** | Official rebrand by the maintainer with improved API and better documentation. | | **Vite 3 → Vite 5** | Faster builds, improved HMR, enhanced TypeScript support. | | **Dependency Updates** | Storybook v7→v8, i18next v21→v23, react-i18next v11→v13, GraphQL 16.6→16.11. | | **Enhanced Configuration** | Vite config expanded with critical build settings and factory function pattern. | ## Prerequisites - Backup your project (create a git branch) - Node.js 18.20.7 required (check: `node -v`) - npm 8.19.2+ (check: `npm -v`) - Clean git state — commit or stash pending changes - Working 2.x app that builds and runs successfully ## Step 1: Update Dependencies ```json { "dependencies": { "@thoughtindustries/helium-server": "^3.0.0", "@thoughtindustries/catalog": "^2.0.0", "@thoughtindustries/content": "^2.0.0", "vike": "0.4.195", "vite": "^5.4.11", "i18next": "^23.16.8", "react-i18next": "^13.5.0" } } ``` Remove old dependency and clean install: ```bash npm uninstall vite-plugin-ssr rm -rf node_modules package-lock.json npm install ``` ## Step 2: Add Build Scripts ```bash npm pack @thoughtindustries/helium-template@latest tar -xzf thoughtindustries-helium-template-*.tgz package/scripts mv package/scripts ./scripts rm -rf package thoughtindustries-helium-template-*.tgz ``` Update `build:vite` in `package.json`: ```json "build:vite": "vite build && node scripts/generate-manifest.js && node scripts/generate-asset-tags.js" ``` ## Step 3: Update Imports ```diff - import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr/server'; + import { escapeInject, dangerouslySkipEscape } from 'vike/server'; ``` ## Step 4: Update Vite Configuration ```typescript export default defineConfig(async () => { const config = await createVikeConfig(); return config; }); ``` ## Step 5: Test and Deploy ```bash rm -rf dist node_modules package-lock.json npm install npm run dev npm run build npm run deploy sandbox ``` :::info[Migration time] Most migrations take 1–4 hours depending on project size. The majority of time is testing, not code changes. ::: --- ## Project Breakdown :::tip[Video tutorial] There is also a [video tutorial](https://www.youtube.com/watch?v=sIUreFrmdLA&t=90s) of the Project Breakdown. ::: ``` my-helium-app ├── components │ ├── Hero.tsx │ └── Testimonial.tsx ├── pages │ ├── catalog │ │ ├── test.page.tsx │ │ └── index.page.tsx │ ├── courses │ │ └── @courseSlug.page.tsx │ ├── index │ │ └── index.page.tsx │ └── dashboard ├── renderer │ ├── _default.page.client.tsx │ ├── _default.page.server.tsx │ └── _error.page.tsx ├── locales │ └── translations.json ├── ti-config.json └── package.json ``` ## /components The `/components` directory holds various components that you can use in your Helium app. ## /pages Helium utilizes a file-based routing system which is controlled here. Every folder and file corresponds to a subdirectory of your URL. The `catalog/index.page.tsx` file corresponds to the `/catalog` route and the `catalog/test.page.tsx` corresponds to the `/catalog/test` route. ## /renderer The `/renderer` directory holds the `_default.page.client.tsx` and `_default.page.server.tsx` files. These are useful for handling client-side vs server-side operations. ## /locales The `/locales` directory holds the `translations.json` file. Sync translations from your instance by running `npm run update-translations`. ## ti-config.json The `ti-config.json` file connects a Helium app to learning instance(s). This file is generated by running `npm run authenticate`, but can also be created manually. A typical config holds 2–3 instances for sandbox and production environments. Projects using `@thoughtindustries/helium` CLI >2.0.0 will have a `content` property. Directories provided to `content` will be parsed for GraphQL operations at deploy time. ```json { "content": [ "./pages/**/*.{js,jsx,ts,tsx}", "./components/**/*.{js,jsx,ts,tsx}" ], "instances": [ { "nickname": "sandbox", "instanceUrl": "https://sandbox.thoughtindustries.com", "email": "dev@example.com", "apiKey": "**************" }, { "nickname": "production", "instanceUrl": "https://production.thoughtindustries.com", "email": "dev@example.com", "apiKey": "**************" } ] } ``` --- ## Routing Helium utilizes a file-based routing system which means you define all routes for your Helium app in the `/pages` directory. The Helium Framework's job is to replace pages in Thought Industries with your own code and functionality. ## Replacing an Existing Page Taking control of a page in Helium is as simple as defining a new file-path in the pages directory. Create a new folder underneath the pages directory with the name of the route you want to replace, then create a file named `index.page.tsx`. - Dashboard: `/pages/learn/dashboard.page.tsx` - Catalog: `/pages/catalog/index.page.tsx` - Homepage: `/pages/index/index.page.tsx` ## Creating a New Page Create new pages for your instance by adding a new folder named the new route in the pages folder with an `index.page.tsx`. You can also create other files with the naming convention `[otherName].page.tsx`. - New `/test` route: `/pages/test/index.page.tsx` - Sub-route under catalog: `/pages/catalog/test-catalog.page.tsx` ## Replacing Multiple Pages at Once For pages that act more like templates (e.g., course details), a single file can control the layout for multiple/all courses using dynamic route parameters. 1. In the `pages/courses` directory, create a file named `@courseSlug.page.tsx` 2. Add `'routeParams'` to `passToClient` in `render/_default.page.server.tsx`: ```js export const passToClient = [ ..., 'isProduction', 'queryParams', 'authToken', 'routeParams' ]; ``` 3. Reference the `courseSlug` param: ```tsx function Page() { const { routeParams } = usePageContext(); return ( <> Welcome to {routeParams.courseSlug} ); } export { Page }; ``` More information on route strings: [vike.dev/route-string](https://vike.dev/route-string) ## Creating a Catch-All Route Catch-all routes replace the classic 404 error page. Create a file named `*.page.tsx` in a pages directory (e.g., `pages/courses/*.page.tsx`). This page will be shown when the user visits any unmatched route under that path. --- ## Secrets Management This document outlines how environment variables are supported and managed in the Helium project. Environment variable support depends on both the build toolchain and the publishing infrastructure used by Helium. ## Use Cases There are two primary use cases for environment variables in Helium: public and private. ### Public Variables - Use public variables for non-sensitive configuration values—such as application titles or feature flags—that are safe to embed in the frontend bundle. Access via `import.meta.env`. - During build, these variables are statically replaced with their actual values and embedded in the final bundle. - Example: `import.meta.env.HELIUM_PUBLIC_APP_TITLE` ### Private Variables - Use private variables for sensitive values such as API keys or credentials used in server-side data fetching. Access via `process.env`. - Private variables remain untouched during build and are injected at runtime in the deployed environment. - Example: `process.env.HELIUM_PRIVATE_SECRET_API_KEY` ## Step 1: Define Environment Variables Once authenticated, add environment variables to the `.env` file at the root of your project. Use `HELIUM_PUBLIC_` prefix for public and `HELIUM_PRIVATE_` for private variables. ```bash # Example public variables HELIUM_PUBLIC_APP_VERSION='v1' # Example private variables HELIUM_PRIVATE_SECRET='secret value' ``` ## Step 2: Reference Public Variables In Page components and their imports, reference public variables with `import.meta.env`: ```typescript // in pages/hello.page.ts const appVersion = import.meta.env.HELIUM_PUBLIC_APP_VERSION; export { Page }; function Page() { return <>`Hello! App version: ${appVersion}`; } ``` ## Step 3: Reference Private Variables In server Page components, reference private variables with `process.env`: ```typescript // in page/data.page.server.ts export { onBeforeRender }; async function onBeforeRender(pageContext) { const secretValue = process.env.HELIUM_PRIVATE_SECRET; const response = await fetch('', { headers: { secret: secretValue } }); const { data } = await response.json(); return { pageContext: { data } }; } ``` ## Step 4: TypeScript IntelliSense Use an `env.d.ts` file to augment `ImportMetaEnv`: ```typescript /// interface ImportMetaEnv { readonly HELIUM_PUBLIC_APP_VERSION: string; } interface ImportMeta { readonly env: ImportMetaEnv; } ``` ## How It Works Under the Hood ### Build Phase - Public variables (`import.meta.env`) are statically replaced with actual values - Private variables (`process.env`) are left intact ### Deploy Phase - Private variables are transformed into global runtime variables - Secrets are securely created and attached to the Cloudflare Worker --- ## Single Sign On Accommodating Single Sign On within your Helium project is as simple as adding a button, hook, or action that will send the user to your Provider's login page. After successfully logging in via your Provider, the user will be sent to the `returnTo` url as specified by your Provider or by the query parameters included in your request. Additional information regarding the support and configuration of various SSO Providers can be reviewed on [Academy](https://academy.thoughtindustries.com/learn/video/overview-of-sso). The Thought Industries SSO docs also live [here](https://support.thoughtindustries.com/hc/en-us/article_attachments/33377520060055). ## SAML 2.0 ``` ${host}/access/saml/login/:client? ``` | Path | Description | |------|-------------| | `client` | *Optional* The slug of the Panorama the user should belong to, if the Panorama has its own SSO Configuration | ## OpenID Connect ``` ${host}/access/openId/login/:client? ``` | Path | Description | |------|-------------| | `client` | *Optional* The slug of the Panorama the user should belong to, if the Panorama has its own SSO Configuration | ### Query Parameters | Parameter | Description | |-----------|-------------| | `returnTo` | *Optional* The URL the user should be sent to after successfully authenticating. If no `returnTo` is provided, the user will be sent to `/learn/` | ## JSON Web Token (JWT) ``` ${host}/access/jwt ``` | Parameter | Description | |-----------|-------------| | `jwt` | **Required** A signed, valid token containing the identity and attributes of the user, e.g., `email`, `externalCustomerId`, `role`, `returnTo`, etc. The token can be signed either by your Secret Key or API Key. | :::tip[JWT persistence] The JWT token generated when authenticated into the main Thought Industries platform will persist across Helium pages also. ::: --- ## Server Side Rendering ## What is SSR? Server-Side Rendering (SSR) is a technique used to improve load performance and SEO. Instead of sending a blank HTML shell and loading everything on the client, SSR sends pre-rendered HTML from the server. This allows users to see meaningful content faster and improves search engine crawlability. Once loaded, client-side JavaScript takes over ("hydration") to make the application interactive. ## Helium and SSR Helium supports Server-Side Rendering out of the box. While not strictly required, we strongly recommend using SSR to optimize performance—especially for content-heavy pages or SEO-critical routes. Here's an example using the `` component: ```tsx FeaturedContent, ContentTileStandardLayout, FeaturedContentContentItem } from '@thoughtindustries/featured-content'; hydrateContent, useCatalogQuery, useAddResourceToQueueMutation } from '@thoughtindustries/content'; export { Page }; function Page() { const { i18n } = useTranslation(); const [addResourceToQueue] = useAddResourceToQueueMutation(); const handleAddedToQueue = (item: FeaturedContentContentItem): Promise => item.displayCourse ? addResourceToQueue({ variables: { resourceId: item.displayCourse } }).then() : Promise.resolve(undefined); const { data, loading, error } = useCatalogQuery(); let content; if (data) { content = data.CatalogQuery.contentItems.map((item, index) => { const hydratedItem = hydrateContent(i18n, item); const { authors, description, href, ...restItemProps } = hydratedItem; const transformedItem = { ...restItemProps, authors, shortDescription: description && `${description.substring(0, 75)} ...`, linkUrl: href }; return ; }); } return ( {content} ); } ``` --- ## Building Your Dashboard # Building Your Dashboard With Helium In this tutorial, we'll walk through creating a custom dashboard experience powered by Helium. We'll identify which GraphQL calls to use, then build out the frontend components. ## Part 1: API Calls ### CurrentUser Query Fetch the user's name, courses, and panorama: ```graphql query { CurrentUser { firstName lastName client { name } } } ``` ### UserContentItems Query ```graphql query { UserContentItems { title id } } ``` ### UserCertificates Query ```graphql UserCertificates { source certificateTemplate { title } } ``` ## Part 2: Building the Frontend ### Custom Welcome Message ```jsx function Dashboard() { const dashboard_query = gql` query DashboardQuery { CurrentUser { firstName client { name } } } `; const { data, error } = useQuery(dashboard_query); if (data) { console.log(data); } return ( I'm a custom dashboard page! ); } export { Dashboard }; ``` ### Adding Dashboard Stats Install the component: ```bash npm i @thoughtindustries/dashboard-stats ``` Add it to your dashboard: ```jsx return ( {header} ); ``` ### Adding Certificates Add `UserCertificates` to your query and render the data: ```jsx let certificates; if (data) { certificates = data.UserCertificates.map((cert, i) => ( {cert.certificateTemplate.title} )); } return ( {header} {certificates} ); ``` --- ## Use Cases Step-by-step tutorials showing how to build real features with the Helium framework. | Tutorial | Description | |----------|-------------| | [Building Your Dashboard](./dashboard) | Create a custom dashboard experience powered by Helium using GraphQL queries and frontend components. | | [Multi-Panorama Dashboards](./panorama-dashboards) | Build different dashboard pages for different panoramas (clients) extending the base dashboard tutorial. | --- ## Multi-Panorama Dashboards Build on the [Build Your First Dashboard](./dashboard.mdx) tutorial to create different dashboard pages for different panoramas (clients). ## Prerequisites - Completed the Build Your First Dashboard tutorial - Basic understanding of Helium's page structure and routing - Access to your Helium instance and clientIds (panorama IDs) - This only works in a deployed environment where currentUser is populated ## Part 1: Project Structure ``` my-helium-app ├── pages │ └── learn │ ├── dashboard.page.server.tsx │ └── dashboard.page.tsx │ └── PanoramaDashboardA │ └── index.page.tsx │ └── PanoramaDashboardB │ └── index.page.tsx ├── renderer ├── locales ├── ti-config.json └── package.json ``` ## Part 2: Server-Side Redirect Use `dashboard.page.server.tsx` to detect the user's panorama and redirect: ```typescript // dashboard.page.server.tsx export { onBeforeRender }; async function onBeforeRender(pageContext: PageContext) { const { currentUser } = pageContext; if (!currentUser) { return { pageContext: { redirectTo: '/sign_in' } }; } const baseUrl = process.env.HELIUM_PRIVATE_INSTANCE_URL; const clientId = currentUser.client; if (!baseUrl) { throw new Error('HELIUM_PRIVATE_INSTANCE_URL is not set'); } // Panorama A if (clientId === '143tyg3y-0280-470b-bfc3-873bd882a5b3') { return { pageContext: { redirectTo: `${baseUrl}/PanoramaDashboardA` } }; } // Panorama B else if (clientId === '1045e00f-4707-df9b-972e-10842edbaa7f') { return { pageContext: { redirectTo: `${baseUrl}/PanoramaDashboardB` } }; } return {}; } ``` ## Part 3: Individual Dashboard Pages Create separate dashboard components for each panorama: ```tsx // pages/PanoramaDashboardA/index.page.tsx function Page() { return ( Welcome to Panorama A Dashboard ); } export { Page }; ``` :::tip[Shared components] Extract common dashboard elements (stats, certificates) into shared components. Only the layout and branding differ between panorama dashboards. ::: --- ## What's New in Helium 3.0 Helium 3.0 brings significant improvements to performance, developer experience, and reliability. ## 1. Modern Build Tooling | Tool | Update | |------|--------| | **Vike** (formerly vite-plugin-ssr) | Updated to vike 0.4.195 — better docs, improved error messages, more active development. | | **Vite 5** | Faster builds, better HMR, improved caching, enhanced TypeScript support. | ## 2. Performance Improvements - **Manifest Caching** — Manifest parsed once at server startup instead of every request. Faster response times for all pages. - **Memory Management** — Apollo Client automatic cleanup prevents memory leaks. Proper disposal of unused clients ensures consistent performance. ## 3. Developer Experience - Centralized Vite config via `createVikeConfig()` — less boilerplate, can't break production settings - Better error messages with troubleshooting steps for Vike loading, missing dependencies, and build failures - Enhanced TypeScript support with better type definitions and autocomplete ## 4. Security Improvements Dependencies updated to mitigate security vulnerabilities: - GraphQL: 16.6.0 → 16.11.0 - universal-cookie: ^4.0.4 → ^7.2.2 - Vite: 3.x → 5.4.11 - Storybook: v7 → v8.6.14 ## 5. Enhanced Client Routing Enable client routing for instant page transitions: ```typescript // In renderer/_default.page.client.tsx export const clientRouting = true; ``` ## 6. Backward Compatibility Helium 3.0 maintains backward compatibility for: - All GraphQL queries and mutations - Component APIs (no breaking changes) - Routing patterns (file-based routing unchanged) - Deployment process (same commands) - `ti-config.json` structure Only import statements and vite.config.ts need updating. See the [Migration guide](./migration.mdx) for step-by-step instructions. --- ## Analytics & Data --- ## CRMs & Automation --- ## Single Sign-On (SSO) --- ## Tracking Scripts --- ## Bulk Operations --- ## Creating Courses via API Create and update entire courses — course groups, sessions, sections, lessons, and topics — in a single API call, then publish, archive, and automate them at scale. This tutorial is a task-oriented walkthrough; for the exhaustive field-by-field contract see the [Create courses](/docs/rest/courses/create) and [Update courses](/docs/rest/courses/update) reference pages and [Rate limits](/docs/rest/rate-limits). > **What you can and can't create over REST.** You can create and update the full learning-content hierarchy (course groups, courses/sessions, sections, lessons, topics) for **five** course kinds, publish/unpublish, and archive. There is **no REST endpoint to hard-delete a course** — you archive instead. **Webinars (VILT) and in-person events (ILT) cannot be made with the create endpoint**; they use dedicated bulk-import routes. Details in each section below. --- ## 1. Overview and prerequisites {#overview} The two endpoints that do almost everything: | Action | Method + path | |---|---| | Create up to 100 courses (each with its full hierarchy) | [`POST /incoming/v2/content/course/create`](/docs/rest/courses/create) | | Update existing content, or append sub-entities, or archive | [`PUT /incoming/v2/content/course/update`](/docs/rest/courses/update) | **Base URL.** All paths in this tutorial are relative to your instance's incoming API base: ``` https://{your-instance}.thoughtindustries.com/incoming/v2 ``` **Authentication.** Every `/v2/` route is authenticated with a bearer API key: ``` Authorization: Bearer YOUR_API_KEY ``` Generate a key in the admin panel under **Settings → Security**. Pass it as the second whitespace-delimited token of the `Authorization` header (i.e. `Bearer `). See [Authentication](/docs/rest/authentication) for key management. **Permissions.** The key's role must grant: - `courses.new` — to call `course/create`. - `courses.edit` — to call `course/update` (including archive). - `courses.edit.settings` — to publish/unpublish via `releaseContent`. **Rate limits.** `course/create` and `course/update` are **not** rate-limited. A few routes an automation typically touches alongside them **are** throttled (`userAPI` tier: 20/min default, 250/min with `increasedAPIRateLimits`) — notably [`GET /v2/jobs/{id}`](/docs/rest/background-jobs) (job polling) and `GET /v2/content` (content search). See [Rate limits](/docs/rest/rate-limits) for the full table and backoff code. A throttled route returns HTTP `429` with this body and headers: ```json { "errors": ["Rate limit exceeded, retry in 2 seconds"] } ``` ``` x-ratelimit-limit: 20 x-ratelimit-remaining: 0 x-ratelimit-reset: 1785438355 retry-after: 12 ``` `retry-after` is in seconds and is the value to honor. > **Related training:** [API Fundamentals — Thought Industries Academy](https://academy.thoughtindustries.com/learn/course/api-fundamentals/main/api-fundamentals) — a 5-minute course covering the basics of API fundamentals. --- ## 2. The content hierarchy {#hierarchy} A single `course/create` call builds an entire tree: ``` Course Group (the catalog listing — title, description, pricing, tags, images) └── Course (a "session" — one is auto-created per group on create) └── Section └── Lesson └── Topic (the actual pages: text, video, audio, SCORM, PDF, quiz, …) ``` - **Course Group** = the catalog entity learners browse and enroll in. - **Course (session)** = a runnable instance of the group. On create, a group is created with **exactly one** session; you can add more sessions later via `course/update`. - **Section → Lesson → Topic** = the ordered structure inside a session. You don't have to build the whole tree at once. You can create a bare course group and then append sections/lessons/topics later with `course/update` (see [§8](#8-updating-content)). A create with **no** `sections` and no `topics` is valid: the platform auto-creates one section and one lesson, both titled `Main`, so the session is never structurally empty. Reading back a `{"title": "…", "kind": "courseGroup"}` create gives: ```json { "id": "d2e3f4a5-b6c7-8901-defa-234567890123", "title": "Workplace Safety Basics", "status": "draft", "sections": [ { "id": "1a2b3c4d-0000-0000-0000-000000000021", "title": "Main", "releaseAt": null, "lessons": [ { "id": "1a2b3c4d-0000-0000-0000-000000000022", "title": "Main", "accessLevel": null, "topics": [] } ] } ], "completionCriteria": [] } ``` **Limits to keep in mind.** All four messages below are returned as **plain text**, not JSON — see [§10](#10-validation-and-errors): - Up to **100 courses per `create` request** → `Cannot create more than 100 courses at a time` - Up to **25 children per course** (sections + lessons + topics combined) → `Course at index 0 exceeds maximum of 25 children (sections + lessons + topics). Found 26 children.` - An empty `courseAttributes` array — or a body with no `courseAttributes` key at all — returns `No items provided` - `update` accepts up to **100 total items** across all entity types combined → `Cannot update more than 100 items at a time` --- ## 3. Supported course kinds {#kinds} The required `kind` field takes exactly **five** values on create. This is the single most common source of errors — there is no `courseType` field, and values like `learning_path`, `webinar`, `scorm`, or `xapi` are **not** valid kinds. (See also the [Supported course kinds](/docs/rest/courses/kinds) reference.) | `kind` | What it creates | Notes | |---|---|---| | `courseGroup` | A full multi-section course (group + session + `sections[]`) | The general-purpose kind; build the whole tree with `sections`. | | `microCourse` | A short course whose topics live in a top-level `topics[]` (no sections/lessons) | Use `topics` directly on the course object. | | `article` | A single article page | Provide `articleVariant.body`. | | `video` | A standalone video course | Provide course-level `videoUrl` (upload) **or** `videoAsset` (existing Wistia media). | | `shareableContentObject` | A standalone SCORM course | Provide course-level `scormUrl`. | **Not kinds (common mistakes):** - `xApiObject` is a **topic type**, not a course kind. (You add xAPI as a topic inside a lesson.) - A **learning path** is a separate content type, not a course kind. - **Webinar (VILT)** and **in-person event (ILT)** are created only through the bulk-import routes in [§11](#11-creating-at-scale-bulk). Sending an unsupported kind returns HTTP `400`. The message begins with `Invalid course kinds:` and ends with the list of supported kinds: ```json { "errors": [ "Invalid course kinds: … Supported kinds are: article, video, courseGroup, microCourse, shareableContentObject" ] } ``` Match on the `Invalid course kinds:` **prefix** in your error handling rather than trying to parse the offending value back out of the message. Not every bad `kind` reaches that check. Values that exist elsewhere in the platform's type system (for example `xApiObject`, which is a real *topic* type) produce the message above. Values that are not a known type anywhere (for example `learning_path`, `webinar`, or `scorm`) are rejected one layer earlier and return a generic processing error instead: ```json { "errors": [ { "message": "A processing error occurred. Please refresh the page and try again.", "extensions": {} } ] } ``` Because a bad `kind` may return either message, validate `kind` against the five supported values client-side before sending. --- ## 4. Your first course (minimal create) {#first-course} The smallest valid create needs only `title` and `kind`. Here is a `courseGroup` with one section, one lesson, and one text topic: ```bash curl -X POST "https://{your-instance}.thoughtindustries.com/incoming/v2/content/course/create" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "courseAttributes": [ { "title": "Introduction to Marketing", "kind": "courseGroup", "sections": [ { "title": "Getting Started", "lessons": [ { "title": "Welcome", "openType": "studentsOnly", "topics": [ { "title": "Course Overview", "type": "text", "body": "Welcome to the course!" } ] } ] } ] } ] }' ``` The request body is always `{ "courseAttributes": [ ... ] }` — an **array**, even for one course. Each `courseAttributes` entry is one course. **Response** — HTTP `200` (not `201`), with an object containing the IDs it created (UUIDs), not the full course object: ```json { "courseIds": ["a1b2c3d4-e5f6-7890-abcd-ef1234567890"], "courseGroupIds": ["b2c3d4e5-f6a7-8901-bcde-f23456789012"] } ``` - `courseGroupIds` — the catalog entity IDs (one per `courseAttributes` entry). - `courseIds` — the session IDs (one per group on create). - `backgroundJob` — **only** present when the request queued an asynchronous upload (SCORM, xAPI, PDF, video-from-URL, audio, or images). See [§9](#9-background-jobs). There is **no `externalId`, no `slug`-on-create, no `authors`, no `source`, and no `isFree`** input — and no `courseType` either. Sending any of those six field names on an otherwise-valid course returns `400` with the generic processing-error message, because an unrecognized field is rejected before validation runs. Slugs are auto-generated from `title` on create (editable later via update). Pricing uses `priceInCents` / `freeWithRegistration` / `purchasable`. --- ## 5. Field reference (the essentials) {#field-reference} This is the working subset most integrations need. The complete tables (every catalog, session, section, lesson, and topic field) live in the [Create courses](/docs/rest/courses/create) and [Update courses](/docs/rest/courses/update) reference pages — link there rather than memorizing everything here. ### Course-level (top of each `courseAttributes` entry) | Field | Type | Notes | |---|---|---| | `title` | string | **Required.** Omitting it returns `400` with the generic message only, so validate client-side. | | `kind` | string | **Required.** One of the 5 kinds ([§3](#3-supported-course-kinds)). Omitting it returns the same generic `400`. | | `sku` | string | Free-form SKU. **Not** enforced unique — a second course with an SKU already in use is accepted. | | `sections` | array | For `courseGroup` kind. | | `topics` | array | For `microCourse` kind (top-level topics). | | `articleVariant` | object | For `article` kind (`{ "body": "…" }`). | | `scormUrl` | URL | For standalone `shareableContentObject`. | | `videoUrl` / `videoAsset` | URL / ID | For standalone `video` (mutually exclusive). | | `enrollmentLimit`, `enrollmentStartDate`, `enrollmentEndDate`, `courseStartDate`, `courseEndDate`, `gracePeriodEndDate` | int / date | Enrollment + scheduling. Dates are ISO 8601, and **their ordering is validated** — see below. | | `discussionsEnabled`, `availableToPublic` | boolean | Feature toggles. | The date fields are cross-validated, with a readable message. Sending `enrollmentEndDate` after `courseEndDate` returns `400`: ```json { "errors": ["Invalid course dates: The course end date must be after or equal to the course enrollment end date"] } ``` ### Catalog (course-group) attributes | Field | Type | Notes | |---|---|---| | `description` | string | Max 5,000 chars → `{"errors":["Description exceeds maximum length of 5,000 characters"]}` | | `tagIds` | array | UUIDs of **pre-existing** tags, **max 10**. Tags are **not** auto-created; use `GET /tags` to list valid IDs. | | `customFields` | JSON object | `{ "slug": "value" }` or `{ "slug": ["v1","v2"] }`. Values must be string / string[] / null. Replaces the whole object on update. | | `asset` / `detailAsset` | URL | Catalog & detail images. **Queues a background job** — one `contentUploads` entry per image field ([§9](#9-background-jobs)). `assetAltText` / `detailAssetAltText` for alt text. | | `ribbon` | string | Ribbon badge slug; must exist in the company config. | | `metaTitle` / `metaDescription` | string | SEO (max 200 / 500 chars) → `{"errors":["metaTitle exceeds maximum length of 200 characters"]}` | | `contentType` | string | Content-type label; auto-derived from `kind` if omitted: `courseGroup` → `Course`, `microCourse` → `MicroCourse`, `article` → `Article`, `video` → `Video`, `shareableContentObject` → `SCORM Course`. | | `relatedCourseGroupIds` | array | UUIDs; must exist on the company. | The catalog validations all return readable messages in the JSON `errors`-array-of-strings shape: ```json { "errors": ["Some tag IDs were not found"] } { "errors": ["Cannot assign more than 10 tags per course. Please reduce your selection."] } { "errors": ["customFields values must be string, string array, or null (field \"department\" has type number)"] } { "errors": ["Invalid ribbon slug 'not-a-ribbon'. Available ribbons: new-release"] } { "errors": ["Invalid relatedCourseGroupIds: 00000000-0000-0000-0000-000000000000. Course groups not found or not accessible."] } ``` Handy detail: the `ribbon` error **enumerates the valid ribbon slugs for your instance**, so a deliberately bogus `ribbon` is the quickest way to discover them. ### Session (course) attributes | Field | Type | Notes | |---|---|---| | `status` | string | Omit → `draft`. Accepted values: `draft`, `authoring`, `published`, `loginRestriction`, `archived`, `pending`, `deleted`. With `futurePublishDate` (and no `status`), becomes `pending`. | | `priceInCents` | integer | e.g. `9900` = $99.00. Max 99,999,999; not negative. | | `freeWithRegistration` | boolean | Default `false`. | | `purchasable` | boolean | Default `false`. | | `futurePublishDate` / `publishDate` | date | Scheduled publish / catalog ordering date. | | `forceLinearProgress` | boolean | Force in-order completion (default `false`). | | `showProgress` | boolean | Show progress indicator (default `true`). | | `prerequisiteCourseIds` / `prerequisiteLearningPathIds` | array | UUIDs; must exist. | `status: "archived"` is accepted on create: the session is created already out of circulation, and reading it back returns `"status": "archived"`. To take *existing* content down, archive the course group via update instead ([§8](#8-updating-content)). Status-related validations: ```json { "errors": ["Invalid status 'zzz'. Valid values are: draft, authoring, published, loginRestriction, archived, pending, deleted"] } { "errors": ["Cannot set futurePublishDate when status is published."] } { "errors": ["priceInCents cannot be negative"] } { "errors": ["priceInCents exceeds maximum allowed value of 99999999 ($999,999.99)"] } ``` `futurePublishDate` and `status: "published"` are **mutually exclusive**: send `futurePublishDate` on its own and the session comes back as `"status": "pending"`; send both and you get the `400` above. ### Section / Lesson / Topic | Object | Required fields | Key optional fields | |---|---|---| | **Section** | `title`, `lessons` | `releaseDate` | | **Lesson** | `title`, `openType`, `topics` | — | | **Topic** | `title`, `type` | `body`, `languages`, plus type-specific fields (see [§7](#7-topic-types)) | `openType` (lesson access level) is one of: **`studentsOnly`**, **`open`**, **`emailCaptureOpen`**. There is no `openToAll` or `instructorsOnly`; sending one returns `400` with the generic message and no enum hint, so validate `openType` client-side. `languages` on a topic takes an array of per-language bodies, e.g. `"languages": [{ "language": "en", "body": "hello" }]`. --- ## 6. Worked examples by kind {#examples} Each example is a complete, copy-pasteable request plus the response shape. ### 6.1 `courseGroup` — full course with mixed topics ```bash curl -X POST "https://{your-instance}.thoughtindustries.com/incoming/v2/content/course/create" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "courseAttributes": [ { "title": "Introduction to Marketing", "kind": "courseGroup", "sku": "MKT-101", "description": "Marketing fundamentals for new hires.", "discussionsEnabled": true, "sections": [ { "title": "Getting Started", "lessons": [ { "title": "Welcome", "openType": "studentsOnly", "topics": [ { "title": "Course Overview", "type": "text", "body": "Welcome!" }, { "title": "Embedded Media", "type": "embed", "body": "" } ] } ] }, { "title": "Core Concepts", "lessons": [ { "title": "Marketing Fundamentals", "openType": "studentsOnly", "topics": [ { "title": "The 4 Ps of Marketing", "type": "article", "body": "Product, Price, Place, Promotion" }, { "title": "Knowledge Check", "type": "quiz" } ] } ] } ] } ] }' ``` ```json { "courseIds": ["c3d4e5f6-a7b8-9012-cdef-345678901234"], "courseGroupIds": ["d4e5f6a7-b8c9-0123-defa-456789012345"] } ``` Note the `quiz` topic is created as a **shell** — the page exists, but its questions must be authored in the admin UI ([§7](#7-topic-types)). Reading the session back with [`GET /v2/courses/{courseId}/structure`](/docs/rest/courses/structure) confirms the whole tree, and is how you get the child IDs you'll need for later updates: ```json { "id": "c3d4e5f6-a7b8-9012-cdef-345678901234", "title": "Introduction to Marketing", "status": "draft", "sections": [ { "id": "1a2b3c4d-0000-0000-0000-000000000001", "title": "Getting Started", "releaseAt": null, "lessons": [ { "id": "1a2b3c4d-0000-0000-0000-000000000002", "title": "Welcome", "accessLevel": null, "topics": [ { "id": "1a2b3c4d-0000-0000-0000-000000000003", "title": "Course Overview", "type": "text", "editableByChildren": false }, { "id": "1a2b3c4d-0000-0000-0000-000000000004", "title": "Embedded Media", "type": "embed", "editableByChildren": false } ] } ] }, { "id": "1a2b3c4d-0000-0000-0000-000000000005", "title": "Core Concepts", "releaseAt": null, "lessons": [ { "id": "1a2b3c4d-0000-0000-0000-000000000006", "title": "Marketing Fundamentals", "accessLevel": null, "topics": [ { "id": "1a2b3c4d-0000-0000-0000-000000000007", "title": "The 4 Ps of Marketing", "type": "article", "editableByChildren": false }, { "id": "1a2b3c4d-0000-0000-0000-000000000008", "title": "Knowledge Check", "type": "quiz", "editableByChildren": false } ] } ] } ], "completionCriteria": [] } ``` ### 6.2 `microCourse` — top-level topics, no sections ```bash curl -X POST "https://{your-instance}.thoughtindustries.com/incoming/v2/content/course/create" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "courseAttributes": [ { "title": "Quick Tips: Email Etiquette", "kind": "microCourse", "topics": [ { "title": "Subject Lines", "type": "text", "body": "Keep subject lines clear." }, { "title": "Professional Tone", "type": "text", "body": "Stay professional." }, { "title": "Quiz", "type": "quiz" } ] } ] }' ``` ```json { "courseIds": ["e5f6a7b8-c9d0-1234-efab-567890123456"], "courseGroupIds": ["f6a7b8c9-d0e1-2345-fabc-678901234567"] } ``` ### 6.3 `article` — a single article page ```bash curl -X POST "https://{your-instance}.thoughtindustries.com/incoming/v2/content/course/create" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "courseAttributes": [ { "title": "Best Practices for Remote Work", "kind": "article", "articleVariant": { "body": "Working from HomeHere are the best practices…" } } ] }' ``` ```json { "courseIds": ["a7b8c9d0-e1f2-3456-abcd-789012345678"], "courseGroupIds": ["b8c9d0e1-f2a3-4567-bcde-890123456789"] } ``` ### 6.4 `shareableContentObject` — standalone SCORM (queues a background job) ```bash curl -X POST "https://{your-instance}.thoughtindustries.com/incoming/v2/content/course/create" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "courseAttributes": [ { "title": "Compliance Training Module", "kind": "shareableContentObject", "sku": "COMP-2024", "width": 1024, "height": 768, "embeddedEnabled": true, "scormUrl": "https://example.com/scorm-packages/compliance-2024.zip" } ] }' ``` Because the SCORM package is fetched asynchronously, the response carries a `backgroundJob`: ```json { "courseIds": ["c9d0e1f2-a3b4-5678-cdef-901234567890"], "courseGroupIds": ["d0e1f2a3-b4c5-6789-defa-012345678901"], "backgroundJob": { "id": "7c9e6f80-3d2a-4b15-9f6e-1a2b3c4d5e01", "type": "contentBulkUpload", "status": "queued", "contentUploads": [ { "topicId": "1a2b3c4d-0000-0000-0000-000000000011", "courseId": "c9d0e1f2-a3b4-5678-cdef-901234567890", "contentType": "scorm" } ] } } ``` `scormUrl` is accepted and queued **before** the platform has fetched the package, so a `200` here means "the URL was accepted", not "this is a valid SCORM package". Only the background job can tell you that ([§9](#9-background-jobs)). The `shareableContentObject` kind also auto-creates the containing section/lesson/topic for you; the returned `contentUploads[].topicId` is that generated topic. ### 6.5 `video` — upload from URL vs. existing Wistia media Video fields go at the **course (top) level**, not inside `topics`. `videoUrl` and `videoAsset` are mutually exclusive, and `assetType` cannot be combined with `videoUrl`. Both violations return `400`, but with the generic message (as a bare string in the `errors` array), so there is no machine-readable signal of *which* rule you broke: ```json { "errors": ["A processing error occurred. Please refresh the page and try again."] } ``` **Upload from URL** (downloaded to Wistia in a background job): ```bash curl -X POST "https://{your-instance}.thoughtindustries.com/incoming/v2/content/course/create" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "courseAttributes": [ { "title": "Welcome Video", "kind": "video", "sku": "VID-001", "videoUrl": "https://example.com/videos/welcome.mp4", "posterImageAsset": "https://example.com/images/welcome-poster.jpg" } ] }' ``` ```json { "courseIds": ["e1f2a3b4-c5d6-789a-efab-123456789012"], "courseGroupIds": ["f2a3b4c5-d6e7-89ab-fabc-234567890123"], "backgroundJob": { "id": "7c9e6f80-3d2a-4b15-9f6e-1a2b3c4d5e02", "type": "contentBulkUpload", "status": "queued", "contentUploads": [ { "topicId": "1a2b3c4d-0000-0000-0000-000000000012", "courseId": "e1f2a3b4-c5d6-789a-efab-123456789012", "contentType": "video" } ] } } ``` Note there is **one** `contentUploads` entry, for the video. `posterImageAsset` does not produce its own upload entry, unlike the catalog `asset` / `detailAsset` fields, which each do ([§9](#9-background-jobs)). **Reuse existing Wistia media** (`videoAsset`; no background job): ```bash curl -X POST "https://{your-instance}.thoughtindustries.com/incoming/v2/content/course/create" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "courseAttributes": [ { "title": "Product Demo", "kind": "video", "videoAsset": "abc123wistia", "preAsset": "intro456wistia", "postAsset": "outro789wistia" } ] }' ``` ```json { "courseIds": ["a3b4c5d6-e7f8-9012-abcd-345678901234"], "courseGroupIds": ["b4c5d6e7-f8a9-0123-bcde-456789012345"] } ``` There is no `backgroundJob` here — but there is also no validation of the IDs themselves. The API does not check that a `videoAsset` / `preAsset` / `postAsset` hash exists in Wistia, so a typo returns `200` and produces a silently broken video page. Confirm the IDs before you send them. ### 6.6 `audio` topic — supply an audio file by URL Audio content is API-populatable via the `audioUrl` field on a topic whose `type` is `audio`. The platform downloads the file and stores it in a background job. Supported formats: **MP3, WAV, OGG** (max **200 MB**); the URL must be HTTP/HTTPS. ```bash curl -X POST "https://{your-instance}.thoughtindustries.com/incoming/v2/content/course/create" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "courseAttributes": [ { "title": "Language Lessons", "kind": "courseGroup", "sections": [ { "title": "Pronunciation", "lessons": [ { "title": "Vowel Sounds", "openType": "studentsOnly", "topics": [ { "title": "Listen: Vowel Sounds", "type": "audio", "audioUrl": "https://example.com/audio/vowel-sounds.mp3" } ] } ] } ] } ] }' ``` ```json { "courseIds": ["c5d6e7f8-a9b0-1234-cdef-567890123456"], "courseGroupIds": ["d6e7f8a9-b0c1-2345-defa-678901234567"], "backgroundJob": { "id": "7c9e6f80-3d2a-4b15-9f6e-1a2b3c4d5e03", "type": "contentBulkUpload", "status": "queued", "contentUploads": [ { "topicId": "1a2b3c4d-0000-0000-0000-000000000013", "courseId": "c5d6e7f8-a9b0-1234-cdef-567890123456", "contentType": "audio" } ] } } ``` `audioUrl` is **only** valid on topics with `"type": "audio"`. A mismatch returns HTTP `400` with a precise message that includes the path to the offending topic. The body is **plain text**, not JSON: ``` Cannot use 'audioUrl' with topic type 'text' for topic at course[0].sections[0].lessons[0].topics[0]. This field is only valid for topics with type 'audio'. ``` For a `microCourse`, where topics are top-level, the path is shorter: ``` Cannot use 'audioUrl' with topic type 'text' for topic at course[0].topics[0]. This field is only valid for topics with type 'audio'. ``` The file itself is validated later, during the download, not at request time — a URL that isn't audio still returns `200` here and fails in the background job ([§9](#9-background-jobs)). --- ## 7. Topic types {#topic-types} Topics are the pages inside a lesson. Some types accept content over the API; others create a **shell** (placeholder) that must be finished in the admin UI. **Populatable via API:** | `type` | Field to populate | |---|---| | `text` | `body` (HTML) | | `article` | `body` or `languages` | | `video` | `videoUrl` (upload) or `videoAsset` (existing) | | `audio` | `audioUrl` | | `shareableContentObject` | `scormUrl` | | `xApiObject` | `scormUrl` | | `pdfViewer` | `pdfUrl` | | `embed` | `body` (iframe) | | `discussionBoard` | — (forum) | **Shell only** (created empty; authored in the UI): `quiz`, `test`, `survey`, `assignment`, `image`, `slideshow`, `presentation`, `meetingInfo` (and other assessment/interactive types such as `workbook`, `bongo`, `lti`, `listRoll`, `tally`, `flipCardSet`, `notebook`). A single request can mix both groups freely. One containing `pdfViewer` + `xApiObject` + `shareableContentObject` + `discussionBoard` topics alongside seven shell types returns `200`, with one `contentUploads` entry per URL-fed topic: ```json { "courseIds": ["e7f8a9b0-c1d2-3456-efab-789012345678"], "courseGroupIds": ["f8a9b0c1-d2e3-4567-fabc-890123456789"], "backgroundJob": { "id": "7c9e6f80-3d2a-4b15-9f6e-1a2b3c4d5e04", "type": "contentBulkUpload", "status": "queued", "contentUploads": [ { "topicId": "1a2b3c4d-0000-0000-0000-000000000014", "courseId": "e7f8a9b0-c1d2-3456-efab-789012345678", "contentType": "pdf" }, { "topicId": "1a2b3c4d-0000-0000-0000-000000000015", "courseId": "e7f8a9b0-c1d2-3456-efab-789012345678", "contentType": "xapi" }, { "topicId": "1a2b3c4d-0000-0000-0000-000000000016", "courseId": "e7f8a9b0-c1d2-3456-efab-789012345678", "contentType": "scorm" } ] } } ``` So `contentType` in `contentUploads` is `pdf` / `xapi` / `scorm` / `video` / `audio` / `image` — not the topic `type` string. > **Put type-specific fields on the matching topic `type`.** `audioUrl` is the only type-specific upload field the platform validates against the topic type. Any other mismatch — `pdfUrl` on a `"type": "text"` topic, for example — is accepted with `200` and then silently ignored: no error, no background job, and no content on the page. Make sure the field and the `type` agree before you send the request. --- ## 8. Updating content {#updating} [`PUT /content/course/update`](/docs/rest/courses/update) does three jobs: **partial update**, **append new sub-entities**, and **archive**. Its body shape differs from create — it is an **object keyed by entity type**, not an array: ```json { "courseAttributes": { "courseGroups": [ ], "courses": [ ], "sections": [ ], "lessons": [ ], "topics": [ ] } } ``` Only the fields you send are changed. A simple response is the bare JSON literal `true` (HTTP `200`); if the update queues an upload it returns `{ "success": true, "backgroundJob": { … } }`. A client must handle *either* a boolean *or* an object from this endpoint. ### Partial update (change a title) ```bash curl -X PUT "https://{your-instance}.thoughtindustries.com/incoming/v2/content/course/update" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "courseAttributes": { "courseGroups": [ { "id": "b2c3d4e5-f6a7-8901-bcde-f23456789012", "title": "Updated Course Title" } ] } }' ``` ```json true ``` ### Append new sub-entities (omit `id`) To create a new entity instead of updating one, **omit its `id`** and supply the required parent ID: | Creating a… | Omit `id`, provide parent | |---|---| | Course (session) | `courseGroupId` | | Section | `courseId` | | Lesson | `sectionId` | | Topic | `lessonId` + `type` | ```bash curl -X PUT "https://{your-instance}.thoughtindustries.com/incoming/v2/content/course/update" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "courseAttributes": { "sections": [ { "courseId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "title": "New Section", "displayOrder": 3 } ] } }' ``` ```json true ``` All four append forms return `true`: a new section under a `courseId`, a new lesson under a `sectionId`, a new topic under a `lessonId` (with `type`), and a new session under a `courseGroupId`. Re-read [`GET /v2/courses/{courseId}/structure`](/docs/rest/courses/structure) afterwards to see the appended children in place — that's also the only way to get their new IDs, since the update response doesn't return them. ### Replace SCORM on an existing topic ```bash curl -X PUT "https://{your-instance}.thoughtindustries.com/incoming/v2/content/course/update" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "courseAttributes": { "topics": [ { "id": "1a2b3c4d-0000-0000-0000-000000000011", "scormUrl": "https://example.com/scorm-packages/compliance-2025.zip", "restartProgress": true } ] } }' ``` ```json { "success": true, "backgroundJob": { "id": "7c9e6f80-3d2a-4b15-9f6e-1a2b3c4d5e06", "type": "contentBulkUpload", "status": "queued", "contentUploads": [ { "topicId": "1a2b3c4d-0000-0000-0000-000000000011", "contentType": "scorm" } ] } } ``` `restartProgress: true` resets learner progress when the package is replaced. This returns a `backgroundJob` (the new package is fetched asynchronously). Watch the `contentUploads` shape: on **update** the entry has only `topicId` + `contentType`, with **no `courseId`** — unlike the create response, where `courseId` is present. Don't write a parser that requires `courseId`. ### Publish / unpublish Publishing is a [separate endpoint](/docs/rest/content/release): ```bash curl -X PUT "https://{your-instance}.thoughtindustries.com/incoming/v2/content//releaseContent" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "kind": "course", "action": "release" }' ``` ```json { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "published" } ``` - `kind` is `course` or `learningPath`; `action` is `release` or `unrelease`. - `unrelease` returns `{ "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "draft" }`. - Requires the `courses.edit.settings` permission. - Note the `{id}` in the path is the **session (course) ID**, not the course-group ID. ### Archive instead of delete There is **no REST endpoint to hard-delete a course**. To take a course out of circulation, archive its course group via update: ```bash curl -X PUT "https://{your-instance}.thoughtindustries.com/incoming/v2/content/course/update" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "courseAttributes": { "courseGroups": [ { "id": "e3f4a5b6-c7d8-9012-efab-345678901234", "archived": true } ] } }' ``` ```json true ``` Archiving a group also archives its child courses. Reading a child session back afterwards shows: ```json { "id": "d2e3f4a5-b6c7-8901-defa-234567890123", "title": "Workplace Safety Basics", "status": "archived" } ``` Set `archived: false` to restore it. **Restore does not remember the previous status** — the child session comes back as `draft`, even if it was `published` before being archived. If the course was live, re-publish it via `releaseContent` after restoring. --- ## 9. Background jobs {#background-jobs} Inputs that require fetching a remote asset queue an **asynchronous** `contentBulkUpload` job: SCORM (`scormUrl`), xAPI, PDF (`pdfUrl`), video-from-URL (`videoUrl`), audio (`audioUrl`), and catalog/detail images. When that happens, the create/update response includes a `backgroundJob` with a job `id`. ### The `contentUploads` array has two entry shapes Topic-level uploads are keyed by topic; catalog images are keyed by course group and name the field they came from. A single create that sets both `asset` and `detailAsset` returns: ```json { "courseIds": ["a9b0c1d2-e3f4-5678-abcd-901234567890"], "courseGroupIds": ["b0c1d2e3-f4a5-6789-bcde-012345678901"], "backgroundJob": { "id": "7c9e6f80-3d2a-4b15-9f6e-1a2b3c4d5e05", "type": "contentBulkUpload", "status": "queued", "contentUploads": [ { "courseGroupId": "b0c1d2e3-f4a5-6789-bcde-012345678901", "contentType": "image", "fieldName": "asset" }, { "courseGroupId": "b0c1d2e3-f4a5-6789-bcde-012345678901", "contentType": "image", "fieldName": "detailAsset" } ] } } ``` So a `contentUploads` entry is either `{topicId, courseId, contentType}` (create, topic asset), `{topicId, contentType}` (update, topic asset), or `{courseGroupId, contentType, fieldName}` (catalog image). Treat every key as optional. ### Polling Poll the job until it finishes before treating the content as ready: ```bash curl "https://{your-instance}.thoughtindustries.com/incoming/v2/jobs/" \ -H "Authorization: Bearer YOUR_API_KEY" ``` The response is a **flat** object — the job fields are at the top level, with no envelope around them: ```json { "id": "7c9e6f80-3d2a-4b15-9f6e-1a2b3c4d5e01", "description": "Upload 1 content file for Example Company", "errorMessage": null, "status": "queued" } ``` - Four fields only: `id`, `description`, `errorMessage`, `status`. There is no `contentUploads` on the *read* side — the job read tells you the aggregate outcome, not per-file detail. - `description` is human-readable and counts the files (`Upload 3 content files for `), which is a useful sanity check that the platform saw everything you sent. - `errorMessage` is `null` (**not** `""`) while queued and on success; on failure it carries the reason. - Terminal states are `complete` and `failed`. - [`GET /v2/jobs/{id}`](/docs/rest/background-jobs) **is** rate-limited (`userAPI`, 20/min by default). Honor `retry-after` (see [Rate limits](/docs/rest/rate-limits) and the [Background jobs](/docs/rest/background-jobs) reference). A polling loop: ```bash while :; do RESP=$(curl -s -w '\n%{http_code}' "$BASE/jobs/$JOB_ID" -H "Authorization: Bearer $KEY") CODE=$(printf '%s' "$RESP" | tail -1) BODY=$(printf '%s' "$RESP" | sed '$d') if [ "$CODE" = "429" ]; then sleep 15; continue; fi # or read retry-after STATUS=$(printf '%s' "$BODY" | jq -r '.status') case "$STATUS" in complete) break ;; failed) echo "job failed: $(printf '%s' "$BODY" | jq -r '.errorMessage')"; exit 1 ;; esac sleep 10 done ``` ### Timing How long a job takes depends on the size of the asset, the depth of the upload queue, and the environment your instance runs on. Don't design around a fixed duration: - **Poll with backoff**, honoring `retry-after` on `429`, and give the loop a sensible overall timeout. - **`status: "queued"` is not an error signal.** The response carries no timestamp, attempt count, or queue position, so treat `queued` as "still working". - If the timeout is reached, surface the job ID for follow-up — **don't re-send the create**, which would duplicate the course ([§11](#11-creating-at-scale-bulk)). - Don't gate a user-facing "course ready" state on job completion inside a synchronous request. - The synchronous `200` from create/update means *the URL was accepted and the job was enqueued*. Package and file validity are decided later, in the job. --- ## 10. Validation and errors {#validation} Validation failures return **HTTP `400`**. There is **no `code` field**, no `statusCode` in the body, and no `MISSING_REQUIRED_FIELD` / `DUPLICATE_SLUG` / `INVALID_FIELD_VALUE` style codes. Error responses take several forms depending on which layer rejects the request. Handle all of them, and don't assume the body is JSON. ### The four `400` shapes **1. JSON, `errors` as an array of readable strings.** The good case — business-rule validation in the controller. ```json { "errors": ["Some tag IDs were not found"] } ``` **2. Plain text, no JSON at all.** A bare string body — `JSON.parse` on it throws. ``` Cannot create more than 100 courses at a time ``` The plain-text responses are: `No items provided`, `Cannot create more than 100 courses at a time`, `Cannot update more than 100 items at a time`, `Course at index 0 exceeds maximum of 25 children (sections + lessons + topics). Found 26 children.`, and the `Cannot use 'audioUrl' with topic type …` message. **3. JSON, `errors` as an array of *objects*, carrying only a generic message.** This is the schema/parse layer rejecting the request before any business validation runs, so the message doesn't identify the problem. ```json { "errors": [ { "message": "A processing error occurred. Please refresh the page and try again.", "extensions": {} } ] } ``` The array length loosely tracks how many things were rejected (two or three identical entries are common), but the entries are indistinguishable. **4. JSON, `errors` as an array containing the generic message as a bare string.** The same generic failure, differently serialized. ```json { "errors": ["A processing error occurred. Please refresh the page and try again."] } ``` ### Which triggers give you a specific message, and which don't | Trigger | Message | |---|---| | `tagIds` unknown / not yours | `Some tag IDs were not found` | | More than 10 `tagIds` | `Cannot assign more than 10 tags per course. Please reduce your selection.` | | `customFields` value of the wrong type | `customFields values must be string, string array, or null (field "department" has type number)` | | Bad `ribbon` slug | `Invalid ribbon slug 'not-a-ribbon'. Available ribbons: new-release` | | Bad `relatedCourseGroupIds` | `Invalid relatedCourseGroupIds: . Course groups not found or not accessible.` | | Bad `status` value | `Invalid status 'zzz'. Valid values are: draft, authoring, published, loginRestriction, archived, pending, deleted` | | `futurePublishDate` + `status: published` | `Cannot set futurePublishDate when status is published.` | | Negative / oversized `priceInCents` | `priceInCents cannot be negative` / `priceInCents exceeds maximum allowed value of 99999999 ($999,999.99)` | | `description` / `metaTitle` too long | `Description exceeds maximum length of 5,000 characters` / `metaTitle exceeds maximum length of 200 characters` | | Inconsistent course/enrollment dates | `Invalid course dates: The course end date must be after or equal to the course enrollment end date` | | `audioUrl` on a non-`audio` topic | `Cannot use 'audioUrl' with topic type 'text' for topic at course[0].sections[0].lessons[0].topics[0]. …` (plain text) | | Over 100 courses / over 25 children / empty `courseAttributes` / over 100 update items | plain-text limit messages (see above) | | Unknown topic `id` on update | `Topics not found: 00000000-0000-0000-0000-000000000000` | | Missing `title` | Generic processing-error message only | | Missing `kind` | Generic processing-error message only | | `kind` not a known platform type (`learning_path`, `webinar`, …) | Generic processing-error message only | | Invalid `openType` (e.g. `openToAll`) | Generic processing-error message only | | `videoUrl` + `videoAsset` together | Generic processing-error message only | | `assetType` + `videoUrl` together | Generic processing-error message only | | Any unrecognized field anywhere in the payload | Generic processing-error message only | When you get the generic processing-error message, re-check the payload against the rules in this page rather than looking for a more specific message — there isn't one. The practical rule: **validate required fields and enum values client-side**, since the API will stop a bad request but won't always tell you which mistake you made. ### Index prefixing in multi-course batches Some validations name the offending entry. A batch whose third entry exceeds the child limit returns: ``` Course at index 2 exceeds maximum of 25 children (sections + lessons + topics). Found 26 children. ``` Two things to design for: - **Errors are not accumulated.** The first failing check stops the whole request, so a batch with problems in two entries reports only the first one. Fixing one reported error can reveal the next — retry loops should expect several rounds. - **Not every message is index-prefixed.** A bad `tagIds`, for instance, returns the unprefixed `Some tag IDs were not found`. Where the prefix is present, the format is `Course at index N ` (no colon). ### Create is atomic A batch that fails validation creates **nothing** — including the entries that were valid. Fix and resubmit the whole batch. See [Status codes](/docs/rest/status-codes) for HTTP-level details. --- ## 11. Creating at scale (bulk) {#bulk} ### Native bulk in one request `course/create` is already a bulk endpoint: put up to **100 courses** in `courseAttributes[]` and they're created in a single call. You do **not** need to loop one course per request. Because the batch is atomic (all-or-nothing), prefer **small batches of 10–25** in production so a single bad record doesn't force you to resubmit hundreds of courses, and so the index-prefixed error points at a short list. ### There is no server-side idempotency The API has **no `externalId` or idempotency key**. Re-sending the same batch creates **duplicate** courses. To make an integration safe to retry, **you** must: 1. Keep your own map of `your-external-id → courseGroupId` / `courseId` from the returned IDs. 2. Before creating, check whether the item already exists (see [§13.2](#132-idempotency-is-the-integrations-job) for which read endpoints to use). 3. Use `course/update` for anything that already exists. ### Webinars (VILT) and in-person events (ILT) These kinds are **not** creatable through `course/create`. Use the dedicated bulk-import routes: | Method | Path | |---|---| | POST | `/content/session/webinar/bulk` | | POST | `/content/session/inPersonEvent/bulk` | | POST | `/content/courseGroup/webinar/bulk` | | POST | `/content/courseGroup/inPersonEvent/bulk` | Body: `{ "bulkImportAssetPath": "" }`. All four return the same shape: ```json { "dryRun": false, "backgroundJob": { "id": "7c9e6f80-3d2a-4b15-9f6e-1a2b3c4d5e07", "description": "Bulk import webinars from API for Example Company", "errorMessage": null, "status": "queued" } } ``` Two things to note: - The `backgroundJob` here is the **flat** four-field job object (the same shape `GET /v2/jobs/{id}` returns), **not** the `contentBulkUpload` object with `type` and `contentUploads` that `course/create` returns. Poll it the same way as any other job ([§9](#9-background-jobs)). - The import file at `bulkImportAssetPath` is not fetched or checked synchronously, so a `200` only means the request was accepted. Problems with the file surface in the background job. --- ## 12. Using Postman {#postman} You can drive the whole create → poll → update → publish flow from Postman. ### 12.1 Set up an environment Create a Postman **Environment** with two variables: | Variable | Example value | |---|---| | `baseUrl` | `https://{your-instance}.thoughtindustries.com/incoming/v2` | | `apiKey` | your secret API key (mark it **secret**) | Set **collection-level Authorization** to **Bearer Token** with the token `{{apiKey}}` so every request inherits it. (Equivalently, add a header `Authorization: Bearer {{apiKey}}`.) ### 12.2 Importing / starter requests Point Postman at the published API reference at `https://api.thoughtindustries.com/` to import the schema, or create these requests by hand: 1. **Create course** — `POST {{baseUrl}}/content/course/create`, body = raw JSON from [§4](#4-your-first-course-minimal-create). 2. **Poll job** — `GET {{baseUrl}}/jobs/{{jobId}}`. 3. **Update course** — `PUT {{baseUrl}}/content/course/update`. 4. **Publish** — `PUT {{baseUrl}}/content/{{courseId}}/releaseContent`, body `{ "kind": "course", "action": "release" }`. ### 12.3 Chain returned IDs with a test script On the **Create course** request, add a **Post-response** (Tests) script to capture the returned IDs into environment variables so later requests can reuse them: ```javascript const res = pm.response.json(); if (res.courseGroupIds && res.courseGroupIds.length) { pm.environment.set("courseGroupId", res.courseGroupIds[0]); } if (res.courseIds && res.courseIds.length) { pm.environment.set("courseId", res.courseIds[0]); } if (res.backgroundJob && res.backgroundJob.id) { pm.environment.set("jobId", res.backgroundJob.id); } ``` The `backgroundJob` guard matters: plain creates with no remote assets omit the key entirely. If you add a test on the **Poll job** request, read `status` from the **top level** of the response — `pm.response.json().status` ([§9](#9-background-jobs)). Upload jobs can take a while, so don't wire polling into a Runner step that has to finish quickly. Now the **Publish** request can target `{{baseUrl}}/content/{{courseId}}/releaseContent` and the **Poll job** request can hit `{{baseUrl}}/jobs/{{jobId}}`. ### 12.4 Collection Runner with a CSV → `courseAttributes` batches To create many courses from a spreadsheet, use the **Collection Runner** with a data file. A simple approach maps **one CSV row → one create request**: CSV (`courses.csv`): ```csv title,kind,sku Intro to Sales,courseGroup,SALES-101 Onboarding Basics,microCourse,ONB-001 Compliance 2024,shareableContentObject,COMP-2024 ``` On the **Create course** request, use a **Pre-request** script to assemble the body from the current row and store it, then reference it in the request body: ```javascript // Pre-request: build a one-course courseAttributes array from the CSV row const course = { title: pm.iterationData.get("title"), kind: pm.iterationData.get("kind") }; const sku = pm.iterationData.get("sku"); if (sku) course.sku = sku; pm.variables.set("courseBody", JSON.stringify({ courseAttributes: [course] })); ``` Request body (raw JSON): `{{courseBody}}`. To send **true batches** (up to 100 courses per request instead of one), group rows in code and post the assembled `courseAttributes` array — Postman's row-per-iteration model is best for modest volumes; for large imports, script the batching in your own client (or use the native bulk array directly). ### 12.5 Rate-limit note for runs `course/create` and `course/update` aren't throttled, so a create-only run won't hit `429`. But if your run **also** polls `GET /jobs/{id}` or searches `GET /content`, those are `userAPI` (20/min default): past the limit they return `429` with `retry-after` and `x-ratelimit-*` headers. Read `retry-after` and back off — see [Rate limits](/docs/rest/rate-limits). --- ## 13. Automating with AI agents {#ai-agents} This section is about an LLM agent **orchestrating the real REST endpoints above**. Thought Industries does not ship an agent product or an MCP server for this, so an agent integration is ordinary REST calls with good bookkeeping. Ground the agent in the [Create courses](/docs/rest/courses/create), [Update courses](/docs/rest/courses/update), and [Rate limits](/docs/rest/rate-limits) pages, and in the behavior described below. ### 13.1 Sequencing - **Prefer one call.** When the agent knows the whole structure, send the entire tree in a single `course/create` (group → sections → lessons → topics). Fewer calls, atomic validation. - **Incremental builds.** When building step by step, create the group first, then append sections/lessons/topics with `course/update` (omit `id`, supply the parent ID from [§8](#8-updating-content)). Read the current tree back with [`GET /v2/courses/{courseId}/structure`](/docs/rest/courses/structure) (not throttled) to get child IDs before the next append. ### 13.2 Idempotency is the integration's job There is **no server-side idempotency key**, which makes existence checking the most important thing to get right. The agent must maintain its **own** `external-id → courseGroupId/courseId` map and check existence **before** creating: - Use an **unthrottled** read for existence checks: [`GET /v2/courseGroups`](/docs/rest/course-groups/list) (list), [`GET /v2/courseGroups/{id}`](/docs/rest/course-groups/by-id), or [`GET /v2/courseGroups/slug/{slug}`](/docs/rest/course-groups/by-slug). - If it exists, call `course/update`; otherwise `course/create`. Never assume the server will dedupe — re-posting a create makes duplicates. `sku` won't save you either: it isn't enforced unique, so a duplicate `sku` is accepted. Two details for the existence check: - **`GET /v2/courseGroups/slug/{slug}` returns HTTP `200` on a miss**, not `404`, with an error envelope and a null payload. Branch on the payload, not the status code: ```json { "errors": [ { "message": "CourseGroup not found", "path": ["APICourseGroupBySlug"], "extensions": { "code": "INTERNAL_SERVER_ERROR" } } ], "data": { "APICourseGroupBySlug": null } } ``` - **Don't guess the slug.** Slug generation strips punctuation in ways that are easy to get wrong: `"Introduction to Marketing (v2.1)"` becomes `introduction-to-marketing-v21` — the parentheses vanish *and* the dot closes up. Store the slug returned by a read rather than deriving it from your title. ### 13.3 Error handling from the real shape - Handle all four `400` shapes from [§10](#10-validation-and-errors), including the plain-text ones — don't assume the body parses as JSON. - For multi-course batches, some messages carry a `Course at index N` prefix; map `N` back to the record and correct just that one. Where there's no prefix, and where the message is the generic processing error, re-validate the whole payload. - Because create is **atomic**, a rejected batch created nothing, so it's safe to fix the flagged record(s) and **retry the whole batch**. - Keep batches small (10–25) so a failure is cheap to diagnose and resubmit. ### 13.4 Rate-limit / backoff - `course/create` and `course/update` are unthrottled — the agent can push content fast. - The routes it will hit **around** creation are throttled at `userAPI` (20/min default, 250/min increased): `GET /jobs/{id}` (polling) and `GET /content` (search), plus the bulk price/criteria/certificate endpoints. - On `429`, read `Retry-After` (authoritative) and back off with jitter. Mirror the retry helpers already published in [Rate limits](/docs/rest/rate-limits) rather than re-implementing them. ### 13.5 Wait for async content before "done" After any SCORM/xAPI/PDF/video-URL/audio/image upload, the response carries a `backgroundJob`. The agent should poll [`GET /v2/jobs/{id}`](/docs/rest/background-jobs) — reading `status` from the top level of the flat job object — until it is `complete`, checking `errorMessage` on `failed`, before reporting the course ready. Two things an agent in particular needs to get right here: - **Don't treat a long `queued` as a failure.** Completion time varies with asset size, queue depth, and environment. An agent that gives up after a fixed timeout and retries the create will produce duplicate courses — the worst failure mode on an endpoint with no idempotency key. Back off, and when a timeout is reached hand the job ID to a human rather than re-creating. - **A `200` from create is not "content is live".** It means the URL was accepted. Package validity is decided in the job. ### 13.6 A minimal end-to-end orchestration Using only real endpoints and shapes: ```bash # 1) Create a SCORM course (queues an async upload) CREATE=$(curl -s -X POST "$BASE/content/course/create" \ -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \ -d '{"courseAttributes":[{"title":"Compliance 2024","kind":"shareableContentObject","scormUrl":"https://your-host.example/pkg.zip"}]}') COURSE_ID=$(echo "$CREATE" | jq -r '.courseIds[0]') JOB_ID=$(echo "$CREATE" | jq -r '.backgroundJob.id') # 2) Poll the background job until it reaches a terminal state. # The job object is flat: {id, description, errorMessage, status}. # GET /jobs/{id} is userAPI-throttled (20/min) -> back off on 429. while :; do RESP=$(curl -s -w '\n%{http_code}' "$BASE/jobs/$JOB_ID" -H "Authorization: Bearer $KEY") CODE=$(printf '%s' "$RESP" | tail -1) BODY=$(printf '%s' "$RESP" | sed '$d') if [ "$CODE" = "429" ]; then sleep 15; continue; fi STATUS=$(printf '%s' "$BODY" | jq -r '.status') case "$STATUS" in complete) break ;; failed) echo "upload failed: $(printf '%s' "$BODY" | jq -r '.errorMessage')" >&2; exit 1 ;; esac sleep 10 done # 3) Update a session field (partial update; returns the JSON literal `true`) curl -s -X PUT "$BASE/content/course/update" \ -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \ -d "{\"courseAttributes\":{\"courses\":[{\"id\":\"$COURSE_ID\",\"priceInCents\":9900,\"purchasable\":true}]}}" # 4) Publish -> {"id":"","status":"published"} curl -s -X PUT "$BASE/content/$COURSE_ID/releaseContent" \ -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \ -d '{"kind":"course","action":"release"}' ``` (`$BASE = https://{your-instance}.thoughtindustries.com/incoming/v2`, `$KEY = your API key`.) Responses for steps 1, 3 and 4: ```json // 1) create {"courseIds":["f4a5b6c7-d8e9-0123-fabc-456789012345"],"courseGroupIds":["a5b6c7d8-e9f0-1234-abcd-567890123456"], "backgroundJob":{"id":"7c9e6f80-3d2a-4b15-9f6e-1a2b3c4d5e08","type":"contentBulkUpload","status":"queued", "contentUploads":[{"topicId":"1a2b3c4d-0000-0000-0000-000000000031","courseId":"f4a5b6c7-d8e9-0123-fabc-456789012345","contentType":"scorm"}]}} // 3) update true // 4) publish {"id":"f4a5b6c7-d8e9-0123-fabc-456789012345","status":"published"} ``` Publishing is **not** gated on the upload job: step 4 succeeds while the job is still `queued`. If you publish before the job completes, learners can reach a course whose SCORM package hasn't landed yet. Keep step 2 before step 4, and treat its completion as a real gate. --- ## 14. Next steps {#next-steps} - [Create courses](/docs/rest/courses/create) — the full `POST /v2/content/course/create` reference: every course, catalog, session, section, lesson, and topic field. - [Update courses](/docs/rest/courses/update) — the full `PUT /v2/content/course/update` reference: partial updates, appending sub-entities, and archiving. - [Supported course kinds](/docs/rest/courses/kinds) — the five kinds `create` accepts. - [Course structure](/docs/rest/courses/structure) — `GET /v2/courses/{courseId}/structure`, how you read child IDs back. - [Release or unrelease content](/docs/rest/content/release) — publish and unpublish via `releaseContent`. - [Background jobs](/docs/rest/background-jobs) — `GET /v2/jobs/{id}` background-job polling. - [REST API reference](/docs/rest) — every REST endpoint, browsable by category. - [Rate limits](/docs/rest/rate-limits) — throttle tiers, headers, and backoff code. - [Authentication](/docs/rest/authentication) — API keys and bearer auth. - [Status codes](/docs/rest/status-codes) — HTTP error reference. --- ## Ecommerce 2.0 Multi-Currency Data Extraction --- ## External Ecommerce Integration --- ## SSO Integration --- ## Webhook Configuration