Streaming

The OpenRouter API allows streaming responses from any model. This is useful for building chat interfaces or other applications where the UI should update as the model generates the response.

To enable streaming, you can set the stream parameter to true in your request. The model will then stream the response to the client in chunks, rather than returning the entire response at once.

Here is an example of how to stream a response, and process it:

1import requests
2import json
3
4question = "How would you build the tallest building ever?"
5
6url = "https://openrouter.ai/api/v1/chat/completions"
7headers = {
8 "Authorization": f"Bearer {{API_KEY_REF}}",
9 "Content-Type": "application/json"
10}
11
12payload = {
13 "model": "{{MODEL}}",
14 "messages": [{"role": "user", "content": question}],
15 "stream": True
16}
17
18buffer = ""
19with requests.post(url, headers=headers, json=payload, stream=True) as r:
20 for chunk in r.iter_content(chunk_size=1024, decode_unicode=True):
21 buffer += chunk
22 while True:
23 try:
24 # Find the next complete SSE line
25 line_end = buffer.find('\n')
26 if line_end == -1:
27 break
28
29 line = buffer[:line_end].strip()
30 buffer = buffer[line_end + 1:]
31
32 if line.startswith('data: '):
33 data = line[6:]
34 if data == '[DONE]':
35 break
36
37 try:
38 data_obj = json.loads(data)
39 content = data_obj["choices"][0]["delta"].get("content")
40 if content:
41 print(content, end="", flush=True)
42 except json.JSONDecodeError:
43 pass
44 except Exception:
45 break

Additional Information

For SSE (Server-Sent Events) streams, OpenRouter occasionally sends comments to prevent connection timeouts. These comments look like:

: OPENROUTER PROCESSING

Comment payload can be safely ignored per the SSE specs. However, you can leverage it to improve UX as needed, e.g. by showing a dynamic loading indicator.

Some SSE client implementations might not parse the payload according to spec, which leads to an uncaught error when you JSON.stringify the non-JSON payloads. We recommend the following clients:

Stream Cancellation

Streaming requests can be cancelled by aborting the connection. For supported providers, this immediately stops model processing and billing.

Supported

  • OpenAI, Azure, Anthropic
  • Fireworks, Mancer, Recursal
  • AnyScale, Lepton, OctoAI
  • Novita, DeepInfra, Together
  • Cohere, Hyperbolic, Infermatic
  • Avian, XAI, Cloudflare
  • SFCompute, Nineteen, Liquid
  • Friendli, Chutes, DeepSeek

Not Currently Supported

  • AWS Bedrock, Groq, Modal
  • Google, Google AI Studio, Minimax
  • HuggingFace, Replicate, Perplexity
  • Mistral, AI21, Featherless
  • Lynn, Lambda, Reflection
  • SambaNova, Inflection, ZeroOneAI
  • AionLabs, Alibaba, Nebius
  • Kluster, Targon, InferenceNet

To implement stream cancellation:

1import requests
2from threading import Event, Thread
3
4def stream_with_cancellation(prompt: str, cancel_event: Event):
5 with requests.Session() as session:
6 response = session.post(
7 "https://openrouter.ai/api/v1/chat/completions",
8 headers={"Authorization": f"Bearer {{API_KEY_REF}}"},
9 json={"model": "{{MODEL}}", "messages": [{"role": "user", "content": prompt}], "stream": True},
10 stream=True
11 )
12
13 try:
14 for line in response.iter_lines():
15 if cancel_event.is_set():
16 response.close()
17 return
18 if line:
19 print(line.decode(), end="", flush=True)
20 finally:
21 response.close()
22
23# Example usage:
24cancel_event = Event()
25stream_thread = Thread(target=lambda: stream_with_cancellation("Write a story", cancel_event))
26stream_thread.start()
27
28# To cancel the stream:
29cancel_event.set()

Cancellation only works for streaming requests with supported providers. For non-streaming requests or unsupported providers, the model will continue processing and you will be billed for the complete response.

Handling Errors During Streaming

OpenRouter handles errors differently depending on when they occur during the streaming process:

Errors Before Any Tokens Are Sent

If an error occurs before any tokens have been streamed to the client, OpenRouter returns a standard JSON error response with the appropriate HTTP status code. This follows the standard error format:

1{
2 "error": {
3 "code": 400,
4 "message": "Invalid model specified"
5 }
6}

Common HTTP status codes include:

  • 400: Bad Request (invalid parameters)
  • 401: Unauthorized (invalid API key)
  • 402: Payment Required (insufficient credits)
  • 429: Too Many Requests (rate limited)
  • 502: Bad Gateway (provider error)
  • 503: Service Unavailable (no available providers)

Errors After Tokens Have Been Sent (Mid-Stream)

If an error occurs after some tokens have already been streamed to the client, OpenRouter cannot change the HTTP status code (which is already 200 OK). Instead, the error is sent as a Server-Sent Event (SSE) with a unified structure:

data: {"id":"cmpl-abc123","object":"chat.completion.chunk","created":1234567890,"model":"gpt-3.5-turbo","provider":"openai","error":{"code":"server_error","message":"Provider disconnected unexpectedly"},"choices":[{"index":0,"delta":{"content":""},"finish_reason":"error"}]}

Key characteristics of mid-stream errors:

  • The error appears at the top level alongside standard response fields (id, object, created, etc.)
  • A choices array is included with finish_reason: "error" to properly terminate the stream
  • The HTTP status remains 200 OK since headers were already sent
  • The stream is terminated after this unified error event

Code Examples

Here’s how to properly handle both types of errors in your streaming implementation:

1import requests
2import json
3
4async def stream_with_error_handling(prompt):
5 response = requests.post(
6 'https://openrouter.ai/api/v1/chat/completions',
7 headers={'Authorization': f'Bearer {{API_KEY_REF}}'},
8 json={
9 'model': '{{MODEL}}',
10 'messages': [{'role': 'user', 'content': prompt}],
11 'stream': True
12 },
13 stream=True
14 )
15
16 # Check initial HTTP status for pre-stream errors
17 if response.status_code != 200:
18 error_data = response.json()
19 print(f"Error: {error_data['error']['message']}")
20 return
21
22 # Process stream and handle mid-stream errors
23 for line in response.iter_lines():
24 if line:
25 line_text = line.decode('utf-8')
26 if line_text.startswith('data: '):
27 data = line_text[6:]
28 if data == '[DONE]':
29 break
30
31 try:
32 parsed = json.loads(data)
33
34 # Check for mid-stream error
35 if 'error' in parsed:
36 print(f"Stream error: {parsed['error']['message']}")
37 # Check finish_reason if needed
38 if parsed.get('choices', [{}])[0].get('finish_reason') == 'error':
39 print("Stream terminated due to error")
40 break
41
42 # Process normal content
43 content = parsed['choices'][0]['delta'].get('content')
44 if content:
45 print(content, end='', flush=True)
46
47 except json.JSONDecodeError:
48 pass

API-Specific Behavior

Different API endpoints may handle streaming errors slightly differently:

  • OpenAI Chat Completions API: Returns ErrorResponse directly if no chunks were processed, or includes error information in the response if some chunks were processed
  • OpenAI Responses API: May transform certain error codes (like context_length_exceeded) into a successful response with finish_reason: "length" instead of treating them as errors