HFU Digital Docs
StarPlan APIEndpoints

Change endpoints

Read the StarPlan change log

Every time the platform's StarPlan sync job detects a difference between upstream and the mirrored database, it appends a row to the CkStarPlanChangeLog table. The change endpoints expose those rows for polling consumers; for push-style delivery use Webhooks.

GET /v1/starplan/changes

curl 'https://api.hfu.digital/v1/starplan/changes?since=2026-04-26T00:00:00Z&limit=50'
{
  "data": [
    {
      "id": "ch…",
      "entityType": "course",
      "entityId":   "co…",
      "action":     "updated",
      "previousData": { /* … */ },
      "newData":      { /* … */ },
      "changedFields": ["roomId", "startTime"],
      "programId":    "8b7c…",
      "semesterId":   "se…",
      "courseId":     "co…",
      "createdAt":    "2026-04-26T11:46:12.314Z"
    }
  ]
}

Filters

ParamTypeNotes
sinceISO 8601 instantcreatedAt >= since. Invalid timestamps return { data: [], error: 'Invalid since timestamp' } (no 400).
entityTypestringOne of the StarPlan entity types — typically program, semester, course, room, instructor, event.
actionstringOne of created, updated, deleted.
programIdUUIDMatch exact program.
semesterIdUUIDMatch exact semester.
courseIdUUIDMatch exact course.
limitintegerMax rows returned. Default 100, hard cap 500.

Sort order: createdAt DESC (newest first).

Response shape

The row shape follows the ChangeRecord type used internally by @hfu.digital/coursekit-starplan. previousData and newData may be null for created (no previous) and deleted (no new) actions. changedFields is an array of property names that differ between previous and new — empty for created/deleted rows.

GET /v1/starplan/changes/:id

Single change row. 404 if unknown.

curl https://api.hfu.digital/v1/starplan/changes/<changeId>
{
  "data": {
    "id": "ch…",
    "entityType": "course",
    "action": "updated",
    "/* …same fields as in the list response */": null
  }
}

Polling vs webhooks

If you need to know about changes within seconds rather than minutes, prefer webhooks — the delivery service runs every 30 seconds and matches subscriptions against incoming change events. Polling /changes works but should respect rate limits and use the since filter with the createdAt of the last row you saw.

A robust polling loop looks like:

let lastSeen = new Date(Date.now() - 24 * 3600_000).toISOString(); // start at 24h ago
while (true) {
  const r = await fetch(
    `https://api.hfu.digital/v1/starplan/changes?since=${encodeURIComponent(lastSeen)}&limit=200`,
  );
  const { data } = await r.json();
  for (const change of data.reverse()) {
    process(change);
    lastSeen = change.createdAt;
  }
  await sleep(60_000);
}

Process oldest → newest so lastSeen advances monotonically, and bound limit so a long quiet period followed by a burst doesn't fetch unbounded history.

On this page