Skip to content

SSE API Contract — Generic Events

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.


{
"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>" }
]
}
}
}
  • event_type (string, required) — Always data_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 } }.
  • Derive placeholder count from data.key.ids.length.
  • Placeholders are keyed by data.id to match with corresponding data_loaded events.
  • If navigation exists in an ID ref, clients may expose it as a tappable target.

{
"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 */ ]
}
}
  • event_type (string, required) — Always data_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 in data.key.ids[*].id.
  • Replace placeholders by matching the data.id field from the data_loading event.
  • If expected entries are missing, render an inline error tile.

{
"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_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 */ }
]
}
}

{
"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
}
}

  • SSE event size target: ≤ 256KB (TBD)
  • Timeouts: data_loaded should follow within 2s P50 / 5s P95 (TBD, observe in prod and tune)

  • 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.

event: data_loading
data: {"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_loaded
data: {"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":{}}]}}

  • Fields unknown to the client must be ignored.
  • New optional fields may be added without a version bump.
  • Breaking changes require a version increment.