WebSocket Protocol
JAOT provides a WebSocket endpoint for real-time execution monitoring. Connect to receive live progress updates -- iteration count, objective value, MIP gap -- as the solver works on your problem. This is ideal for building progress bars, live dashboards, and responsive UIs.
Info: WebSocket monitoring is optional. If your environment does not support WebSockets, use
GET /api/v2/solve/async/{task_id}for simple HTTP polling instead. See the Executions page for polling details.
Connection URL
ws://localhost:8001/api/v2/ws/executions/{execution_id}
For production deployments, use the secure variant:
wss://api.jaot.io/api/v2/ws/executions/{execution_id}
The execution_id can be either:
- A Celery task ID returned from
POST /api/v2/solve/async - A
ModelExecutiondatabase ID (e.g.,exe_abc123)
Connection flow
- Start an async solve --
POST /api/v2/solve/asyncreturns atask_idandws_url - Connect WebSocket -- open a connection to the
ws_url - Receive initial status -- server sends the current execution state immediately
- Receive progress updates -- server pushes updates every ~5 seconds while the solver runs
- Receive final result -- server sends
completed,failed, orcancelledand closes the connection
Client Server
| |
| POST /api/v2/solve/async |
|------------------------------------>|
| {"task_id": "abc-123", "ws_url": |
| "/api/v2/ws/executions/abc-123"} |
|<------------------------------------|
| |
| WebSocket connect |
|------------------------------------>|
| {"type":"status","status":"pending"}|
|<------------------------------------|
| {"type":"progress","progress":0.2} |
|<------------------------------------|
| {"type":"progress","progress":0.6} |
|<------------------------------------|
| {"type":"completed","result":{...}}|
|<------------------------------------|
| Connection closed |
Message types
All messages are JSON objects with a type field.
Server-to-client messages
| Type | Description | Terminal |
|---|---|---|
status | Initial status on connection | No |
progress | Periodic solve progress update | No |
completed | Solve finished successfully | Yes |
failed | Solve encountered an error | Yes |
cancelled | Solve was cancelled | Yes |
error | Connection error (e.g., invalid execution ID) | Yes |
Client-to-server messages
| Message | Description |
|---|---|
"ping" | Keepalive. Server responds with "pong" |
Send ping messages every 30 seconds to prevent connection timeout on long-running solves.
Status message
Sent immediately after connection. Provides the current state of the execution.
{
"type": "status",
"execution_id": "abc-123",
"status": "pending",
"progress_data": null
}| Field | Type | Description |
|---|---|---|
type | string | Always "status" |
execution_id | string | The execution being monitored |
status | string | Current status: pending, running, completed, failed |
progress_data | object | Latest progress data (null if not yet running) |
Progress message
Sent approximately every 5 seconds while the solver is running. Contains real-time solver metrics.
{
"type": "progress",
"execution_id": "abc-123",
"status": "running",
"progress": 0.45,
"message": "Iteration 234: obj=1234.56, gap=2.3%",
"iteration": 234,
"objective_value": 1234.56,
"gap": 0.023,
"timestamp": "2026-02-19T10:05:30Z"
}| Field | Type | Description |
|---|---|---|
progress | float | Estimated completion (0.0 to 1.0) |
message | string | Human-readable status message |
iteration | int | Current solver iteration count |
objective_value | float | Best objective value found so far |
gap | float | Current MIP gap (0 = proven optimal) |
timestamp | string | ISO 8601 timestamp of this update |
Completed message
Sent when the solve finishes successfully. Contains the full solution. The server closes the connection after sending this message.
{
"type": "completed",
"execution_id": "abc-123",
"status": "completed",
"result": {
"status": "optimal",
"objective_value": 3500.0,
"solution": {"widgets": 30, "gadgets": 60},
"solve_time_seconds": 12.3,
"credits_used": 3,
"credits_remaining": 97
}
}Failed message
Sent when the solve encounters an error. The server closes the connection after sending this message.
{
"type": "failed",
"execution_id": "abc-123",
"status": "failed",
"error": "Solver error: problem is infeasible"
}Cancelled message
Sent when the task is cancelled via POST /api/v2/solve/async/{task_id}/cancel.
{
"type": "cancelled",
"execution_id": "abc-123",
"status": "cancelled",
"result": null
}Error message
Sent when the execution ID is not found. The server closes the connection immediately.
{
"type": "error",
"message": "Execution abc-123 not found"
}Complete examples
Full working examples showing how to start an async solve, connect via WebSocket, and receive real-time progress updates.
import asyncio
import json
import aiohttp
API_KEY = "ok_live_..."
BASE_URL = "https://api.jaot.io"
WS_BASE = "wss://api.jaot.io"
async def solve_with_realtime_progress(problem: dict):
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
async with aiohttp.ClientSession() as session:
# 1. Start async solve
async with session.post(
f"{BASE_URL}/api/v2/solve/async",
headers=headers,
json=problem,
) as resp:
data = await resp.json()
task_id = data["task_id"]
print(f"Solve started: {task_id}")
# 2. Connect WebSocket for real-time updates
ws_url = f"{WS_BASE}/api/v2/ws/executions/{task_id}"
async with session.ws_connect(ws_url) as ws:
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
data = json.loads(msg.data)
msg_type = data.get("type")
if msg_type == "status":
print(f"Initial status: {data['status']}")
elif msg_type == "progress":
pct = data.get("progress", 0) * 100
obj = data.get("objective_value", "?")
gap = data.get("gap", "?")
print(f"Progress: {pct:.0f}% | Objective: {obj} | Gap: {gap}")
elif msg_type == "completed":
result = data["result"]
print(f"\nSolve completed!")
print(f"Status: {result['status']}")
print(f"Objective: {result['objective_value']}")
print(f"Solution: {result['solution']}")
print(f"Time: {result['solve_time_seconds']}s")
print(f"Credits used: {result['credits_used']}")
break
elif msg_type == "failed":
print(f"Solve failed: {data['error']}")
break
elif msg.type == aiohttp.WSMsgType.ERROR:
print(f"WebSocket error: {ws.exception()}")
break
# Example usage
problem = {
"name": "production_planning",
"objective": {
"sense": "maximize",
"expression": "50*widgets + 40*gadgets",
},
"variables": [
{"name": "widgets", "type": "integer", "lower_bound": 0, "upper_bound": 100},
{"name": "gadgets", "type": "integer", "lower_bound": 0, "upper_bound": 80},
],
"constraints": [
{"name": "machine_hours", "expression": "2*widgets + 3*gadgets <= 240"},
{"name": "labor_hours", "expression": "4*widgets + 2*gadgets <= 200"},
],
}
asyncio.run(solve_with_realtime_progress(problem))Error handling and reconnection
Connection drops
WebSocket connections can drop due to network issues, server restarts, or timeouts. Handle disconnections gracefully:
import asyncio
import aiohttp
import json
async def resilient_monitor(task_id: str, max_retries: int = 3):
ws_url = f"wss://api.jaot.io/api/v2/ws/executions/{task_id}"
retries = 0
while retries < max_retries:
try:
async with aiohttp.ClientSession() as session:
async with session.ws_connect(ws_url) as ws:
retries = 0 # Reset on successful connect
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
data = json.loads(msg.data)
if data["type"] in ("completed", "failed", "cancelled"):
return data # Terminal state -- done
print(f"Progress: {data.get('progress', 0):.0%}")
except (aiohttp.ClientError, ConnectionError) as e:
retries += 1
wait = min(2 ** retries, 30) # Exponential backoff, max 30s
print(f"Connection lost ({e}). Reconnecting in {wait}s...")
await asyncio.sleep(wait)
# Fallback to HTTP polling
print("WebSocket reconnection failed. Falling back to HTTP polling.")
return await poll_for_result(task_id)Timeout behavior
- The server keeps the connection open as long as the solve is running
- Send
"ping"messages every 30 seconds to prevent proxy/load balancer timeouts - The server responds to
"ping"with"pong" - If no messages are received for 60+ seconds, the connection may be closed by intermediate proxies
Multiple clients
Multiple clients can connect to the same execution_id simultaneously. All connected clients receive the same progress broadcasts.
Fallback: HTTP polling
If WebSockets are unavailable in your environment, poll the async status endpoint instead:
import time
import requests
def poll_for_result(task_id: str, api_key: str, interval: float = 3.0):
"""Poll for solve result via HTTP (WebSocket fallback)."""
url = f"https://api.jaot.io/api/v2/solve/async/{task_id}"
headers = {"Authorization": f"Bearer {api_key}"}
while True:
response = requests.get(url, headers=headers)
data = response.json()
status = data["status"]
if status == "completed":
return data["result"]
elif status == "failed":
raise Exception(f"Solve failed: {data.get('error')}")
elif status == "running":
progress = data.get("progress", 0)
print(f"Running... {progress:.0%}")
time.sleep(interval)Recommended polling interval: 2--5 seconds.