Summary and recommendation
Skilljar's REST API is versioned at /v1/ with token-based auth: pass the API key as `Token <api_key>` in the Authorization header, or via HTTP Basic Auth with the key as the username and an empty password.
All paths require a trailing slash - omitting it risks 301 redirects or 404s.
No official SDK is published;
all integrations use raw HTTP.
Rate limits are not publicly documented;
implement exponential backoff on 429 responses as a precaution.
Pagination is offset-based using a `page` parameter, with a hard max of 100 records per page.
Iterate the `next` URL in the response until it returns null.
User lookup by email is not a direct endpoint - filter via GET /v1/users/ or paginate results to resolve an internal ID before any user-scoped operation.
Custom user attributes (the `custom_attributes` object on the user record) must be pre-created in the dashboard before they can be written via API.
The `sso_id` and `groups` fields are key anchors for identity graph construction: `sso_id` maps the Skilljar user to an external IdP identity, while `groups` drives content entitlement logic downstream.
API quick reference
| Has user API | Yes |
| Auth method | API Key (Token-based) — passed as HTTP Basic Auth with the API key as the username and an empty password, or via Authorization header as a Bearer token. |
| Base URL | Official docs |
| SCIM available | Yes |
| SCIM plan required | Enterprise |
Authentication
Auth method: API Key (Token-based) - passed as HTTP Basic Auth with the API key as the username and an empty password, or via Authorization header as a Bearer token.
Setup steps
- Log in to the Skilljar Dashboard as an admin.
- Navigate to Settings > Integrations > API Keys.
- Click 'Generate API Key' and copy the key.
- Include the key in requests using HTTP Basic Auth: base64-encode '
:' and set the Authorization header to 'Basic ', or use 'Token ' as the Authorization header value.
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | string | Unique Skilljar user identifier. | system-generated | immutable | Used as path parameter in user endpoints. |
| string | User's email address; used as login identifier. | required | optional | Must be unique across the domain. | |
| first_name | string | User's first name. | optional | optional | |
| last_name | string | User's last name. | optional | optional | |
| username | string | Username for the user account. | optional | optional | Defaults to email if not provided. |
| is_active | boolean | Whether the user account is active. | optional | optional | Set to false to deactivate a user. |
| date_joined | string (ISO 8601) | Timestamp when the user registered. | system-generated | immutable | |
| groups | array | List of group IDs the user belongs to. | optional | optional | Used for access control and segmentation. |
| custom_attributes | object | Key-value pairs for custom user metadata fields defined in the dashboard. | optional | optional | Attribute keys must be pre-configured in the Skilljar dashboard. |
| sso_id | string | External SSO identifier for the user. | optional | optional | Used to map users from an IdP. |
| domain | string | The Skilljar domain/subdomain the user belongs to. | system-assigned | immutable |
Core endpoints
List Users
- Method: GET
- URL:
https://api.skilljar.com/v1/users/ - Watch out for: Pagination is page-based; iterate 'next' URL until null. Default and max page size is 100.
Request example
GET /v1/users/?page=1 HTTP/1.1
Host: api.skilljar.com
Authorization: Token <api_key>
Response example
{
"count": 250,
"next": "https://api.skilljar.com/v1/users/?page=2",
"previous": null,
"results": [{"id": "usr_abc123", "email": "user@example.com", "first_name": "Jane"}]
}
Get User
- Method: GET
- URL:
https://api.skilljar.com/v1/users/{user_id}/ - Watch out for: Requires the Skilljar internal user ID, not email. Retrieve the ID via List Users first.
Request example
GET /v1/users/usr_abc123/ HTTP/1.1
Host: api.skilljar.com
Authorization: Token <api_key>
Response example
{
"id": "usr_abc123",
"email": "user@example.com",
"first_name": "Jane",
"last_name": "Doe",
"is_active": true
}
Create User
- Method: POST
- URL:
https://api.skilljar.com/v1/users/ - Watch out for: Email must be unique within the domain. Duplicate email returns a 400 error.
Request example
POST /v1/users/ HTTP/1.1
Host: api.skilljar.com
Authorization: Token <api_key>
Content-Type: application/json
{"email": "newuser@example.com", "first_name": "John", "last_name": "Smith"}
Response example
{
"id": "usr_xyz789",
"email": "newuser@example.com",
"first_name": "John",
"last_name": "Smith",
"is_active": true
}
Update User
- Method: PATCH
- URL:
https://api.skilljar.com/v1/users/{user_id}/ - Watch out for: PATCH is partial update. Use PUT for full replacement if supported; PATCH is the documented method.
Request example
PATCH /v1/users/usr_xyz789/ HTTP/1.1
Host: api.skilljar.com
Authorization: Token <api_key>
Content-Type: application/json
{"first_name": "Jonathan", "is_active": false}
Response example
{
"id": "usr_xyz789",
"email": "newuser@example.com",
"first_name": "Jonathan",
"is_active": false
}
List Enrollments for User
- Method: GET
- URL:
https://api.skilljar.com/v1/users/{user_id}/enrollments/ - Watch out for: Returns enrollments scoped to the specific user. Pagination applies.
Request example
GET /v1/users/usr_abc123/enrollments/ HTTP/1.1
Host: api.skilljar.com
Authorization: Token <api_key>
Response example
{
"count": 3,
"results": [{"course_id": "crs_001", "status": "completed", "progress": 100}]
}
Enroll User in Course
- Method: POST
- URL:
https://api.skilljar.com/v1/users/{user_id}/enrollments/ - Watch out for: Course must exist and be published. Enrolling an already-enrolled user may return a 400 or idempotent 200 depending on version.
Request example
POST /v1/users/usr_abc123/enrollments/ HTTP/1.1
Host: api.skilljar.com
Authorization: Token <api_key>
Content-Type: application/json
{"course_id": "crs_001"}
Response example
{
"id": "enr_111",
"user_id": "usr_abc123",
"course_id": "crs_001",
"status": "enrolled"
}
List Groups
- Method: GET
- URL:
https://api.skilljar.com/v1/groups/ - Watch out for: Group IDs are needed to assign users to groups via the user update endpoint.
Request example
GET /v1/groups/ HTTP/1.1
Host: api.skilljar.com
Authorization: Token <api_key>
Response example
{
"count": 5,
"results": [{"id": "grp_001", "name": "Enterprise Customers"}]
}
Add User to Group
- Method: POST
- URL:
https://api.skilljar.com/v1/groups/{group_id}/users/ - Watch out for: Group must exist prior to this call. No bulk-add endpoint is documented; loop per user.
Request example
POST /v1/groups/grp_001/users/ HTTP/1.1
Host: api.skilljar.com
Authorization: Token <api_key>
Content-Type: application/json
{"user_id": "usr_abc123"}
Response example
{
"group_id": "grp_001",
"user_id": "usr_abc123",
"status": "added"
}
Rate limits, pagination, and events
Rate limits: Skilljar's public documentation does not explicitly publish rate limit thresholds or tier-based limits as of the last known documentation review.
Rate-limit headers: No
Retry-After header: No
Rate-limit notes: No official rate limit figures or headers documented. Contact Skilljar support for current limits.
Pagination method: offset
Default page size: 100
Max page size: 100
Pagination pointer: page
Webhooks available: Yes
Webhook notes: Skilljar supports outbound webhooks that fire on learner activity events. Webhooks are configured in the Skilljar Dashboard under Settings > Integrations > Webhooks.
Alternative event strategy: For near-real-time user data without webhooks, poll the List Users or List Enrollments endpoints.
Webhook events: course.completed, course.started, lesson.completed, user.registered, enrollment.created, certificate.issued
SCIM API status
SCIM available: Yes
SCIM version: 2.0
Plan required: Enterprise
Endpoint: https://api.skilljar.com/scim/v2/
Supported operations: GET /Users, GET /Users/{id}, POST /Users, PUT /Users/{id}, PATCH /Users/{id}, DELETE /Users/{id}, GET /Groups, POST /Groups, PATCH /Groups/{id}, DELETE /Groups/{id}
Limitations:
- SCIM provisioning requires an Enterprise plan.
- Officially documented IdP integrations are Okta and Azure AD (Microsoft Entra ID); other IdPs may work but are not officially supported.
- SCIM endpoint URL and bearer token are generated within the Skilljar dashboard; the token is IdP-specific.
- Group push support may vary by IdP connector configuration.
- Deprovisioning (DELETE) deactivates the user in Skilljar but may not permanently delete the record.
Common scenarios
Three scenarios cover the majority of lifecycle automation use cases:
Provision and enroll: POST /v1/users/ with email, first_name, and last_name.
Capture the returned id, then POST /v1/users/{id}/enrollments/ with the target course_id.
Guard the create call with a prior GET /v1/users/ email filter - duplicate email returns a 400 with no upsert behavior.
Deactivate on offboard: Resolve the internal user ID via GET /v1/users/ (email filter or pagination), then PATCH /v1/users/{id}/ with {"is_active": false}.
Optionally DELETE /v1/groups/{group_id}/users/{user_id}/ to remove group memberships.
Note: REST deactivation does not emit SCIM deprovision events - if SCIM is also active on the account, coordinate state to avoid conflicts.
SCIM sync on Enterprise: Generate a SCIM bearer token in Settings > Integrations > SCIM (this token is distinct from the REST API key - rotating one does not affect the other).
Configure the SCIM 2.0 connector in Okta or Azure AD with base URL https://api.skilljar.com/scim/v2/.
Officially supported IdPs are Okta and Azure AD;
others may function but are not documented.
Test with a single user assignment before broad rollout.
Provision a new user and enroll them in an onboarding course
- POST /v1/users/ with email, first_name, last_name to create the user account.
- Capture the returned 'id' field from the response.
- POST /v1/users/{id}/enrollments/ with the target course_id to enroll the user.
Watch out for: If the email already exists, the POST /users/ call returns a 400. Check for existing users via GET /v1/users/ with email filter before creating.
Deactivate a user when they leave an organization
- GET /v1/users/?email=user@example.com (or paginate) to find the user's internal ID.
- PATCH /v1/users/{id}/ with body {"is_active": false} to deactivate the account.
- Optionally, remove the user from relevant groups via DELETE /v1/groups/{group_id}/users/{user_id}/.
Watch out for: Deactivation via REST API does not trigger SCIM deprovision events. If SCIM is also configured, coordinate to avoid state conflicts.
Sync users from an IdP using SCIM on Enterprise plan
- Confirm the account is on the Enterprise plan.
- In the Skilljar Dashboard, navigate to Settings > Integrations > SCIM and generate a SCIM bearer token.
- In Okta or Azure AD, configure the SCIM 2.0 connector with base URL https://api.skilljar.com/scim/v2/ and the generated bearer token.
- Enable user provisioning (create, update, deactivate) and optionally group push in the IdP connector settings.
- Test with a single user assignment before enabling for all users.
Watch out for: The SCIM bearer token is separate from the REST API key. Rotating one does not affect the other. SCIM group push behavior depends on IdP connector capabilities.
Why building this yourself is a trap
The primary integration trap is the identity resolution gap: there is no GET /v1/users/by-email/ endpoint, so any system that receives an email address (from a webhook, an HR feed, or an identity graph node) must either maintain a local email-to-ID cache or accept the latency of a paginated list scan on every operation.
At 100 records per page with no documented rate limit ceiling, this becomes a meaningful bottleneck for orgs with large learner populations.
The second trap is the SCIM/REST state split. Both surfaces can modify is_active and group membership independently, with no documented reconciliation behavior. Teams building on top of an identity graph that routes some events through SCIM and others through REST must enforce a single write path per attribute or risk divergent state.
Webhooks fire on learner activity events (course.completed, user.registered, enrollment.created, etc.) and are the only near-real-time signal available - the REST API has no server-sent events or long-poll mechanism. Any identity graph that needs to react to completion or registration events must consume webhooks;
polling List Users or List Enrollments is the only fallback, and it carries the pagination cost described above.
Automate Skilljar 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.