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 paramTypeRequiredMeaning
limitinteger as a stringoptionalLines to return. Must parse as an integer between 1 and 100. Defaults to 40.
before_idinteger as a stringoptionalCursor. 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
}
FieldTypeMeaning
items[].idnumberThe event row id. Also what you pass back as before_id.
items[].atstringThe event's occurred_at, verbatim.
items[].linestringThe rendered sentence. First names only.
items[].chipstring or nullProvenance label, when the line has one.
next_before_idnumber or nullCursor 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

StatusErrorWhen
400invalid limitlimit is not a whole number, or is below 1 or above 100.
400invalid before_idbefore_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
FieldTypeRequiredMeaning
event_typestringrequiredThe event name. friction_detected, recovery_sent, and recovery_claimed are the three the platform reads back.
stay_idstringrequiredThe stay the event belongs to. Not validated against the stays table.
occurred_atstringrequiredISO timestamp, stored verbatim.
any other fieldanyoptionalEverything 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

StatusErrorWhen
400invalid_eventevent_type, stay_id, or occurred_at is missing or empty.

Notes

  • Every event written through this route is stamped with source make_it_right and property slug solara-cove. The body cannot override either. That stamp is what the recovery blocks in GET /v1/report and GET /v1/property/summary count, and it is what puts the Make-It-Right chip on the rendered line.
  • The route does not check that stay_id exists. 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 an offer_created carrying pending: true, which has not gone out yet and gets no line until the GM approves it. The full template map is at the top of activity.ts.