# Server Sent Events in OpenAPI best practices

# Server-sent events in OpenAPI

Server-sent events (SSE) allow servers to push real-time updates to clients over a single HTTP connection. This protocol is widely used for scenarios requiring steady updates, such as notifications, live data feeds, or chat applications. While SSE shares some conceptual similarities with the WebSocket protocol, SSE differs significantly as it is a one-way communication from the server to the client, whereas the WebSocket protocol supports full-duplex communication.

The following table summarizes the main differences between SSE and WebSockets.

SSE is ideal for simple, efficient, one-way server-to-client communication scenarios. The WebSocket protocol is better suited to applications that require interactive, low-latency, two-way communication between the client and server.

## Defining SSE in OpenAPI documents

<Callout title="OpenAPI v3.2.0 SSE support" type="info">
OpenAPI v3.2.0 introduces the `itemSchema` keyword for `text/event-stream` media types, providing first-class support for defining the structure of individual streamed events. See the [itemSchema section](#defining-sse-with-itemschema-in-openapi-32) below.
</Callout>

In OpenAPI v3.0 and v3.1, server-sent events (SSE) are not natively supported but can be represented as an endpoint using the `text/event-stream` MIME type to indicate the data format. OpenAPI v3.2.0 adds first-class support for SSE with the `itemSchema` keyword.

The event stream format is a UTF-8-encoded text stream with messages separated by a newline (`\n`). Each message may include up to four fields:

- `event`: A string specifying the event type.
- `data`: The payload, often a JSON object or plain text.
- `id`: An optional unique identifier for resuming streams after disconnection.
- `retry`: An optional integer defining reconnection delay in milliseconds.

Depending on application needs, messages can include only the `data` field, only the `event` field, or a combination of fields. This flexibility allows for tailored implementations, for example, a data-only stream for updates or an event-only stream for simple notifications.

This example SSE endpoint notifies the client about stock price updates and includes only the `id`, `event`, and `data` fields:

```yaml filename="openapi.yaml"
paths:
  /stock-updates:
    get:
      tags:
        - ServerSentEvents
      summary: Subscribe to real-time stock market updates
      description: >
        This endpoint streams real-time stock updates to the client using server-sent events (SSE).
        The client must establish a persistent HTTP connection to receive updates.
      responses:
        "200":
          description: Stream of real-time stock updates
          content:
            text/event-stream:
              schema:
                $ref: "#/components/schemas/StockStream"
        "400":
          description: Invalid request
        "500":
          description: Internal server error
components:
  schemas:
    StockStream:
      type: object
      description: A server-sent event containing stock market update content
      required: [id, event, data]
      properties:
        id:
          type: string
          description: Unique identifier for the stock update event
        event:
          type: string
          const: stock_update
          description: Event type
        data:
          $ref: "#/components/schemas/StockUpdate"

    StockUpdate:
      type: object
      properties:
        symbol:
          type: string
          description: Stock ticker symbol
        price:
          type: string
          description: Current stock price
          example: "100.25"
```

A JavaScript client can subscribe to the endpoint using the `EventSource` API:

```javascript
const eventSource = new EventSource("https:://api.example.com/stock-updates");

eventSource.onmessage = function (event) {
  // The event has the following format as example:
  // {"id":"1","event":"stock_update","data":{"symbol":"AAPL","price":"100.25"}}
  const stockUpdate = JSON.parse(event).data;
  console.log(
    `Stock Update: ${stockUpdate.symbol} is now ${stockUpdate.price}`,
  );
};

eventSource.onerror = function (error) {
  console.error("Error occurred:", error);
};
```

## Defining SSE with itemSchema in OpenAPI 3.2

OpenAPI v3.2.0 introduces the `itemSchema` keyword, which defines the schema for individual events within a `text/event-stream` response. Instead of describing the entire stream as one schema, `itemSchema` describes the structure of each event delivered over the stream.

```yaml filename="openapi.yaml"
paths:
  /stock-updates:
    get:
      tags:
        - ServerSentEvents
      summary: Subscribe to real-time stock market updates
      responses:
        "200":
          description: Stream of real-time stock updates
          content:
            text/event-stream:
              schema:
                type: string
              itemSchema:
                $ref: "#/components/schemas/StockUpdate"
components:
  schemas:
    StockUpdate:
      type: object
      properties:
        symbol:
          type: string
          description: Stock ticker symbol
        price:
          type: string
          description: Current stock price
          example: "100.25"
```

In this example, the `schema` describes the overall stream as a string (the raw event stream), while `itemSchema` defines the structure of each individual event within the stream. This separation allows tooling to generate typed handlers for each event.

## Best practices for SSE design and OpenAPI integration

Here are some best practices for handling server-sent events and including them in an OpenAPI document.

### Improve reliability with heartbeats

Sending a heartbeat every few seconds is recommended to improve reliability by keeping the connection alive. Heartbeats can also help detect network issues and prompt the client to reconnect.

If you implement heartbeats, your SSE APIs can send multiple types of events, allowing you to use the `oneOf` keyword to describe the heartbeat message format.

```yaml filename="openapi.yaml"
components:
  schemas:
    StockStream:
      oneOf:
        - $ref: "#/components/schemas/HeartbeatEvent"
        - $ref: "#/components/schemas/StockUpdateEvent"
      discriminator:
        propertyName: event
        mapping:
          ping: "#/components/schemas/HeartbeatEvent"
          stock_update: "#/components/schemas/StockUpdateEvent"

    HeartbeatEvent:
      description: A server-sent event indicating that the server is still processing the request
      type: object
      required: [event]
      properties:
        event:
          type: string
          const: "ping"
        timestamp:
          type: string
          format: date-time
          description: Timestamp of the heartbeat

    StockUpdateEvent:
      description: A server-sent event containing stock market update content
      type: object
      required: [id, event, data]
      properties:
        id:
          type: string
          description: Unique identifier for the stock update event
        event:
          type: string
          const: stock_update
```

### Include event identification in the event payload

Include an `id` or `sequence` property in the event payload to ensure that the client receives events in the correct order and avoid missing or out-of-order updates.

### Implement a retry mechanism

To prevent data loss when an API fails to send an event, implement a retry mechanism such as introducing a delay before retrying or using exponential backoff.

### Use sentinel events to signal a closed connection

Sending a sentinel event can be helpful to indicate that the connection is closed or the server is no longer available. This is useful for error handling or notifying the client that there is no more data to be received.

The following example schema for a sentinel event demonstrates how a client can terminate a connection when it receives the `CLOSED` sentinel event:

```yaml filename="openapi.yaml"
paths:
  /stock-updates:
    get:
      summary: Subscribe to real-time stock market updates
      operationId: stockUpdates
      tags:
        - ServerSentEvents
      responses:
        "200":
          description: Stream of real-time stock updates
          content:
            text/event-stream:
              x-speakeasy-sse-sentinel: "CLOSED" # Speakeasy extension for sentinel events
              schema:
                $ref: "#/components/schemas/StockUpdateEvent"
```

For each event received, the client can check the `X-SSE-Sentinel` header to determine whether the connection has closed or if no more data needs to be received.

### Handle SSE errors effectively

To handle errors in SSE effectively, servers can send specific error events within the stream that include an error field detailing the issue. For non-critical errors, custom headers like `X-SSE-Error` can communicate problems without interrupting the event flow.

Specific error events in the stream can be defined using a structured schema, as demonstrated below.

```yaml filename="openapi.yaml"
components:
  schemas:
    ErrorEvent:
      description: A server-sent error event
      type: object
      required: [event, message]
      properties:
        event:
          type: string
          const: error
        message:
          type: string
          description: Description of the error
```

For critical errors, use a sentinel event as described in the previous section.
