How Do You Stream API Responses Using Server-Sent Events (SSE)?

Server-Sent Events let you stream API responses in real-time. Learn how to implement SSE for live updates, AI streaming, and progress tracking with Modern PetstoreAPI examples.

Ashley Innocent

Ashley Innocent

13 March 2026

How Do You Stream API Responses Using Server-Sent Events (SSE)?

TL;DR

Use Server-Sent Events (SSE) to stream API responses over HTTP. Send Content-Type: text/event-stream and write events as data: {json}\n\n. SSE works for AI response streaming, progress updates, and live feeds. Modern PetstoreAPI uses SSE for AI pet recommendations and order status updates.

Introduction

Your API generates AI recommendations for pets. The response takes 10 seconds. Do you make users wait, or stream results as they’re generated?

With Server-Sent Events (SSE), you stream responses in real-time. Users see results immediately as the AI generates them, creating a better experience.

Modern PetstoreAPI uses SSE for AI pet recommendations, order status updates, and inventory changes.

If you’re testing streaming APIs, Apidog supports SSE testing and validation.

button

SSE Basics

SSE is HTTP-based one-way streaming from server to client.

SSE Format

Content-Type: text/event-stream
Cache-Control: no-cache

data: {"message":"First chunk"}\n\n
data: {"message":"Second chunk"}\n\n
data: {"message":"Third chunk"}\n\n

Each event:

Named Events

event: recommendation
data: {"petId":"019b4132","score":0.95}

event: recommendation
data: {"petId":"019b4127","score":0.89}

event: complete
data: {"total":2}

Event IDs

id: 1
data: {"message":"First"}

id: 2
data: {"message":"Second"}

Client can resume from last ID if disconnected.

Implementing SSE Server

Node.js/Express Example

app.get('/v1/pets/recommendations/stream', async (req, res) => {
  // Set SSE headers
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  // Send recommendations as they're generated
  const recommendations = await generateRecommendations(req.query.userId);

  for (const rec of recommendations) {
    res.write(`data: ${JSON.stringify(rec)}\n\n`);
    await sleep(100); // Simulate streaming delay
  }

  // Send completion event
  res.write(`event: complete\ndata: {"total":${recommendations.length}}\n\n`);
  res.end();
});

Python/FastAPI Example

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import json
import asyncio

app = FastAPI()

@app.get("/v1/pets/recommendations/stream")
async def stream_recommendations(user_id: str):
    async def generate():
        recommendations = await get_recommendations(user_id)

        for rec in recommendations:
            yield f"data: {json.dumps(rec)}\n\n"
            await asyncio.sleep(0.1)

        yield f"event: complete\ndata: {json.dumps({'total': len(recommendations)})}\n\n"

    return StreamingResponse(
        generate(),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive"
        }
    )

SSE Client Implementation

JavaScript/Browser

const eventSource = new EventSource(
  'https://petstoreapi.com/v1/pets/recommendations/stream?userId=user-456'
);

eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  displayRecommendation(data);
};

eventSource.addEventListener('complete', (event) => {
  const data = JSON.parse(event.data);
  console.log(`Received ${data.total} recommendations`);
  eventSource.close();
});

eventSource.onerror = (error) => {
  console.error('SSE error:', error);
  eventSource.close();
};

React Hook

import { useEffect, useState } from 'react';

function useSSE(url) {
  const [data, setData] = useState([]);
  const [complete, setComplete] = useState(false);

  useEffect(() => {
    const eventSource = new EventSource(url);

    eventSource.onmessage = (event) => {
      const item = JSON.parse(event.data);
      setData(prev => [...prev, item]);
    };

    eventSource.addEventListener('complete', () => {
      setComplete(true);
      eventSource.close();
    });

    return () => eventSource.close();
  }, [url]);

  return { data, complete };
}

// Usage
function Recommendations({ userId }) {
  const { data, complete } = useSSE(
    `https://petstoreapi.com/v1/pets/recommendations/stream?userId=${userId}`
  );

  return (
    <div>
      {data.map(rec => (
        <PetCard key={rec.petId} pet={rec} />
      ))}
      {!complete && <Spinner />}
    </div>
  );
}

How Modern PetstoreAPI Uses SSE

AI Pet Recommendations

Stream AI-generated recommendations:

GET /v1/pets/recommendations/stream?userId=user-456
Accept: text/event-stream

event: recommendation
data: {"petId":"019b4132","name":"Fluffy","score":0.95,"reason":"Matches your preference for cats"}

event: recommendation
data: {"petId":"019b4127","name":"Buddy","score":0.89,"reason":"Similar to pets you liked"}

event: complete
data: {"total":2,"processingTime":850}

Order Status Updates

Stream order processing steps:

GET /v1/orders/019b4132/status/stream
Accept: text/event-stream

data: {"status":"payment_processing","timestamp":"2026-03-13T10:30:00Z"}

data: {"status":"payment_confirmed","timestamp":"2026-03-13T10:30:02Z"}

data: {"status":"preparing_shipment","timestamp":"2026-03-13T10:30:05Z"}

event: complete
data: {"status":"shipped","trackingNumber":"1Z999AA10123456784"}

Inventory Changes

Stream real-time inventory updates:

GET /v1/inventory/stream
Accept: text/event-stream

event: stock-change
data: {"petId":"019b4132","oldStock":5,"newStock":4}

event: price-change
data: {"petId":"019b4127","oldPrice":299.99,"newPrice":279.99}

See Modern PetstoreAPI SSE docs.

Testing SSE with Apidog

Apidog supports SSE testing:

  1. Create SSE request
  2. Set Accept: text/event-stream
  3. Connect and view events in real-time
  4. Validate event format
  5. Test reconnection

Best Practices

1. Set Proper Headers

res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.setHeader('X-Accel-Buffering', 'no'); // Disable nginx buffering

2. Send Heartbeats

Keep connection alive:

const heartbeat = setInterval(() => {
  res.write(': heartbeat\n\n');
}, 15000);

res.on('close', () => clearInterval(heartbeat));

3. Handle Errors Gracefully

eventSource.onerror = (error) => {
  if (eventSource.readyState === EventSource.CLOSED) {
    // Connection closed, will auto-reconnect
  } else {
    // Other error
    console.error('SSE error:', error);
  }
};

4. Use Event IDs for Resume

let lastEventId = 0;

app.get('/stream', (req, res) => {
  const startId = parseInt(req.headers['last-event-id'] || '0');

  for (let i = startId + 1; i <= 100; i++) {
    res.write(`id: ${i}\ndata: {"message":"Event ${i}"}\n\n`);
  }
});

5. Close Connections

// Client
eventSource.addEventListener('complete', () => {
  eventSource.close();
});

// Server
res.on('close', () => {
  // Cleanup resources
});

Conclusion

SSE is perfect for streaming API responses. It’s simpler than WebSocket for one-way communication, works over HTTP, and handles reconnection automatically.

Modern PetstoreAPI uses SSE for AI streaming, order updates, and live feeds. Test SSE endpoints with Apidog.

FAQ

Can SSE work through corporate firewalls?

Yes, SSE uses standard HTTP/HTTPS, so it works through most firewalls and proxies.

How long can SSE connections stay open?

Indefinitely, but use heartbeats every 15-30 seconds to keep connections alive through proxies.

Can I send binary data over SSE?

No, SSE is text-only. Base64-encode binary data or use WebSocket instead.

Does SSE support bidirectional communication?

No, SSE is server-to-client only. Clients use regular HTTP requests for client-to-server communication.

How many SSE connections can a browser have?

Browsers limit SSE connections per domain (typically 6). Use multiplexing or WebSocket for many connections.

Explore more

HappyHorse-1.0 vs Seedance 2.0: which AI video model wins right now?

HappyHorse-1.0 vs Seedance 2.0: which AI video model wins right now?

HappyHorse-1.0 leads on visual quality benchmarks (T2V Elo 1333 vs Seedance 2.0’s 1273) but has no stable API and no consumer access. Seedance 2.0 has a ByteDance backing, consumer access via Dreamina, and leads on audio generation

10 April 2026

Best free AI face swapper in 2026: no signup options, API access, ethical use

Best free AI face swapper in 2026: no signup options, API access, ethical use

The best free AI face swappers in 2026 are WaveSpeedAI (no-signup web tool, full REST API, consent-first design), Reface (mobile app), DeepFaceLab (open source desktop), Akool (API-ready), and Vidnoz (web-based).

10 April 2026

How to use Google Genie 3: interface walkthrough, generation tips, and what to expect

How to use Google Genie 3: interface walkthrough, generation tips, and what to expect

Google Genie 3 is a sketch-to-video model in limited research access as of early 2026. Access is through experimental demos and select partner pilots, not a public API.

10 April 2026

Practice API Design-first in Apidog

Discover an easier way to build and use APIs