diff --git a/openhands/usage/cloud/cloud-api.mdx b/openhands/usage/cloud/cloud-api.mdx
index c8e7bb6d..6229e336 100644
--- a/openhands/usage/cloud/cloud-api.mdx
+++ b/openhands/usage/cloud/cloud-api.mdx
@@ -127,6 +127,7 @@ The `status` field indicates the current state of the conversation startup proce
- `WORKING` - Initial processing
- `WAITING_FOR_SANDBOX` - Waiting for sandbox to be ready
- `PREPARING_REPOSITORY` - Cloning and setting up the repository
+- `SETTING_UP_SKILLS` - Configuring agent skills and tools
- `READY` - Conversation is ready to use
- `ERROR` - An error occurred during startup
@@ -167,6 +168,272 @@ The endpoint streams a JSON array incrementally. Each element represents a statu
Each update is streamed as it occurs, allowing you to provide real-time feedback to users about the conversation startup progress.
+### Checking Conversation Status
+
+After starting a conversation, you can check its status to monitor whether the agent has completed its task.
+
+
+ The examples below show basic polling patterns. For production use, add proper error handling,
+ exponential backoff, and handle network failures gracefully.
+
+
+#### Step 1: Check Start Task Status
+
+When you start a conversation, you receive a start task ID. Poll this endpoint until `status` becomes `READY` and `app_conversation_id` is available:
+
+```bash
+curl -X GET "https://app.all-hands.dev/api/v1/app-conversations/start-tasks?ids=TASK_ID" \
+ -H "Authorization: Bearer YOUR_API_KEY"
+```
+
+**Response:**
+```json
+{
+ "id": "550e8400-e29b-41d4-a716-446655440000",
+ "status": "READY",
+ "app_conversation_id": "660e8400-e29b-41d4-a716-446655440001",
+ "sandbox_id": "sandbox-abc123"
+}
+```
+
+#### Step 2: Check Conversation Execution Status
+
+Once you have the `app_conversation_id`, check whether the agent has finished its task:
+
+
+
+ ```bash
+ curl -X GET "https://app.all-hands.dev/api/v1/app-conversations?ids=CONVERSATION_ID" \
+ -H "Authorization: Bearer YOUR_API_KEY"
+ ```
+
+
+ ```python
+ import requests
+
+ api_key = "YOUR_API_KEY"
+ conversation_id = "YOUR_CONVERSATION_ID"
+
+ headers = {
+ "Authorization": f"Bearer {api_key}",
+ "Content-Type": "application/json"
+ }
+
+ response = requests.get(
+ "https://app.all-hands.dev/api/v1/app-conversations",
+ headers=headers,
+ params={"ids": conversation_id}
+ )
+ response.raise_for_status() # Raise exception for HTTP errors
+ conversations = response.json()
+
+ if conversations:
+ conv = conversations[0]
+ print(f"Sandbox Status: {conv.get('sandbox_status')}")
+ print(f"Execution Status: {conv.get('execution_status')}")
+ else:
+ print("Conversation not found")
+ ```
+
+
+
+**Response:**
+```json
+[
+ {
+ "id": "660e8400-e29b-41d4-a716-446655440001",
+ "sandbox_status": "RUNNING",
+ "execution_status": "finished",
+ "selected_repository": "yourusername/your-repo",
+ "title": "Fix README"
+ }
+]
+```
+
+#### Status Fields
+
+**`sandbox_status`** - The state of the sandbox environment:
+- `STARTING` - Sandbox is being created. **Action:** Continue polling.
+- `RUNNING` - Sandbox is active. **Action:** Check `execution_status` for task progress.
+- `PAUSED` - Sandbox is paused (due to rate limits or user action). **Action:** The sandbox will resume automatically when resources are available, or resume manually via the UI.
+- `ERROR` - Sandbox encountered an error. **Action:** This is a terminal state. Check conversation details in the UI for error information.
+- `MISSING` - Sandbox was deleted. **Action:** This is a terminal state. Start a new conversation if needed.
+
+**`execution_status`** - The state of the agent's task (available when sandbox is `RUNNING`):
+- `idle` - Agent is ready to receive tasks. **Action:** Continue polling if task was recently submitted.
+- `running` - Agent is actively working. **Action:** Continue polling.
+- `paused` - Execution is paused. **Action:** Continue polling; will resume automatically.
+- `waiting_for_confirmation` - Agent is waiting for user confirmation. **Action:** This is a blocking state. The agent needs user input via the UI to proceed. Your polling loop should treat this as a terminal state or alert the user.
+- `finished` - Agent has completed the task. **Action:** Terminal state. Task is done successfully.
+- `error` - Agent encountered an error. **Action:** Terminal state. Check conversation in UI for error details.
+- `stuck` - Agent is stuck and unable to proceed. **Action:** Terminal state. Manual intervention may be required.
+
+
+ **Terminal states** that should exit your polling loop: `finished`, `error`, `stuck`, `waiting_for_confirmation`.
+ The `waiting_for_confirmation` state requires user action through the UI before the agent can continue.
+
+
+#### Complete Polling Example
+
+Here's a complete example that starts a conversation and polls until completion:
+
+```python
+import requests
+import time
+
+api_key = "YOUR_API_KEY"
+base_url = "https://app.all-hands.dev"
+
+headers = {
+ "Authorization": f"Bearer {api_key}",
+ "Content-Type": "application/json"
+}
+
+# Start a conversation
+print("Starting conversation...")
+start_response = requests.post(
+ f"{base_url}/api/v1/app-conversations",
+ headers=headers,
+ json={
+ "initial_message": {
+ "content": [{"type": "text", "text": "Your task here"}]
+ },
+ "selected_repository": "yourusername/your-repo"
+ }
+)
+start_response.raise_for_status()
+start_task = start_response.json()
+task_id = start_task["id"]
+print(f"Start task ID: {task_id}")
+
+# Poll start task until conversation is ready (with timeout)
+conversation_id = None
+max_attempts = 60 # 5 minutes with 5-second intervals
+attempts = 0
+while not conversation_id and attempts < max_attempts:
+ task_response = requests.get(
+ f"{base_url}/api/v1/app-conversations/start-tasks",
+ headers=headers,
+ params={"ids": task_id}
+ )
+ task_response.raise_for_status()
+ tasks = task_response.json()
+
+ if tasks and tasks[0].get("status") == "READY":
+ conversation_id = tasks[0].get("app_conversation_id")
+ print(f"Conversation ready: {base_url}/conversations/{conversation_id}")
+ elif tasks and tasks[0].get("status") == "ERROR":
+ print(f"Start task failed: {tasks[0].get('error', 'Unknown error')}")
+ exit(1)
+ else:
+ status = tasks[0].get("status") if tasks else "no response"
+ print(f"Start task status: {status}")
+ time.sleep(5)
+ attempts += 1
+
+if not conversation_id:
+ print("Timeout waiting for conversation to start")
+ exit(1)
+
+# Poll conversation until agent finishes (with timeout)
+# Terminal states: finished, error, stuck, waiting_for_confirmation
+max_attempts = 120 # 1 hour with 30-second intervals
+attempts = 0
+while attempts < max_attempts:
+ conv_response = requests.get(
+ f"{base_url}/api/v1/app-conversations",
+ headers=headers,
+ params={"ids": conversation_id}
+ )
+ conv_response.raise_for_status()
+ conversations = conv_response.json()
+
+ if not conversations:
+ print("Warning: Conversation not found")
+ time.sleep(30)
+ attempts += 1
+ continue
+
+ conv = conversations[0]
+ sandbox_status = conv.get("sandbox_status")
+ exec_status = conv.get("execution_status")
+
+ # Check sandbox health first
+ if sandbox_status in ["ERROR", "MISSING"]:
+ print(f"Sandbox failed with status: {sandbox_status}")
+ exit(1)
+
+ print(f"Execution status: {exec_status}")
+
+ # Check for terminal states
+ if exec_status in ["finished", "error", "stuck"]:
+ print(f"Conversation completed with status: {exec_status}")
+ break
+ elif exec_status == "waiting_for_confirmation":
+ print("Agent is waiting for user confirmation in the UI")
+ print(f"Visit: {base_url}/conversations/{conversation_id}")
+ break
+
+ time.sleep(30)
+ attempts += 1
+else:
+ print("Timeout waiting for conversation to complete")
+ exit(1)
+```
+
+### Listing All Conversations
+
+To list all your conversations, use the search endpoint:
+
+
+
+ ```bash
+ curl -X GET "https://app.all-hands.dev/api/v1/app-conversations/search?limit=20" \
+ -H "Authorization: Bearer YOUR_API_KEY"
+ ```
+
+
+ ```python
+ import requests
+
+ api_key = "YOUR_API_KEY"
+ headers = {"Authorization": f"Bearer {api_key}"}
+
+ response = requests.get(
+ "https://app.all-hands.dev/api/v1/app-conversations/search",
+ headers=headers,
+ params={"limit": 20}
+ )
+ response.raise_for_status()
+ result = response.json()
+
+ for conv in result.get("items", []):
+ print(f"ID: {conv['id']}, Status: {conv.get('execution_status')}")
+ ```
+
+
+
+**Response:**
+```json
+{
+ "items": [
+ {
+ "id": "660e8400-e29b-41d4-a716-446655440001",
+ "sandbox_status": "RUNNING",
+ "execution_status": "finished",
+ "selected_repository": "yourusername/your-repo",
+ "title": "Fix README"
+ }
+ ],
+ "next_page_id": null
+}
+```
+
+
+ The search endpoint returns conversations in the `items` array. Use `next_page_id`
+ for pagination if you have more conversations than the `limit`.
+
+
## Rate Limits
If you have too many conversations running at once, older conversations will be paused to limit the number of concurrent conversations.