API reference
Activity and Events
The plain-English event stream the Property tab renders, and the sink that the federated services write into.
Two halves of the same table. GET /v1/activity reads the events table back as sentences for the Property tab in Hotel Operator. POST /v1/events/sink is the write door the Make-It-Right service uses to put its own events into the same stream.
The stream is a plain historical read. Nothing in it is time-filtered, and the rendering is fixed: an event type with no line template is never shown.
GET /v1/activity
One page of rendered activity lines, newest first.
Auth: unauthenticated in this build.
Request
GET /v1/activity
GET /v1/activity?limit=40&before_id=812
| Query param | Type | Required | Meaning |
|---|---|---|---|
limit | integer as a string | optional | Lines to return. Must parse as an integer between 1 and 100. Defaults to 40. |
before_id | integer as a string | optional | Cursor. Returns only events with a lower id. Must parse as an integer of 1 or more. Omit for the first page. |
Response
JSON
{
"items": [
{ "id": 41, "at": "2026-09-10T15:00:00.000Z", "line": "Guest arrived on the guest app", "chip": null }
],
"next_before_id": null
}
| Field | Type | Meaning |
|---|---|---|
items[].id | number | The event row id. Also what you pass back as before_id. |
items[].at | string | The event's occurred_at, verbatim. |
items[].line | string | The rendered sentence. First names only. |
items[].chip | string or null | Provenance label, when the line has one. |
next_before_id | number or null | Cursor for the next page, or null when the stream is exhausted. |
Chips name the system a line came from: SynXis, Stayntouch, Hapi, OPERA, Simphony, QR Find, Make-It-Right. Lines with no external system carry chip: null.
Adjacent rows by the same actor merge into one line. Three completions by the same staff member render as Dana completed 3 dining requests ($103.32). A different actor in between breaks the merge, so the two runs stay separate.
Paging keeps working across skipped rows. The service scans up to 300 raw event rows per call, drops the ones with no template, and returns next_before_id positioned so the next call picks up exactly where this one stopped, with no loss and no duplicates. A page can come back with an empty items array and a non-null next_before_id when a whole window was noise. That is not the end of the stream. Keep following the cursor until it is null. See core/src/__tests__/activity.test.ts.
Errors
| Status | Error | When |
|---|---|---|
| 400 | invalid limit | limit is not a whole number, or is below 1 or above 100. |
| 400 | invalid before_id | before_id is not a whole number, or is below 1. |
POST /v1/events/sink
Write a federated service event into the stream.
Auth: unauthenticated in this build.
Request
POST /v1/events/sink
POST /v1/events/sink
content-type: application/json
| Field | Type | Required | Meaning |
|---|---|---|---|
event_type | string | required | The event name. friction_detected, recovery_sent, and recovery_claimed are the three the platform reads back. |
stay_id | string | required | The stay the event belongs to. Not validated against the stays table. |
occurred_at | string | required | ISO timestamp, stored verbatim. |
| any other field | any | optional | Everything else in the body is kept together as the event's params. cost_usd on a recovery_sent is the one the report sums. |
JSON
{
"event_type": "recovery_sent",
"stay_id": "stay-871",
"occurred_at": "2026-08-30T18:00:00Z",
"cost_usd": 14,
"play": "pool_drink_credit"
}
Response
JSON
{ "ok": true }
Errors
| Status | Error | When |
|---|---|---|
| 400 | invalid_event | event_type, stay_id, or occurred_at is missing or empty. |
Notes
- Every event written through this route is stamped with source
make_it_rightand property slugsolara-cove. The body cannot override either. That stamp is what the recovery blocks inGET /v1/reportandGET /v1/property/summarycount, and it is what puts the Make-It-Right chip on the rendered line. - The route does not check that
stay_idexists. An event on an unknown stay is stored and then never rendered, because the feed drops any row whose stay join fails rather than printing a blank name. - Rendered lines carry first names, request types, offer labels, and money. They never carry a room number, booking code, tier, or federation reference.
- Some events are written and deliberately never rendered:
folio_skipped,pos_check_failed,offer_pending_approval, and anoffer_createdcarryingpending: true, which has not gone out yet and gets no line until the GM approves it. The full template map is at the top ofactivity.ts.