SSE API Contract — Generic Events
SSE API Contract — Generic Events
Section titled “SSE API Contract — Generic Events”Purpose
Section titled “Purpose”Define generic SSE events for streamed UX, then specialize them for specific UX data. Backends emit a lightweight data_loading event (to render placeholders immediately), followed by a data_loaded event containing the full payload.
Generic Event: data_loading
Section titled “Generic Event: data_loading”{ "event_type": "data_loading", "version": "0.4", "timestamp": "2025-09-03T15:04:05.000Z", "response_id": "<consistent id for the chat session>", "data": { "id": "<unique id for this data object>", "type": "<data type>", "key": { "ids": [ { "id": "<id>", "navigation": { "deeplink": "<app://...>", "web": "<https://...>" } }, { "id": "<id>" } ] } }}Field definitions
Section titled “Field definitions”event_type(string, required) — Alwaysdata_loading.version(string, required) — Schema version.timestamp(string, required) — ISO-8601 timestamp when emitted.response_id(string, required) — Consistent ID for the entire chat session, matching the OpenAI response ID.data(object, required) — Data container object.id(string, required) — Unique identifier for this data object. Used to correlate data_loading with its corresponding data_loaded event.type(string, required) — Domain object type (e.g.,offer_list,offer,retailer,product).key(object, required) — Selector for the data being loaded.ids(array, optional) — Ordered list of ID refs. Each ID ref is an object:{ id: string, navigation?: { deeplink?: string, web?: string } }.
Client behavior
Section titled “Client behavior”- Derive placeholder count from
data.key.ids.length. - Placeholders are keyed by
data.idto match with corresponding data_loaded events. - If
navigationexists in an ID ref, clients may expose it as a tappable target.
Generic Event: data_loaded
Section titled “Generic Event: data_loaded”{ "event_type": "data_loaded", "version": "0.4", "timestamp": "2025-09-03T15:04:05.000Z", "response_id": "<consistent id for the chat session>", "data": { "id": "<unique id for this data object>", "type": "<data type>", "key": { "ids": [ { "id": "<id>", "navigation": { "deeplink": "<app://...>", "web": "<https://...>" } } ] }, "items": [ /* ordered results; shape depends on type */ ] }}Field definitions
Section titled “Field definitions”event_type(string, required) — Alwaysdata_loaded.version(string, required) — Schema version.timestamp(string, required) — ISO-8601 timestamp when emitted.response_id(string, required) — Consistent ID for the entire chat session, matching the OpenAI response ID.data(object, required) — Data container object.id(string, required) — Same unique identifier as the corresponding data_loading event.type(string, required) — Domain object type (e.g.,offer_list).key(object, required) — Echo of the requested selector for integrity checks.items(array, required) — Render-ready payload. Each item should match one of the IDs indata.key.ids[*].id.
Client behavior
Section titled “Client behavior”- Replace placeholders by matching the
data.idfield from the data_loading event. - If expected entries are missing, render an inline error tile.
Specialization: offer_list
Section titled “Specialization: offer_list”data_loading — offer_list
Section titled “data_loading — offer_list”{ "event_type": "data_loading", "version": "0.4", "timestamp": "2025-09-03T15:04:05.000Z", "response_id": "chatcmpl-ABC123xyz", "data": { "id": "offer-list-abc123", "type": "offer_list", "key": { "ids": [ { "id": "OFF_123", "navigation": { "deeplink": "app://offer/OFF_123" } }, { "id": "OFF_456", "navigation": { "deeplink": "app://offer/OFF_456" } }, { "id": "OFF_789" } ] } }}data_loaded — offer_list
Section titled “data_loaded — offer_list”{ "event_type": "data_loaded", "version": "0.4", "timestamp": "2025-09-03T15:04:05.000Z", "response_id": "chatcmpl-ABC123xyz", "data": { "id": "offer-list-abc123", "type": "offer_list", "key": { "ids": [ { "id": "OFF_123", "navigation": { "deeplink": "app://offer/OFF_123" } }, { "id": "OFF_456", "navigation": { "deeplink": "app://offer/OFF_456" } }, { "id": "OFF_789" } ] }, "items": [ { "id": "OFF_123", /* Offer BFF object — TBD */ }, { "id": "OFF_456", /* Offer BFF object — TBD */ }, { "id": "OFF_789", /* Offer BFF object — TBD */ } ] }}Error & Retry (initial conventions)
Section titled “Error & Retry (initial conventions)”{ "event_type": "data_loaded", "version": "0.4", "timestamp": "2025-09-03T15:04:05.000Z", "response_id": "chatcmpl-ABC123xyz", "data": { "id": "offer-list-abc123", "type": "offer_list", "key": { "ids": [ { "id": "OFF_123" }, { "id": "OFF_456" }, { "id": "OFF_789" } ] } }, "error": { "code": "UPSTREAM_ERROR", "retry_after_ms": 500 }}Constraints (initial)
Section titled “Constraints (initial)”- SSE event size target: ≤ 256KB (TBD)
- Timeouts:
data_loadedshould follow within 2s P50 / 5s P95 (TBD, observe in prod and tune)
UI Guidelines (on-brand flair)
Section titled “UI Guidelines (on-brand flair)”- Use shimmer skeletons matching the final card size (small/large).
- Animate staggered opacity-in when replacing placeholders.
- Keep layout stable (reserve space via
data.key.ids.length). - Surface a retry CTA on error tiles.
SSE Wire Example
Section titled “SSE Wire Example”event: data_loadingdata: {"event_type":"data_loading","version":"0.4","timestamp":"2025-09-03T15:04:05.000Z","response_id":"chatcmpl-ABC123xyz","data":{"id":"offer-list-abc123","type":"offer_list","key":{"ids":[{"id":"OFF_123","navigation":{"deeplink":"app://offer/OFF_123"}},{"id":"OFF_456","navigation":{"deeplink":"app://offer/OFF_456"}},{"id":"OFF_789"}]}}}
event: data_loadeddata: {"event_type":"data_loaded","version":"0.4","timestamp":"2025-09-03T15:04:05.000Z","response_id":"chatcmpl-ABC123xyz","data":{"id":"offer-list-abc123","type":"offer_list","key":{"ids":[{"id":"OFF_123","navigation":{"deeplink":"app://offer/OFF_123"}},{"id":"OFF_456","navigation":{"deeplink":"app://offer/OFF_456"}},{"id":"OFF_789"}]},"items":[{"id":"OFF_123","bff":{}},{"id":"OFF_456","bff":{}},{"id":"OFF_789","bff":{}}]}}Compatibility
Section titled “Compatibility”- Fields unknown to the client must be ignored.
- New optional fields may be added without a version bump.
- Breaking changes require a
versionincrement.