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
| Param | Type | Notes |
|---|---|---|
since | ISO 8601 instant | createdAt >= since. Invalid timestamps return { data: [], error: 'Invalid since timestamp' } (no 400). |
entityType | string | One of the StarPlan entity types — typically program, semester, course, room, instructor, event. |
action | string | One of created, updated, deleted. |
programId | UUID | Match exact program. |
semesterId | UUID | Match exact semester. |
courseId | UUID | Match exact course. |
limit | integer | Max 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.