Summary and recommendation
LearnUpon exposes a REST API at `https://{subdomain}.learnupon.com/api/v1` authenticated via HTTP Basic Auth using a portal-specific API username and key - not admin login credentials. OAuth 2.0 and token-based auth are not supported. Every request body must wrap the resource in a named key (e.g., `{"user": {...}}`); bare JSON objects are rejected with no partial-success fallback.
Pagination is page-offset only, hard-capped at 50 records per page with no configurable page size. Full directory traversal requires iterating `?page=N` until an empty array is returned. Rate limit thresholds are not publicly documented; HTTP 429 is the only signal, and retry logic with exponential back-off is required.
Each LearnUpon portal subdomain carries independent API credentials, so multi-portal environments require separate auth contexts per portal - a meaningful consideration when building an identity graph across a multi-tenant deployment.
API quick reference
| Has user API | Yes |
| Auth method | HTTP Basic Authentication (API username + API key as password) |
| Base URL | Official docs |
| SCIM available | No |
| SCIM plan required | Enterprise |
Authentication
Auth method: HTTP Basic Authentication (API username + API key as password)
Setup steps
- Log in to LearnUpon as an admin.
- Navigate to Settings > Integrations > API.
- Enable the API and note the generated API Username and API Key.
- Use HTTP Basic Auth: set the API Username as the username and the API Key as the password in every request.
- All requests must be made over HTTPS.
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | integer | Unique internal user ID | system-assigned | read-only | Use in URL path for update/delete |
| string | User's email address (unique identifier) | required | optional | Must be unique per portal | |
| first_name | string | User's first name | required | optional | |
| last_name | string | User's last name | required | optional | |
| password | string | User's password (plain text, write-only) | optional | optional | If omitted on create, a welcome email with set-password link is sent |
| username | string | Username for login (if username login enabled) | optional | optional | |
| role | string | User role: 'learner', 'manager', 'admin' | optional | optional | Defaults to 'learner' if omitted |
| is_active | boolean | Whether the user account is active | optional | optional | Set false to deactivate |
| send_invite | boolean | Send welcome/invite email on creation | optional | n/a | Defaults to portal setting |
| custom_fields | array | Array of portal-defined custom user attributes | optional | optional | Key-value pairs matching portal custom field names |
| group_ids | array | IDs of groups to assign the user to | optional | optional | Separate group membership endpoint also available |
| manager_id | integer | User ID of the user's manager | optional | optional | |
| locale | string | User's preferred language/locale code | optional | optional | e.g., 'en', 'fr' |
| time_zone | string | User's time zone string | optional | optional | Rails time zone format, e.g., 'Eastern Time (US & Canada)' |
| created_at | datetime | Timestamp of account creation | system-assigned | read-only | ISO 8601 |
| updated_at | datetime | Timestamp of last update | system-assigned | read-only | ISO 8601 |
Core endpoints
List Users
- Method: GET
- URL:
https://{subdomain}.learnupon.com/api/v1/users - Watch out for: Returns max 50 users per page. Iterate using ?page=N until an empty array is returned.
Request example
GET /api/v1/users?page=1
Authorization: Basic {base64(api_user:api_key)}
Response example
[
{
"id": 12345,
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Doe",
"is_active": true
}
]
Get User by ID
- Method: GET
- URL:
https://{subdomain}.learnupon.com/api/v1/users/{id} - Watch out for: Can also look up by email using GET /api/v1/users?email={email}.
Request example
GET /api/v1/users/12345
Authorization: Basic {base64(api_user:api_key)}
Response example
{
"id": 12345,
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Doe",
"role": "learner",
"is_active": true
}
Create User
- Method: POST
- URL:
https://{subdomain}.learnupon.com/api/v1/users - Watch out for: Wrap the user object in a 'user' key. Duplicate email returns an error; check for existing users first.
Request example
POST /api/v1/users
Content-Type: application/json
{
"user": {
"email": "new@example.com",
"first_name": "New",
"last_name": "User"
}
}
Response example
{
"id": 12346,
"email": "new@example.com",
"first_name": "New",
"last_name": "User",
"is_active": true
}
Update User
- Method: PUT
- URL:
https://{subdomain}.learnupon.com/api/v1/users/{id} - Watch out for: Uses PUT (full-style), but only supplied fields are changed. Setting is_active=false deactivates without deleting.
Request example
PUT /api/v1/users/12346
Content-Type: application/json
{
"user": {
"first_name": "Updated",
"is_active": false
}
}
Response example
{
"id": 12346,
"email": "new@example.com",
"first_name": "Updated",
"is_active": false
}
Delete User
- Method: DELETE
- URL:
https://{subdomain}.learnupon.com/api/v1/users/{id} - Watch out for: Deletion is permanent and removes all associated enrollment/progress data. Prefer deactivation (is_active=false) for audit retention.
Request example
DELETE /api/v1/users/12346
Authorization: Basic {base64(api_user:api_key)}
Response example
HTTP 200 OK
{}
Enroll User in Course
- Method: POST
- URL:
https://{subdomain}.learnupon.com/api/v1/enrollments - Watch out for: Enrolling an already-enrolled user returns an error. Check existing enrollments before calling.
Request example
POST /api/v1/enrollments
Content-Type: application/json
{
"enrollment": {
"user_id": 12345,
"course_id": 99
}
}
Response example
{
"id": 5001,
"user_id": 12345,
"course_id": 99,
"status": "not_started"
}
Add User to Group
- Method: POST
- URL:
https://{subdomain}.learnupon.com/api/v1/groups/{group_id}/memberships - Watch out for: Group membership can trigger auto-enrollment rules configured on the group. Verify group rules before bulk-adding users.
Request example
POST /api/v1/groups/77/memberships
Content-Type: application/json
{
"membership": {
"user_id": 12345
}
}
Response example
{
"id": 3001,
"group_id": 77,
"user_id": 12345
}
Get User Enrollments
- Method: GET
- URL:
https://{subdomain}.learnupon.com/api/v1/enrollments?user_id={id} - Watch out for: Paginated at 50 per page. Filter by status (not_started, in_progress, passed, failed) using ?status= param.
Request example
GET /api/v1/enrollments?user_id=12345&page=1
Authorization: Basic {base64(api_user:api_key)}
Response example
[
{
"id": 5001,
"user_id": 12345,
"course_id": 99,
"status": "passed",
"score": 85
}
]
Rate limits, pagination, and events
Rate limits: LearnUpon enforces API rate limits but does not publicly document specific numeric thresholds per plan. Requests exceeding limits receive HTTP 429 responses.
Rate-limit headers: Unknown
Retry-After header: Unknown
Rate-limit notes: Specific per-plan rate limit numbers are not publicly documented. Contact LearnUpon support for current limits. Implement exponential back-off on 429 responses.
Pagination method: offset
Default page size: 50
Max page size: 50
Pagination pointer: page
Webhooks available: Yes
Webhook notes: LearnUpon supports webhooks (called 'Notifications' in the UI) that POST JSON payloads to a configured URL when specific events occur.
Alternative event strategy: Poll GET /api/v1/users and GET /api/v1/enrollments with updated_at filters for systems that cannot receive inbound webhooks.
Webhook events: user.created, user.updated, enrollment.created, enrollment.completed, enrollment.passed, enrollment.failed, course.completed, certificate.issued
SCIM API status
- SCIM available: No
- SCIM version: Not documented
- Plan required: Enterprise
- Endpoint: Not documented
Limitations:
- LearnUpon does not offer a native SCIM 2.0 endpoint as of the latest available documentation.
- User provisioning from IdPs (Okta, Entra ID, Google Workspace) is handled via SAML 2.0 JIT (Just-In-Time) provisioning, not SCIM.
- Bulk provisioning outside of JIT requires use of the REST API or CSV import.
- Enterprise plan is required for SAML SSO and JIT provisioning.
Common scenarios
Three primary automation scenarios are well-supported by the API:
Provision and enroll:
POST /api/v1/usersto create the account (capture returnedid), thenPOST /api/v1/groups/{group_id}/membershipsto assign to the onboarding group. If the group has auto-enrollment rules configured, the group membership call may already trigger course enrollment - callingPOST /api/v1/enrollmentsafterward for the same course will return a duplicate-enrollment error.Deactivate a departing employee:
GET /api/v1/users?email={email}to resolve the internalid, thenPUT /api/v1/users/{id}with{"user": {"is_active": false}}. This preserves all enrollment and completion history.DELETE /api/v1/users/{id}is permanent and purges all associated data - avoid unless data removal is explicitly required and policy-compliant.Bulk HR sync: Paginate
GET /api/v1/usersto build an email-to-ID map, then iterate HR records to create or update as needed. There is no bulk-upsert endpoint; every user requires an individual API call. For directories exceeding 1,000 users, schedule sync jobs during off-peak hours to reduce 429 exposure.
Provision a new employee and enroll in onboarding course
- POST /api/v1/users with email, first_name, last_name, and role='learner'; capture returned user id.
- POST /api/v1/groups/{onboarding_group_id}/memberships with the new user_id to assign to the onboarding group (triggers group auto-enrollments if configured).
- Optionally POST /api/v1/enrollments with user_id and specific course_id if direct enrollment is needed beyond group rules.
- Verify enrollment via GET /api/v1/enrollments?user_id={id}.
Watch out for: If the group has auto-enrollment rules, adding the user to the group may already enroll them; calling the enrollment endpoint again will return a duplicate-enrollment error.
Deactivate a departing employee
- GET /api/v1/users?email={email} to retrieve the user's internal id.
- PUT /api/v1/users/{id} with {"user": {"is_active": false}} to deactivate the account.
- Optionally remove from groups via DELETE /api/v1/groups/{group_id}/memberships/{membership_id} to prevent future auto-enrollments.
Watch out for: Deactivation prevents login but preserves all enrollment and completion history. Do not use DELETE unless permanent data removal is intended and compliant with your data retention policy.
Bulk-sync users from an external HR system
- Paginate GET /api/v1/users (page=1, 2, … until empty array) to build a local map of email → learnupon_id.
- For each HR record: if email not in map, POST /api/v1/users to create; if present and attributes differ, PUT /api/v1/users/{id} to update.
- For employees marked inactive in HR, PUT /api/v1/users/{id} with is_active=false.
- Implement exponential back-off on HTTP 429 responses throughout the sync loop.
Watch out for: LearnUpon has no bulk-upsert endpoint; each user requires an individual API call. For large directories (1,000+ users), rate limiting is a practical concern - batch jobs should be run during off-peak hours.
Why building this yourself is a trap
The most significant architectural caveat is the absence of native SCIM 2.0. IdP integrations (Okta, Entra ID, Google Workspace) rely on SAML JIT provisioning, which creates or updates user records on login but performs no deprovisioning.
A user disabled in your IdP will not be deactivated in LearnUpon until an explicit API call or manual action is taken - this is a hard gap in any identity graph that assumes IdP state is authoritative for downstream app access.
Webhooks (called 'Notifications' in the UI) are available for key events including user.created, enrollment.completed, and certificate.issued, and can reduce polling overhead for event-driven pipelines. For systems that cannot receive inbound webhooks, polling GET /api/v1/enrollments with updated_at filters is the documented alternative, though it adds latency and API call volume.
The combination of no SCIM, no OAuth, and undocumented rate limits makes LearnUpon a higher-integration-effort target compared to SCIM-native LMS platforms.
Automate LearnUpon workflows without one-off scripts
Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.