Skip to content
SingularSingular

Streaming, errors, and retries

Parse Singular's Chat Completions SSE stream correctly and retry only failures that can reasonably recover.

On this page

Streaming request

Set stream: true on Chat Completions:

bash
curl -N "$OPENAI_BASE_URL/chat/completions" \
  -H "Authorization: Bearer $SINGULAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "auto",
    "stream": true,
    "messages": [{"role": "user", "content": "Count to three."}]
  }'

The response is a data-only Server-Sent Events stream. Each payload is a Chat Completions chunk; partial text is under choices[].delta.content. A normal stream ends with:

text
data: [DONE]

stream_options is not part of Singular's stable forwarded parameter subset. Do not require OpenAI's optional final usage-only chunk.

JavaScript stream

javascript
const stream = await client.chat.completions.create({
  model: "auto",
  messages: [{ role: "user", content: "Count to three." }],
  stream: true,
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}

Python stream

python
stream = client.chat.completions.create(
    model="auto",
    messages=[{"role": "user", "content": "Count to three."}],
    stream=True,
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content is not None:
        print(chunk.choices[0].delta.content, end="")

Error envelope

Singular-generated API errors commonly use this envelope:

json
{
  "status": "failure",
  "code": "too_many_requests",
  "message": "Too many requests",
  "retryAfterSeconds": 30
}

Sanitized upstream failures can instead preserve an OpenAI-shaped {"error":{"message":"...","type":"...","code":"..."}} object, while some middleware validation failures use the shorter {"status":"failure","message":"..."} form. Clients must branch on HTTP status first and tolerate either family, a missing code, or a short 400 body.

HTTP statusMeaningRetry?
400 / 422Invalid request or unsupported shapeNo; fix the request.
401Missing or invalid keyNo; replace credentials.
402Payment or balance action requiredNo blind retry; satisfy the advertised challenge or fund the key.
403Revoked key or authenticated caller not permittedNo; replace the key or change policy/balance.
404Unknown resource or unavailable surfaceNo.
408Request timeoutUsually, with a bound.
429Rate limitedYes; honor Retry-After.
5xxSingular service or upstream failureUsually, with exponential backoff and jitter.

A 401 response includes WWW-Authenticate: Bearer. A 429 may include Retry-After; the gateway supplies a fallback value when a more specific limit does not.

Retry pattern

python
import random
import time
from openai import APIStatusError, RateLimitError

def call_with_retry(call, attempts=5):
    for attempt in range(attempts):
        try:
            return call()
        except (RateLimitError, APIStatusError) as error:
            status = getattr(error, "status_code", None)
            if status not in (408, 429, 500, 502, 503, 504) or attempt == attempts - 1:
                raise
            time.sleep((2 ** attempt) + random.random())

Use an application-level idempotency strategy before retrying workflows that trigger side effects outside the model call.

Updated 2026-08-10. Live model availability, rates, account state, and payment rails remain request-time data.