Users

The Users API returns and manages the learners, authors, managers, and admins in your tenant. Reading (GET), creating (POST), updating (PATCH), and deactivating (DELETE, a soft delete) users are all available. Reads need the read scope, creates and updates the write scope, and deactivation the admin scope. You can still provision users in bulk via SCIM 2.0 or CSV import.


The User Object

{
  "id": "a3f2c1d0-4e5b-6789-abcd-ef0123456789",
  "email": "jane.doe@acme.com",
  "first_name": "Jane",
  "last_name": "Doe",
  "role": "learner",
  "is_active": true,
  "external_id": "EMP-00412",
  "created_at": "2026-01-15T09:00:00Z"
}
FieldTypeNotes
iduuidUser UUID
emailstringUnique within the tenant
first_namestringFirst name
last_namestringLast name
rolestringlearner, author, manager, or admin
is_activebooleanfalse = deactivated (cannot sign in)
external_idstring | nullYour system’s identifier (e.g. HRIS employee id), if set
created_atISO 8601When the user was created

Soft-deleted users are never returned.


Endpoints

List Users

GET /api/v1/users

Returns a paginated list of users in the tenant, most recently created first.

Authentication required: API key with the read scope

Query Parameters

ParameterTypeRequiredDescription
pageintegerNoPage number. Default: 1
per_pageintegerNoResults per page. Default: 50, max: 200
rolestringNoFilter by role: learner, author, manager, admin

Example Request

curl "https://{your-subdomain}.embaylms.com/api/v1/users?role=learner&per_page=25" \
  -H "Authorization: Bearer ems_live_0000000000000000000000000000"

Example Response

{
  "data": [
    {
      "id": "a3f2c1d0-4e5b-6789-abcd-ef0123456789",
      "email": "jane.doe@acme.com",
      "first_name": "Jane",
      "last_name": "Doe",
      "role": "learner",
      "is_active": true,
      "external_id": "EMP-00412",
      "created_at": "2026-01-15T09:00:00Z"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 25,
    "total": 342
  },
  "error": null
}

Common Errors

Errors use the standard envelope with a numeric error.code matching the HTTP status — see Errors.

HTTP StatusDescription
401Missing or invalid API key
403Key lacks the read scope, or the API is not included in your plan
429Rate limit or monthly usage cap exceeded — see Rate Limits

Mostly available (LMS-573). POST /users, GET /users/{id}, PATCH /users/{id}, DELETE /users/{id} (deactivate), and the additional GET /users list filters (group_id, is_active, created_after, created_before, sort_by, sort_order) are live. Writes require the write scope; deactivate requires admin. Still planned — not yet available (return 404 today): the sub-resource endpoints GET /users/{id}/enrollments (use GET /api/v1/enrollments?user_id={id}) and GET /users/{id}/transcript.

Additional List Filters

The following GET /api/v1/users query parameters are available:

ParameterTypeDescription
is_activebooleanFilter by active status: true or false
group_idUUIDFilter users belonging to a specific group
created_afterISO 8601Filter users created after this timestamp
created_beforeISO 8601Filter users created before this timestamp
sort_bystringField to sort by: created_at, last_name, email. Default: created_at
sort_orderstringasc or desc. Default: desc

The write API also extends the User object with group_ids, custom_fields, last_login_at, and updated_at fields, as shown in the examples below.


Create User

POST /api/v1/users

Creates a new user in the tenant. API-created users have no password set — they authenticate via SSO, or set a password through the password-reset (invite-link) flow. No email is sent on creation.

Authentication required: write scope

Request Body

FieldTypeRequiredDescription
emailstringYesUser’s email address. Must be unique within the tenant
first_namestringYesFirst name
last_namestringYesLast name
rolestringYeslearner, author, manager, or admin. Assigning manager or admin requires the admin scope
is_activebooleanNoWhether the user can sign in. Default: true
external_idstringNoYour system’s identifier (e.g. HRIS employee id)
group_idsUUID[]NoArray of group UUIDs to assign on creation (max 100)
custom_fieldsobjectNoKey-value pairs matching your tenant’s configured custom fields
localestringNoUser’s preferred language: en or fr-CA. Default: en

Example Request

curl -X POST https://{your-subdomain}.embaylms.com/api/v1/users \
  -H "Authorization: Bearer ems_live_0000000000000000000000000000" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "john.smith@acme.com",
    "first_name": "John",
    "last_name": "Smith",
    "role": "learner",
    "external_id": "EMP-00413",
    "group_ids": ["b4c3d2e1-5f6a-7890-bcde-f01234567890"],
    "custom_fields": { "employee_id": "EMP-00413" }
  }'

Example Response

{
  "data": {
    "id": "c5d4e3f2-6a7b-8901-cdef-012345678901",
    "email": "john.smith@acme.com",
    "first_name": "John",
    "last_name": "Smith",
    "role": "learner",
    "is_active": true,
    "external_id": "EMP-00413",
    "group_ids": ["b4c3d2e1-5f6a-7890-bcde-f01234567890"],
    "custom_fields": { "employee_id": "EMP-00413" },
    "created_at": "2026-06-05T10:00:00Z",
    "updated_at": "2026-06-05T10:00:00Z"
  },
  "meta": null,
  "error": null
}

HTTP Status: 201 Created

Common Errors

HTTP StatusDescription
400Missing required field or invalid value
409A user with this email already exists in the tenant

Get User

GET /api/v1/users/{id}

Returns a single user by their UUID.

Authentication required: read scope

Path Parameters

ParameterTypeRequiredDescription
idUUIDYesUser UUID

Example Request

curl "https://{your-subdomain}.embaylms.com/api/v1/users/a3f2c1d0-4e5b-6789-abcd-ef0123456789" \
  -H "Authorization: Bearer ems_live_0000000000000000000000000000"

Example Response

{
  "data": {
    "id": "a3f2c1d0-4e5b-6789-abcd-ef0123456789",
    "email": "jane.doe@acme.com",
    "first_name": "Jane",
    "last_name": "Doe",
    "role": "learner",
    "is_active": true,
    "group_ids": ["b4c3d2e1-5f6a-7890-bcde-f01234567890"],
    "custom_fields": { "employee_id": "EMP-00412" },
    "last_login_at": "2026-06-04T14:22:11Z",
    "created_at": "2026-01-15T09:00:00Z",
    "updated_at": "2026-06-04T14:22:11Z"
  },
  "meta": null,
  "error": null
}

Common Errors

HTTP StatusDescription
404No user with this ID in the tenant

Update User

PATCH /api/v1/users/{id}

Updates one or more fields on an existing user. Only fields included in the request body are changed.

Authentication required: write scope

Path Parameters

ParameterTypeRequiredDescription
idUUIDYesUser UUID

Request Body

All fields are optional. Include only fields you want to change.

FieldTypeDescription
first_namestringFirst name
last_namestringLast name
rolestringlearner, author, manager, or admin. Assigning manager or admin requires the admin scope
is_activebooleantrue (can sign in) or false (deactivated)
external_idstring | nullYour system’s identifier; null to clear
custom_fieldsobject | nullReplaces the user’s custom fields object; null to clear
localestringen or fr-CA

Group membership is not managed here — use the Groups API (POST / DELETE /api/v1/groups/{id}/members) to add or remove a user from groups.

Example Request

curl -X PATCH "https://{your-subdomain}.embaylms.com/api/v1/users/a3f2c1d0-4e5b-6789-abcd-ef0123456789" \
  -H "Authorization: Bearer ems_live_0000000000000000000000000000" \
  -H "Content-Type: application/json" \
  -d '{ "role": "author", "custom_fields": { "department": "L&D" } }'

Example Response

{
  "data": {
    "id": "a3f2c1d0-4e5b-6789-abcd-ef0123456789",
    "email": "jane.doe@acme.com",
    "first_name": "Jane",
    "last_name": "Doe",
    "role": "author",
    "is_active": true,
    "external_id": "EMP-00412",
    "custom_fields": { "employee_id": "EMP-00412", "department": "L&D" },
    "last_login_at": "2026-06-04T14:22:11Z",
    "created_at": "2026-01-15T09:00:00Z",
    "updated_at": "2026-06-05T10:05:00Z"
  },
  "meta": null,
  "error": null
}

Deactivate User

DELETE /api/v1/users/{id}

Deactivates a user. This is a soft delete — the user’s record, learning history, completions, and certificates are preserved. The user cannot log in after deactivation.

Authentication required: admin scope

Path Parameters

ParameterTypeRequiredDescription
idUUIDYesUser UUID

Example Request

curl -X DELETE "https://{your-subdomain}.embaylms.com/api/v1/users/a3f2c1d0-4e5b-6789-abcd-ef0123456789" \
  -H "Authorization: Bearer ems_live_0000000000000000000000000000"

Example Response

HTTP Status: 204 No Content


Get User Enrollments

GET /api/v1/users/{id}/enrollments

⚠️ Planned — not yet available (returns 404 today).

Returns all enrollments for a specific user.

Live alternative: GET /api/v1/enrollments?user_id={id} — see Enrollments.

Authentication required: read scope

Path Parameters

ParameterTypeRequiredDescription
idUUIDYesUser UUID

Query Parameters

ParameterTypeRequiredDescription
statusstringNoFilter: active, completed, dropped, expired, waitlisted, pending_approval
pageintegerNoDefault: 1
per_pageintegerNoDefault: 50, max: 200

Example Request

curl "https://{your-subdomain}.embaylms.com/api/v1/users/a3f2c1d0-4e5b-6789-abcd-ef0123456789/enrollments?status=active" \
  -H "Authorization: Bearer ems_live_0000000000000000000000000000"

Example Response

{
  "data": [
    {
      "id": "d6e5f4a3-7b8c-9012-defa-123456789012",
      "course_id": "e7f6a5b4-8c9d-0123-efab-234567890123",
      "course_title": "Workplace Safety Fundamentals",
      "status": "active",
      "progress_percent": 65,
      "enrolled_at": "2026-05-01T08:00:00Z",
      "due_date": "2026-07-01T00:00:00Z",
      "completed_at": null,
      "score": null
    }
  ],
  "meta": { "page": 1, "per_page": 50, "total": 8 },
  "error": null
}

Get User Transcript

GET /api/v1/users/{id}/transcript

⚠️ Planned — not yet available (returns 404 today).

Returns the full learning transcript for a user — all completed and in-progress learning activities, including certificates.

Authentication required: read scope

Path Parameters

ParameterTypeRequiredDescription
idUUIDYesUser UUID

Query Parameters

ParameterTypeRequiredDescription
completed_afterISO 8601NoFilter completions after this date
completed_beforeISO 8601NoFilter completions before this date
formatstringNojson (default) or csv (triggers a download)

Example Request

curl "https://{your-subdomain}.embaylms.com/api/v1/users/a3f2c1d0-4e5b-6789-abcd-ef0123456789/transcript" \
  -H "Authorization: Bearer ems_live_0000000000000000000000000000"

Example Response

{
  "data": {
    "user_id": "a3f2c1d0-4e5b-6789-abcd-ef0123456789",
    "generated_at": "2026-06-05T10:00:00Z",
    "completions": [
      {
        "enrollment_id": "d6e5f4a3-7b8c-9012-defa-123456789012",
        "course_id": "e7f6a5b4-8c9d-0123-efab-234567890123",
        "course_title": "WHMIS 2015",
        "completed_at": "2026-03-15T11:30:00Z",
        "score": 92,
        "pass_fail": "pass",
        "certificate_id": "f8a7b6c5-9d0e-1234-fabc-345678901234",
        "certificate_expires_at": "2027-03-15T00:00:00Z"
      }
    ]
  },
  "meta": null,
  "error": null
}

Bulk Create / Update Users

⚠️ Planned — not yet available (returns 404 today; part of the LMS-539 bulk API). For bulk operations that are live today, use POST /api/v1/enrollments/bulk (see Enrollments) or provision users via SCIM 2.0.

POST /api/v1/users/bulk

Creates or updates up to 500 users in a single request. Matched on email — if a user with the given email exists, they are updated; otherwise, a new user is created.

Authentication required: write scope

Request Body

FieldTypeRequiredDescription
usersUser[]YesArray of user objects (max 500)
send_invitesbooleanNoSend invitation emails to newly created users. Default: false
on_conflictstringNoupdate (default) or skip — what to do when a user already exists

Each user object in the array accepts the same fields as Create User.

Example Request

curl -X POST https://{your-subdomain}.embaylms.com/api/v1/users/bulk \
  -H "Authorization: Bearer ems_live_0000000000000000000000000000" \
  -H "Content-Type: application/json" \
  -d '{
    "users": [
      {
        "email": "alice.martin@acme.com",
        "first_name": "Alice",
        "last_name": "Martin",
        "role": "learner",
        "custom_fields": { "employee_id": "EMP-00414" }
      },
      {
        "email": "bob.chen@acme.com",
        "first_name": "Bob",
        "last_name": "Chen",
        "role": "learner",
        "custom_fields": { "employee_id": "EMP-00415" }
      }
    ],
    "send_invites": false
  }'

Example Response

{
  "data": {
    "created": 1,
    "updated": 1,
    "skipped": 0,
    "errors": [],
    "users": [
      {
        "email": "alice.martin@acme.com",
        "id": "09a8b7c6-1d2e-3456-abcd-567890123456",
        "result": "created"
      },
      {
        "email": "bob.chen@acme.com",
        "id": "1a2b3c4d-5e6f-7890-bcde-678901234567",
        "result": "updated"
      }
    ]
  },
  "meta": null,
  "error": null
}

If individual rows fail validation, they are reported in errors and the rest of the batch is processed:

{
  "errors": [
    {
      "row": 3,
      "email": "invalid-not-an-email",
      "message": "email: Invalid email address"
    }
  ]
}

HTTP Status: 207 Multi-Status when there are partial errors; 201 when all succeed.

Common Errors

HTTP StatusDescription
400users array is empty or exceeds 500
429Bulk endpoint limit exceeded