eLearning.edu.pl API
ENPL

Typical flow

Most HR integrations follow three steps: keep people in sync, assign courses, read the compliance status. Every step works with the values your HR system already knows — employee number, department name, course code — so you do not need to store our identifiers.

0. Read the reference lists (once, or when they change)

curl "https://api.elearning.edu.pl/api/v1/departments" -H "X-Tenant-Code: your-tenant" -H "X-API-Key: $KEY"
curl "https://api.elearning.edu.pl/api/v1/course-catalog?assignable=true" -H "X-Tenant-Code: your-tenant" -H "X-API-Key: $KEY"
  • GET /api/v1/departments and GET /api/v1/contractors (scope users:read) list departments and contractor companies with their names and identifiers.
  • GET /api/v1/course-catalog (scope enrollments:write or compliance:read) lists courses with their code; assignable: false comes with unassignable_reason (inactive, path_only, license_expired).

1. Synchronise people

Look a person up by employee number, create them if missing, update changed fields.

BASE_URL="${BASE_URL:-https://api.elearning.edu.pl}"
AUTH=(-H "X-Tenant-Code: $TENANT_CODE" -H "X-API-Key: $API_KEY")
# Values your HR system already knows: employee number, department name, course code.
EMPLOYEE_NO=E1001
DEPARTMENT_NAME=Logistics
COURSE_CODE=SAFETY-101

# 1. Look the person up by employee number; create them if missing.
USER_ID=$(curl --fail-with-body -sS "${AUTH[@]}" "$BASE_URL/api/v1/users?employee_no=$EMPLOYEE_NO" | jq -r '.users[0].id // empty')
if [ -z "$USER_ID" ]; then
  curl --fail-with-body -sS "${AUTH[@]}" "$BASE_URL/api/v1/users" \
    -H "Content-Type: application/json" \
    -d "{\"employee_no\": \"$EMPLOYEE_NO\", \"first_name\": \"Anna\", \"last_name\": \"Example\",
         \"email\": \"anna.example@example.com\", \"department_name\": \"$DEPARTMENT_NAME\"}" > /dev/null
fi

# 2. Assign a course for this year's cycle (409 = already assigned).
STATUS=$(curl -sS -o /dev/null -w '%{http_code}' "${AUTH[@]}" "$BASE_URL/api/v1/enrollments" \
  -H "Content-Type: application/json" \
  -d "{\"employee_no\": \"$EMPLOYEE_NO\", \"course_code\": \"$COURSE_CODE\", \"cycle_year\": 2026, \"due_date\": \"2026-12-31\"}")
case "$STATUS" in 201|409) ;; *) echo "enrollment failed: $STATUS" >&2; exit 1 ;; esac

# 3. Read the compliance matrix page by page.
LIMIT=100
OFFSET=0
while :; do
  PAGE=$(curl --fail-with-body -sS "${AUTH[@]}" "$BASE_URL/api/v1/compliance/matrix?limit=$LIMIT&offset=$OFFSET")
  echo "$PAGE" | jq -r '.rows[] | [.employee_no, .full_name, (.courses | to_entries | map(.key + "=" + .value.status) | join(","))] | @tsv'
  [ "$(echo "$PAGE" | jq '.rows | length')" -lt "$LIMIT" ] && break
  OFFSET=$((OFFSET + LIMIT))
done
curl https://api.elearning.edu.pl/api/v1/users -H "X-Tenant-Code: your-tenant" -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"employee_no": "E1001", "first_name": "Anna", "last_name": "Example",
       "email": "anna@example.com", "department_name": "Logistics", "worker_type": "internal_payroll"}'
  • A new person gets an account without a password and the onboarding enrollments of their department.
  • PATCH /api/v1/users/{id} updates standard and custom fields (attributes); omitted fields stay unchanged.
  • Accounts provisioned by SCIM belong to the identity provider: changing them through this API returns 409 MANAGED_ELSEWHERE.
  • Department: department_name (matched ignoring case, diacritics and extra spaces) or department_id. An unknown name returns 422 — the department must exist first.
  • Contractors: worker_type: b2b_contractor with contractor_name or contractor_id. When two companies share a name, send contractor_id.

Required scopes: users:read, users:write.

2. Assign courses

curl https://api.elearning.edu.pl/api/v1/enrollments -H "X-Tenant-Code: your-tenant" -H "X-API-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"employee_no": "E1001", "course_code": "SAFETY-101", "cycle_year": 2026, "due_date": "2026-12-31"}'

Name the person with employee_no or user_id and the course with course_code or course_id. An enrollment for the same course and cycle returns 409. Licensed library courses follow their licence: a course licensed only inside a learning path or with an expired licence returns 422; when a seat-based licence has no free seat you get 409 SEAT_LIMIT_REACHED. Assignments also happen automatically (department rules, groups, renewals) — subscribe to enrollment.assigned to see all of them. Required scope: enrollments:write.

3. Read compliance

curl "https://api.elearning.edu.pl/api/v1/compliance/matrix?course_code=SAFETY-101&compliance_flag=OVERDUE" \
  -H "X-Tenant-Code: your-tenant" -H "X-API-Key: $KEY"

The matrix is refreshed periodically (refreshed_at in the response). For near-real-time updates use webhooks (course.completed, compliance.certified, compliance.expired). Required scope: compliance:read.

Paging

List endpoints use limit and offset. Iterate until a page returns fewer items than limit.