# BlueVolt API Documentation

> This document is the complete, plain-text reference for the BlueVolt APIs, designed to be read by developers and AI assistants. It covers authentication, all API versions and endpoints, data semantics, and answers to the most common integration questions. Interactive testing and key management live in the [BlueVolt Developer Portal](https://developer.bluevolt.com/).

BlueVolt is a learning management platform for channel and extended enterprise training. Each partner organization runs a **university** — a branded training site with its own courses, learners, groups, and enrollments. The APIs give you programmatic access to your university's data.

**The BlueVolt APIs:**

- **Reporting API** (versions 1–4) — read-only access to enrollments, users, groups, courses, modules, SCORM data, scores, Training Tracks, categories, schedules, and billing transactions.
- **User Management API** (UserSync) — add, update, remove, and merge user profiles.
- **Salesforce API** — Salesforce-specific integration (documented separately; see the [Knowledge Base](https://support.bluevolt.com/hc/en-us/articles/30985174331035-SalesForce-API)).

---

## Authentication

Every request needs **two keys**, both found under **Profile → API Keys** in the Developer Portal after your key request is approved:

| Key | Where it goes |
|---|---|
| **Primary Key** (subscription key) | HTTP header: `Ocp-Apim-Subscription-Key: {primary-key}` (query param `subscription-key` also accepted) |
| **API Key** (university-scoped) | Reporting v1–v4: query parameter `APIkey={api-key}`. User Management API: `ApiKey` field in the JSON request body. |

Example:

```
GET https://apigw.bluevolt.com/devapi4/api/EnrollmentsForUser?APIkey={api-key}&userId=9012
Ocp-Apim-Subscription-Key: {primary-key}
```

Note: the v4 endpoint reference in the Developer Portal does not list the `APIkey` parameter, but it is required on v4 as on every other version.

**v4 additionally requires** that the Developer Portal account that requested the keys uses an email address belonging to a user *inside* your university. Keys requested from an account outside the university work on v2 but fail on v4 with a 500 error (see Troubleshooting). For team integrations, use a shared service email (e.g., `data.analytics@yourcompany.com`) that exists as a university user, so access survives staff changes.

**Getting keys:** create a Developer Portal account → confirm your API add-on with your CSM → select the APIs you need and Request Keys → an admin approves the request → both keys appear under Profile → API Keys.

## Rate Limits

30 requests per minute. Design integrations around bulk endpoints with incremental filters (`lastUpdatedUTCStart`) rather than per-learner request loops.

---

## Choosing an API Version

Four Reporting API versions are active. **Use v4** — it has the most complete endpoint set, consistent pagination, and server-side filtering. The table shows what each version can and cannot do:

| Capability | v1 | v2 | v3 | v4 |
|---|---|---|---|---|
| Enrollments — university-wide | ✓ | ✓ | ✓ | ✓ |
| Enrollments — per user | ✓ | ✓ | ✗ (single user+course lookup only) | ✓ (by userId or username) |
| Enrollments — per course | ✓ | ✓ | ✗ | ✓ |
| Users — all / single | ✓ | ✓ | ✗ | ✓ |
| Groups | ✓ | ✓ | ✗ | ✓ |
| Custom user fields in responses | ✓ | ✓ | ✗ | ✓ |
| Courses, modules, categories, Training Tracks | ✓ | ✓ | partial (scores and schedules only) | ✓ |
| Billing transactions | ✓ | ✓ | ✓ | ✗ (planned for v4) |
| Server-side `enrollmentStatus` filter | ✗ | ✗ | ✓ | ✓ |
| `limit`/`offset` pagination | ✗ | ✗ | ✓ | ✓ |

**Known trap:** v3 removed the per-user enrollment, user, group, and custom-field capabilities that existed in v2. If you need those, use v4 (or v2). Billing transactions are the one capability that currently still requires v1–v3.

Base URLs: v2 `https://apigw.bluevolt.com/devapi2/webapi/v2/`, v3 `https://apigw.bluevolt.com/devapi3/webapi/v3/`, v4 `https://apigw.bluevolt.com/devapi4/api/`.

---

## Endpoint Reference — Reporting API v4 (Recommended)

All endpoints are `GET`, return JSON arrays, and accept `limit`/`offset` for pagination. **If `limit` is omitted, a default page size applies** — always pass an explicit `limit` and page until a response returns fewer records than the limit.

| Endpoint | Parameters | Returns |
|---|---|---|
| `/api/GetEnrollments` | limit, offset, enrollmentDateStart/End, enrollmentStatus, lastUpdatedUTCStart/End | All enrollment records university-wide. Not filterable by user — use `EnrollmentsForUser` for one learner. |
| `/api/EnrollmentsForUser` | userId *or* username, limit, offset, enrollmentDateStart/End, enrollmentStatus, lastUpdatedUTCStart/End | All enrollment records for one learner. |
| `/api/EnrollmentsForCourse` | courseId, limit, offset | All enrollment records for one course. |
| `/api/GetAllUsers` | limit, offset, groupIDs, onlyActiveUsers, lastLoginStartDate/EndDate, enrollmentStartDate/EndDate, lastUpdatedUTCStart/End | All user profiles, including `Licenses` and `CustomFields`. |
| `/api/GetUser` | userId *or* username | A single user profile, including `Licenses` and `CustomFields`. |
| `/api/GroupsForUser` | userid *or* username, limit, offset | Groups one user belongs to. |
| `/api/GroupsForUsers` | userids *or* usernames, limit, offset | Group memberships for multiple users. |
| `/api/GroupsList` | limit, offset | All groups in the university. |
| `/api/GetAllCourses` | limit, offset, onlyActiveCourses, scheduleIncluded, createStartDate/EndDate, lastUpdatedUTCStart/End | All courses in the catalog. |
| `/api/GetCourse` | courseId | A single course. |
| `/api/GetAllModules` | limit, offset | All modules. |
| `/api/GetModule` | moduleId | A single module. |
| `/api/GetModuleScores` | userIds, moduleIds, completionDateStart/End, limit, offset | Module-level scores. |
| `/api/GetAllTrainingTracks` | limit, offset, lastUpdatedUTCStart/End | All Training Tracks with per-user progress (`TrainingTrackEnrollmentID`, `EnrollmentDate`, `DueDate`, `CompletionDate`). |
| `/api/GetCategoryHierarchy` | — | The full course category tree. |
| `/api/CourseCategories` | limit, offset, startDate, endDate | Course category details. |
| `/api/Schedules` | limit, offset, startDate, endDate | Course schedules. |
| `/api/GroupBluebucks` | courseId, limit, offset | Group BlueBucks defined at the course level. |

### Key Response Fields — Enrollment Object

An enrollment is one learner-course pair. The fields that matter most:

- `Id` — enrollment record ID. `UserId` / `Username`, `CourseId` — who and what.
- `EnrollmentStatus` — the learner's state in the course. Values: **`Active`** (enrolled, in progress), **`Complete`** (finished the course), **`Closed`** (the enrollment's access duration expired before the learner completed the course), **`Terminated`** (enrollment ended before completion), **`PendingRequestApproval`** (awaiting enrollment approval).
- `EnrollmentDate`, `CompletionDate`, `WorkDoneDate`, `LastUpdatedUTC` — `LastUpdatedUTC` changes on *every* mutation to the record (completion, status change, etc.), which makes it reliable for incremental sync.
- `Passed`, `Score`, `ManualPass`, `HasStartedCourse`, `PercentComplete` — outcome and progress.
- `CourseSeatTime` — time spent in the course, **in seconds**.
- `AccessExpirationDate`, `AccessDuration`, `RenewalDuration` — enrollment access window.
- `AmountSpent`, `PaymentMethod`, `PaymentPromoCode`, `BlueBucksEarned` — commerce fields.

**Important:** when a learner is unenrolled from a course, the enrollment record **disappears from API responses entirely** — there is no "unenrolled" status. All enrollment queries and counts are point-in-time; the API cannot reconstruct historical enrollment state.

### Key Response Fields — User Object

- `ID`, `UserName`, `FirstName`, `LastName`, `EmailAddress`, `SSOUsername`, `EmployeeCode`, `DateCreated`, `DateModified`, `LastLoginDate`
- `MailingAddress` — nested object (`AddressLine1/2`, `City`, `State`, `Region`, `CountryCode`, `PostalCode`, `PhoneNumber`)
- `Licenses` — array of professional license records (`State`, `LicenseNumber`, `ExpirationDate`, …)
- `CustomFields` — array of `{ UserId, FieldName, CustomFieldValue }`. These are your university's admin-configured profile fields (e.g., "Branch/Dept", "Region"). Field names vary per university; values are always strings. Query one user to discover the field names your university uses.

## Endpoint Reference — Reporting API v2

No pagination — endpoints return complete result sets. No server-side status filter. The `APIkey` query parameter is required on every call (omitted from the table).

| Endpoint | Parameters | Returns |
|---|---|---|
| `/webapi/v2/EnrollmentsForUser` | userID, start, end | All enrollments for one learner (flat array; count = array length). |
| `/webapi/v2/EnrollmentsForCourse` | courseId | All enrollments for one course. |
| `/webapi/v2/GetUserCourseEnrollment` | startDate, endDate, enrollmentStatus | Enrollment records filtered by date/status. |
| `/webapi/v2/GetClassCourseEnrollments` | — | Class course enrollments. |
| `/webapi/v2/GetAllUsers` | groupIDs, onlyActiveUsers, lastLoginStartDate/EndDate, enrollmentStartDate/EndDate, lastUpdatedUTCStart/End | All user profiles with `CustomFields`. |
| `/webapi/v2/GetUser` | userID or userIDs | One or more user profiles. |
| `/webapi/v2/GroupsForUser` / `GroupsForUsers` | userID / — | Group memberships. |
| `/webapi/v2/groups/list` / `groups/detail` / `groups/hierarchy` | — / groupId / — | Group data. |
| `/webapi/v2/GetAllCourses` | startDate, endDate, onlyActiveCourses, scheduleIncluded, lastUpdatedUTCStart/End | All courses. |
| `/webapi/v2/GetCourse` | courseId | A single course. |
| `/webapi/v2/GetAllModules` / `GetModule` | — / id | Modules. |
| `/webapi/v2/moduleScores` | id | Module scores. |
| `/webapi/v2/ScormData` / `DetailedScormData` | id, startDate, endDate | SCORM data for a module. |
| `/webapi/v2/Assessment` | moduleId | Individual assessment results. |
| `/webapi/v2/GetAllTrainingTracks` | lastUpdatedUTCStart/End | Training Tracks with per-user progress. |
| `/webapi/v2/GetCategoryDetail` / `GetCategoryHierarchy` | id / — | Categories. |
| `/webapi/v2/CourseSchedules` | beginTime, endTime | Course schedules. |
| `/webapi/v2/GroupBlueBucks` / `GroupBlueBucksAllCourses` | courseId / — | Group BlueBucks. |
| `/webapi/v2/OnlineBillingTransactions` | beginTime, endTime | Online billing transactions. |

Note: the v2 enrollment object uses `CourseEnrollmentStatusId` — a numeric status ID with **no published integer-to-status mapping**. If you need to filter or interpret enrollment status, use v4, which returns the `EnrollmentStatus` string.

## Endpoint Reference — Reporting API v3

v3 is useful primarily for **billing transactions** (split by type, which v2 lacks) and its enrollment envelope. It has **no user, group, per-user enrollment, or custom field endpoints** — use v4 for those.

| Endpoint | Parameters | Returns |
|---|---|---|
| `/webapi/v3/enrollments` | limit, offset, enrollmentDateStart/End, enrollmentStatus, lastUpdatedUTCStart/End | University-wide enrollments in an envelope: `{ Limit, Offset, Count, Total, Collection }` — `Total` is the full matching count without paging. |
| `/webapi/v3/enrollments/user/course` | userId, courseId | A single learner+course enrollment record. |
| `/webapi/v3/modules/scores` | userIds, moduleIds, completionDateStart/End, limit, offset | Module scores. |
| `/webapi/v3/schedules` | startDate, endDate, limit, offset | Course schedules. |
| `/webapi/v3/transactions/coursePurchase` | transactionDateStart/End, limit, offset | Course purchase transactions. |
| `/webapi/v3/transactions/courseReversal` | transactionDateStart/End, limit, offset | Course reversal (refund) transactions. |
| `/webapi/v3/transactions/trackPurchase` | transactionDateStart/End, limit, offset | Training Track purchase transactions. |
| `/webapi/v3/transactions/trackReversal` | transactionDateStart/End, limit, offset | Training Track reversal transactions. |
| `POST /webapi/v3/users/authenticate` | — | Authenticates a user's credentials. |

Reporting API v1 remains operational for legacy integrations but offers nothing v2 doesn't; new work should not target it.

## Endpoint Reference — User Management API (UserSync)

All endpoints are `POST` to `https://apigw.bluevolt.com/usersync/UserSyncApi.svc/…` with `ApiKey` in the JSON body and the subscription key header. This is the **only write API**.

| Endpoint | Purpose |
|---|---|
| `/UserSyncApi.svc/AddUsers` | Create **or update** user profiles. Matching is by `UserName`: if the username exists, the profile is updated; otherwise a new user is created. Accepts `Profiles` (array), `SendWelcomeEmails`, `SendCollisionEmails`. |
| `/UserSyncApi.svc/RemoveUser` | Remove a user from the university. |
| `/UserSyncApi.svc/MergeUsers` | Merge duplicate users: `{ "UserIdToKeep": 12345, "UserIdsToMerge": [6789], "ApiKey": "…" }`. |

Profile fields in `AddUsers`: `UserName` (match key), `FirstName`, `LastName`, `Email`, `Password`, `JobTitle`, `CompanyName`, `EmployeeCode`, `Address1`, `Address2`, `City`, `State`, `Zip`, `Country`, `WorkPhone`, `SSOLogoutUrl`, and `CustomFieldList` for university-defined fields:

```json
{
  "Profiles": [{
    "UserName": "jsmith",
    "Email": "jsmith@example.com",
    "JobTitle": "Service Technician",
    "CustomFieldList": [
      { "FieldName": "Department", "CustomFieldValue": "Field Service" },
      { "FieldName": "Supervisor",  "CustomFieldValue": "R. Alvarez" }
    ]
  }],
  "ApiKey": "{api-key}",
  "SendWelcomeEmails": false
}
```

Custom field names must match fields already configured in your university (discover them via `GetUser` — see the user object above).

**Note:** the `AddUsers` schema also shows `EnrollmentList`, `TrainingTrackList`, `CourseRoleList`, and related sync-type options. **Enrollment creation via the API is not supported** — do not use these fields. See "What the APIs Cannot Do" below.

---

## What the APIs Cannot Do

Stated explicitly so integrations (and AI assistants) don't assume endpoints that don't exist:

- **Create enrollments or unenroll learners** — enrollment management happens in the university admin UI only. (The `EnrollmentList` fields in the UserSync schema are not supported.)
- **Create or modify courses, modules, schedules, or content** — all Reporting endpoints are read-only; UserSync only manages user profiles.
- **Retrieve certificates** — certificate documents and records are not exposed by any version. Use completion data (`CompletionDate`, `EnrollmentStatus = Complete`, `Passed`) as the system of record; learners access their certificates in the university UI.
- **Reconstruct historical enrollment state** — unenrolled records disappear entirely, so all queries are point-in-time.
- **Filter v2 enrollments by status meaningfully** — no published mapping for `CourseEnrollmentStatusId`; use v4.

---

## Common Questions

### How do I get the total number of courses a learner is enrolled in?

There is no count field. Call `GET /api/EnrollmentsForUser?APIkey={api-key}&userId={id}&limit=200` (v4) and count the records, paging with `offset` until a response returns fewer than `limit` records. Decide first what "enrolled" means for your display: `enrollmentStatus=Active` for courses in progress, or `Active` + `Complete` for all-time. Remember counts are point-in-time — unenrolled courses vanish from the results. On v2, `EnrollmentsForUser` returns the complete set with no paging; the count is the array length, but status filtering must happen client-side.

### How do I display completions, training hours, or compliance data in my BI system?

Use bulk incremental sync, not per-learner calls (30 req/min limit): initial load via paged `GetEnrollments`, then periodic re-queries with `lastUpdatedUTCStart` set to your last sync time — `LastUpdatedUTC` updates on every record mutation, so nothing is missed. Sync `GetAllUsers` the same way for the learner dimension, joining branch/department from `CustomFields` on `UserId`. Completions: `CompletionDate` + `EnrollmentStatus = Complete`. Training hours: sum `CourseSeatTime` (seconds) ÷ 3600.

### How do I update user attributes like job title, department, or supervisor?

`POST /UserSyncApi.svc/AddUsers` with the existing `UserName` — it updates the matched profile. `JobTitle` is a first-class field; department, supervisor, location, branch, and similar attributes are university-configured custom fields set via `CustomFieldList` (names must match your university's configuration exactly).

### How do I know which custom fields my university has?

Call `GetUser` for any learner and read the `CustomFields` array — the `FieldName` values are your university's configured fields.

### Why does the same query work on v2 but not v3?

v3 has a reduced endpoint set — no user, group, per-user enrollment, or custom-field endpoints. Check the version capability map above; what's missing from v3 is in v4.

### Can I authenticate learners through the API?

`POST /webapi/v3/users/authenticate` validates credentials. For single sign-on integration (SAML/Entra/Okta), contact your CSM — SSO setup is handled with BlueVolt's integration engineers, not through these APIs.

---

## Troubleshooting

**v4 returns 500 — "An error occurred while processing your request. Ensure the user is enrolled in a university."**
Your v4 keys were requested from a Developer Portal account whose email is not a user in your university (v2 tolerates this; v4 does not). Fix: create a portal account with an email that exists as a university user and request keys from it, or add the developer's email to the university's user list.

**401 / subscription key rejected.** Confirm the `Ocp-Apim-Subscription-Key` header carries the Primary Key (not the API Key), and that your key request was approved (Profile → API Keys).

**Empty results that should have data.** Check you're on the right version for the capability (see the version map — especially anything user- or group-related on v3), and that the `APIkey` query parameter is present.

**Pre-2025 integration stopped working.** Connections not migrated to the Developer Portal were deactivated after May 30, 2025. Follow [Updating existing API connections](https://support.bluevolt.com/hc/en-us/articles/31954635579675-Updating-existing-API-connections).

**Rate limiting (429 or throttling).** Stay under 30 requests/minute — switch per-learner loops to bulk endpoints with `lastUpdatedUTC` filters.

---

## Support

Questions not covered here: [submit a request](https://support.bluevolt.com/hc/en-us/requests/new) or email support@bluevolt.com. Platform status incidents are announced via the BlueVolt status page.
