Background mode
On this page
A third path between synchronous and batch
The Responses API’s background mode fills a specific niche in your toolkit. A synchronous call gives you the answer immediately but forces you to keep the connection open for the entire generation. The Batch API processes thousands of requests at half the price, but results take hours. Background mode sits between them: it handles individual requests at the standard rate without an open connection. You submit the request, receive an ID, and retrieve the result when it’s ready.
This compromise targets a precise use case: a single request that takes too long for a blocking call but can’t wait until tomorrow. This is exactly the scenario when running a high-reasoning model on a complex document.
Launching and retrieving
Activation requires just one parameter. The immediate response contains no text; it returns an ID and a status, typically queued or in_progress.
import openai
client = openai.OpenAI()
# Launch the request in background
response = client.responses.create(
model="gpt-5.6-sol", # Ideal for slow tasks (high reasoning)
input="Analyze the economic implications of European AI regulation "
"for French SMEs. Propose a 10-point action plan.",
background=True, # Background mode
)
print(f"Response ID: {response.id}")
print(f"Status: {response.status}") # "queued" or "in_progress"
This ID is your only link to the work in progress: persist it before doing anything else. Losing it means paying for a generation you’ll never retrieve. Retrieval then involves polling the API until the status becomes terminal, handling failure and cancellation separately since they have different business consequences.
import time
def wait_for_response(response_id: str, interval: int = 5) -> str:
"""Waits for a background response to complete."""
while True:
response = client.responses.retrieve(response_id)
match response.status:
case "completed":
return response.output_text
case "failed":
raise RuntimeError(
f"Request failed: {response.error}"
)
case "cancelled":
raise RuntimeError("Request was cancelled.")
case _:
print(f"Status: {response.status}...")
time.sleep(interval)
Long reasoning tasks
A model like GPT-5.6 Sol with high reasoning enabled can take 30 seconds to two minutes before producing its first visible token. In a synchronous call, these durations hit default timeouts for HTTP clients, proxies, and gateways. Background mode eliminates this problem at the root and incidentally lets you launch several heavy analyses without tying up as many connections.
async def deep_analysis(document: str) -> str:
"""Launches an analysis with GPT-5.6 Sol without blocking."""
response = client.responses.create(
model="gpt-5.6-sol",
input=f"Perform a thorough legal analysis of this contract. "
f"Identify all potential risks, problematic clauses, "
f"and propose modifications.\n\n{document}",
background=True,
)
return response.id
# Launch multiple analyses in parallel
task_ids = []
for doc in documents:
task_id = await deep_analysis(doc)
task_ids.append(task_id)
# Retrieve results later
results = {}
for task_id in task_ids:
results[task_id] = wait_for_response(task_id)
This pattern naturally extends to webhooks when the caller is another service. Your endpoint launches the request, returns the ID, and delegates a background task to monitor completion and send notifications. The caller doesn’t poll; it gets notified.
from fastapi import FastAPI, BackgroundTasks
import httpx
app = FastAPI()
@app.post("/api/long-analysis")
async def launch_analysis(request: dict, background_tasks: BackgroundTasks):
"""Launches an analysis and notifies via webhook when done."""
response = client.responses.create(
model="gpt-5.6-sol",
input=request["prompt"],
background=True,
)
# Monitor in background
background_tasks.add_task(
monitor_and_notify,
response_id=response.id,
webhook_url=request["webhook_url"],
)
return {"response_id": response.id, "status": "launched"}
async def monitor_and_notify(response_id: str, webhook_url: str):
"""Monitors a background request and notifies via webhook."""
result = wait_for_response(response_id)
async with httpx.AsyncClient() as http:
await http.post(webhook_url, json={
"response_id": response_id,
"result": result,
})
Choosing the right mode
The decision rule comes down to two questions. How many requests? And is the user waiting? Beyond a hundred requests with no immediacy requirement, the Batch API wins on price: results in hours at half the cost. If someone is waiting at their screen, synchronous calls remain the only acceptable choice at the standard rate. Background mode fills the rest of the space: few requests, results in minutes, standard price. It pays to codify this logic once rather than rehash it for every new service.
def choose_mode(
num_requests: int,
urgency: str,
budget: str,
) -> str:
"""Recommends the optimal execution mode."""
if num_requests > 100 and urgency != "immediate":
return "batch"
elif urgency == "immediate":
return "synchronous"
else:
return "background"
Finally, plan an escape hatch. A background request that far exceeds its expected duration ties up budget and delays the rest of your pipeline; the API lets you cancel it explicitly. Set this ceiling at double the duration you observe in practice, not the average, or you risk cancelling requests that were about to succeed.
def cancel_if_too_long(response_id: str, timeout: int = 120):
"""Cancels a request if it exceeds the timeout."""
start = time.time()
while time.time() - start < timeout:
response = client.responses.retrieve(response_id)
if response.status == "completed":
return response.output_text
time.sleep(5)
# Timeout exceeded — cancel
client.responses.cancel(response_id)
raise TimeoutError(
f"Request {response_id} cancelled after {timeout}s."
)
Key takeaways
- Background mode launches requests without keeping a connection open
- Ideal for high reasoning settings (GPT-5.6 Sol) and long tasks
- Combine with a webhook for an event-driven architecture
- Persist the response ID: it’s your only link to the work in progress
- For low-cost bulk processing, prefer the Batch API