REST Service for POPCORN - ILIAS
edit | blame | history | raw

REST API

HTTP API of globus-ilias-rest — a REST service for POPCORN → ILIAS synchronization.

The service is implemented with Fastify in app.js and starts on
settings.port (default 4101). The active config is settings.$NODE_ENV.json
(see settings.js).

Base URL

http://<host>:4101

In production the service is usually served behind a reverse proxy, e.g.
https://globusfm-dev2.minervis.com/popcorn.

Authentication

Every request must pass the auth token as a query parameter:

?token=<authtoken>

authtoken is defined in settings.$NODE_ENV.json.

Requests without a valid token are rejected with 403 after a 500 ms delay
(anti brute-force / DoS measure):

{ "status": "error", "error": "access denied" }

Exceptions that do not require a token:

  • /api/version
  • everything under /ui/ (static frontend)

Common error format

Most error responses use a consistent shape:

{ "status": "error", "msg": "not found" }

Some routes use message or error instead of msg; this is noted per route.

Route overview

Method Path Auth Purpose
GET /api/version no Service version
GET /api/search/user yes Search users (returns usr_ids)
POST /api/search/reindex yes Rebuild the user search index
GET /api/user yes List/search users (paged)
GET /api/user/count yes Total number of (numeric-login) users
GET /api/user/login/:login yes Get one user by login
GET /api/user/userid/:userid yes Get one user by usr_id
GET /api/user/teilnahmen/:userId yes Memberships of a user
POST /api/user yes Import/create a user in ILIAS
DELETE /api/user/:usr_id yes Delete a user in ILIAS
GET /api/ref_id/:ref_id yes Resolve ref_id → obj_id
GET /api/obj_id/:obj_id yes Resolve obj_id → ref_id
GET /api/kurs yes List all courses
GET /api/kurs/:refId yes Get one course
GET /api/kurs/items/:refId yes Course item tree (recursive)
GET /api/kurs/:refId/teilnehmer yes Course members
GET /api/kurs/:refId/teilnehmer/:userId yes Single course member
GET /api/kurs/:refId/lp yes Learning progress
GET /api/kurs/:refId/teilnehmerByRole yes Course members via role
GET /api/kurs/:refId/roles yes Roles assigned in a course
POST /api/kurs/:refId/status/:usrId yes Set status + passed for a member
GET /api/kurs/:refId/offline yes Get offline flag of a course
POST /api/kurs/:refId/offline yes Set offline flag of a course
DELETE /api/kurs/:refId/teilnehmer/:usrId yes Unenroll a member (abmelden)
GET /api/kurs/rolle/admin yes Courses with admin role
GET /api/kurs/rolle/noadmin yes Courses without assigned admin role
GET /api/ping yes Ping the ILIAS PHP component
GET /ui/* no Built Vue frontend (SPA)

System

GET /api/version

Returns the service version from package.json. No token required.

Response 200

{ "version": "0.1" }

GET /api/ping

Pings the custom ILIAS PHP component (lib/libIlias.js → ping).

Response 200 — forwarded from the PHP component, e.g.

{ "method": "GET", "command": "ping", "status": "ok" }

Response 500

{ "status": "error", "error": "<error>" }

Search

GET /api/search/user

Full-text search over the user index (lib/search.js, FlexSearch). The index is
built from login firstname lastname institution department.

Query parameters

Name Required Description
search yes Search term

Response 200 — array of matching usr_ids (numbers):

[ 23300, 23301 ]

Response 422 — when search is missing:

{ "status": "error", "msg": "no search" }

POST /api/search/reindex

Rebuilds the user search index from the database.

Response 200

{ "status": "ok", "msg": "reindexed in 231 ms" }

Users

GET /api/user

Paged list / search of users.

Query parameters

Name Default Description
offset 0 Offset
limit 10 Max results
search — Optional search term (uses the search index)

Response 200

{
  "total": 11066,
  "offset": 0,
  "limit": 10,
  "data": [
    {
      "usr_id": 23300,
      "login": "134942",
      "firstname": "Aksana",
      "lastname": "Donhauser",
      "gender": "f",
      "email": "alex@minervis.com",
      "institution": "Globus Baumarkt St. Wendel",
      "street": "",
      "city": "",
      "zipcode": "",
      "country": "",
      "department": "MITARBEITER | FARBEN/TAPETEN/BODENBEL.",
      "active": 1
    }
  ]
}

Only users whose login is numeric (login REGEXP '^[0-9]+$') are returned.
If the query fails, an empty result is returned:
{ "total": 0, "offset": 0, "limit": 0, "data": [] }.

GET /api/user/count

Total number of users (numeric login). Uses db.getUserCount() without filters.

Response 200

11066

GET /api/user/login/:login

Get a single user by login.

Path parameters

Name Description
login ILIAS login

Response 200 — user object (same fields as /api/user) plus all user-defined
fields merged in as top-level keys.

Response 404

{ "status": "error", "msg": "not found" }

GET /api/user/userid/:userid

Get a single user by usr_id.

Path parameters

Name Description
userid numeric usr_id

Response 200 — user object incl. user-defined fields.
Response 404 — { "status": "error", "msg": "not found" }
Response 500 — if userid is missing or not numeric:

{ "status": "error", "msg": "userid error" }

GET /api/user/teilnahmen/:userId

Memberships of a user (rows from obj_members with member = 1), enriched with
object title and learning-progress status.

Path parameters

Name Description
userId numeric usr_id

Response 200 — array of

[
  {
    "obj_id": 32212,
    "ref_id": 213,
    "usr_id": 6,
    "title": "112 Feuerwehr ist da",
    "status": 2,
    "passed": 1,
    "status_changed": "2025-07-03T11:26:20.000Z"
  }
]

Response 500 — invalid userId: { "status": "error", "msg": "userId error" }

POST /api/user

Import (create/update) a user in ILIAS via the custom PHP component.

Body (JSON) — POPCORN/SOAP-style user object:

{
  "login": "123456789",
  "passwd": "123456789",
  "passwd_type": "plain",
  "firstname": "Adolfo",
  "lastname": "de la Cruz",
  "email": "alex@example.com",
  "gender": "m",
  "department": "Bananenpflücker",
  "institution": "Globus Budapest",
  "role": 4,
  "udf": {
    "Markt": "Markt UDF 2",
    "Marktnummer": "Marktnummer UDF 2",
    "Personalnummer": "Personal UDF 2"
  }
}

The keys of udf are user-defined-field names; they are mapped to field ids
via udf_definition before the request is forwarded to ILIAS.

Response 200 — result forwarded from the PHP component.

If a udf key does not match a defined field name, the request fails
(udfMap[key] is undefined).

DELETE /api/user/:usr_id

Delete a user in ILIAS.

Path parameters

Name Description
usr_id numeric usr_id

Response 200 — result forwarded from the PHP component.
Response 500 — invalid usr_id: { "status": "error", "msg": "userId error" }


ref_id / obj_id

GET /api/ref_id/:ref_id

Resolve a ref_id (tree/reference id) to an obj_id.

Response 200

{ "ref_id": 213, "obj_id": 32212 }

Response 404 — { "status": "error", "msg": "not found" }

GET /api/obj_id/:obj_id

Resolve an obj_id to a ref_id.

Response 200

{ "ref_id": 213, "obj_id": 32212 }

Response 404 — { "status": "error", "msg": "not found" }


Courses (Kurs)

GET /api/kurs

List all courses (type = 'crs', not deleted).

Response 200 — array of

[
  {
    "ref_id": 213,
    "obj_id": 32212,
    "title": "112 Feuerwehr ist da",
    "description": "...",
    "type": "crs",
    "offline": 0
  }
]

GET /api/kurs/:refId

Get a single course by ref_id.

Response 200 — single object

{
  "ref_id": 213,
  "obj_id": 32212,
  "title": "112 Feuerwehr ist da",
  "description": "...",
  "type": "crs",
  "create_date": "2024-01-01 00:00:00",
  "offline": 0
}

Response 404 — { "status": "error", "msg": "not found" }

GET /api/kurs/items/:refId

Recursive tree of items contained in a course (crs_items, recursive CTE).

Response 200 — array of

[
  { "parent_id": 213, "obj_id": 36450, "ref_id": 597, "title": "asdf123", "type": "tst" }
]

Response 404 — { "status": "error", "msg": "not found" }

GET /api/kurs/:refId/teilnehmer

Course members.

Response 200 — array of

[
  {
    "parent_id": 213,
    "ref_id": 597,
    "obj_id": 36450,
    "title": "asdf123",
    "type": "tst",
    "usr_id": 6,
    "login": "root",
    "firstname": "root",
    "lastname": "user",
    "active": 1,
    "passed": 1,
    "status": 2,
    "status_changed": "2025-10-22T07:42:36.000Z"
  }
]

Response 404 — { "status": "error", "msg": "not found" }

GET /api/kurs/:refId/teilnehmer/:userId

Single course member.

Response 200 — single member object (same fields as above).
Response 404 — { "status": "error", "msg": "not found" }

GET /api/kurs/:refId/lp

Learning progress of a course.

Query parameters

Name Description
raw If present (any non-empty value, e.g. ?raw=1), returns raw sub-object rows instead of the aggregated view

Response 200 (raw) — one row per tracked sub-object and user:

[
  {
    "usr_id": 6,
    "login": "root",
    "firstname": "root",
    "lastname": "user",
    "obj_id": 32212,
    "item_id": 597,
    "lpmode": 5,
    "item_obj_id": 36450,
    "type": "tst",
    "status": 2,
    "status_changed": "2025-10-22T07:42:36.000Z",
    "percentage": 100,
    "completed": 0
  }
]

Response 200 (aggregated) — one row per user, sub-object statuses combined
via lib/libLp.js:

[
  {
    "usr_id": 6,
    "login": "root",
    "firstname": "root",
    "lastname": "user",
    "status": 1,
    "status_changed": "2025-10-22T09:45:56.000Z"
  }
]

status values:

Value Meaning
0 not attempted (noch nicht bearbeitet)
1 in progress (in Bearbeitung)
2 passed (bestanden)
3 failed (nicht bestanden)

Response 404 — { "status": "error", "msg": "not found" }

GET /api/kurs/:refId/teilnehmerByRole

Course members resolved through the course's member role
(rbac_ua joined via the role whose description matches Member%<obj_id>).

Response 200 — array of

[ { "role_id": 12345, "usr_id": 6, "firstname": "root", "lastname": "user" } ]

GET /api/kurs/:refId/roles

Roles assigned in a course (rbac_pa).

Response 200 — array of

[
  {
    "rol_id": 12345,
    "ref_id": 213,
    "obj_id": 999,
    "type": "role",
    "title": "il_crs_member_32212",
    "description": "Member of course 32212"
  }
]

POST /api/kurs/:refId/status/:usrId

Set the learning-progress status and passed flag for a course member. Writes
both ut_lp_marks (status + status_changed) and obj_members (passed).

Path parameters

Name Description
refId course ref_id
usrId user usr_id

Body (JSON) — both fields required

{ "passed": 1, "status": 2 }

Response 200

{ "status": "ok" }

Response 400 — missing arguments:

{ "status": "error", "msg": "argument error", "statusCode": 400 }

Response 500 — e.g. when the affected rows don't match:

{ "status": "error", "msg": "<message>" }

GET /api/kurs/:refId/offline

Get the offline flag of a course.

Response 200

{ "offline": 0 }

Response 500

{ "status": "error", "message": "<message>" }

POST /api/kurs/:refId/offline

Set the offline flag of a course.

Body (JSON)

{ "offline": 1 }

Response 200

{ "offline": 1 }

Response 500

{ "status": "error", "message": "<message>" }

DELETE /api/kurs/:refId/teilnehmer/:usrId

Unenroll a member ("abmelden") via the custom PHP component.

Path parameters

Name Description
refId course ref_id
usrId user usr_id

Response 200 — result forwarded from the PHP component.
Response 404 — { "status": "error", "msg": "Teilnahme not found" }
Response 500 — { "status": "error", "msg": "<message>" }


Course roles / admins

GET /api/kurs/rolle/admin

Courses that have an admin role assigned (role title contains admin).

Response 200 — array of

[
  {
    "crs_obj_id": 32212,
    "crs_ref_id": 213,
    "crs_title": "112 Feuerwehr ist da",
    "rol_id": 12345,
    "role": "il_crs_admin_32212"
  }
]

Response 500 — { "status": "error", "error": "<error>" }

GET /api/kurs/rolle/noadmin

Courses whose admin role exists but has no user assigned (no rbac_ua entry).

Response 200 — same shape as /api/kurs/rolle/admin.
Response 500 — { "status": "error", "error": "<error>" }


Static frontend

GET /ui/*

Serves the built Vue single-page app from vue/dist (registered with
@fastify/static, prefix /ui/). No token required.

Any route that does not match an API route or a static file returns the SPA
fallback vue/dist/index.html.