{"record":{"id":"b2e99acc44aed729","repo":"BerriAI/litellm","slug":"polling-response-missing-status-field","errorCode":null,"errorMessage":"Polling response missing 'status' field","messagePattern":"Polling response missing 'status' field","errorType":"exception","errorClass":"AzureOpenAIError","httpStatus":502,"severity":"error","filePath":"litellm/llms/azure/azure.py","lineNumber":921,"sourceCode":"                raise AzureOpenAIError(\n                    status_code=502,\n                    message=f\"Rejected polling URL: {ssrf_err}\",\n                )\n            response = await async_handler.get(\n                url=operation_location_url,\n                headers=headers,\n            )\n\n            await response.aread()\n\n            timeout_secs: Final[int] = AZURE_OPERATION_POLLING_TIMEOUT\n            start_time: Final = time.time()\n            if \"status\" not in response.json():\n                # Don't reflect the raw response body — when the polling\n                # URL points at an internal JSON API (cloud metadata\n                # service etc.) reflecting it here turns Blind SSRF into\n                # Full-Read SSRF. VERIA-51.\n                raise AzureOpenAIError(\n                    status_code=502,\n                    message=\"Polling response missing 'status' field\",\n                )\n            while response.json()[\"status\"] not in [\"succeeded\", \"failed\"]:\n                if time.time() - start_time > timeout_secs:\n                    raise AzureOpenAIError(status_code=408, message=\"Operation polling timed out.\")\n\n                await asyncio.sleep(int(response.headers.get(\"retry-after\") or 10))\n                response = await async_handler.get(\n                    url=operation_location_url,\n                    headers=headers,\n                )\n                await response.aread()\n\n            if response.json()[\"status\"] == \"failed\":\n                error_data: Final = response.json()\n                # Preserve Azure error details (e.g. content_policy_violation,\n                # inner_error, content_filter_results) as structured body so","sourceCodeStart":903,"sourceCodeEnd":939,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/llms/azure/azure.py#L903-L939","documentation":"During Azure batch operation polling, the JSON returned from the operation-location URL must contain a top-level 'status' field. If missing, LiteLLM raises this 502 without echoing the body — deliberately, because if the polling URL hit an internal JSON API (e.g. a cloud metadata service) reflecting the body would turn a blind SSRF into a full-read SSRF (VERIA-51).","triggerScenarios":"The operation-location URL responds with JSON lacking 'status': a misrouted endpoint, an auth wall returning an error JSON, or (attack scenario) an internal service responding to the polled URL. Commonly also appears when Azure changes polling payload shape or the URL expires and returns an error document.","commonSituations":"Long-running batch jobs where the operation URL expired before polling; gateways rewriting polling responses; legitimate Azure error JSON (e.g. {\"error\": ...}) instead of an operation status document.","solutions":["Manually GET the operation-location URL (from debug logs) with the same auth headers and inspect whether it returns an operation status document.","Re-submit the batch operation — expired/invalid operation URLs often resolve on a fresh submission.","Ensure api_base and credentials are valid so Azure returns real operation status responses.","Keep total polling time short and start polling promptly after submission to avoid URL expiry."],"exampleFix":"# before\njob = litellm.create_batch(..., completion_window='24h')\n# ... poll hours later via litellm.poll_batch(job.id) -> 502 missing status\n\n# after\njob = litellm.create_batch(..., completion_window='24h')\nstatus = litellm.poll_batch(job.id)  # poll soon after creation; retry once on 'missing status' before re-submitting","handlingStrategy":"try-catch","validationCode":"async def poll_status_safely(url: str, headers: dict) -> dict | None:\n    async with httpx.AsyncClient() as h:\n        r = await h.get(url, headers=headers)\n        try:\n            body = r.json()\n        except Exception:\n            return None\n        return body if isinstance(body, dict) and 'status' in body else None  # pre-check before relying on litellm polling","typeGuard":null,"tryCatchPattern":"from litellm.exceptions import APIError\n\ntry:\n    status = litellm.poll_batch(job.id)\nexcept APIError as e:\n    if \"missing 'status'\" in str(e):\n        # operation URL likely expired or misrouted: re-submit once, then surface\n        job = litellm.create_batch(...)\n        status = litellm.poll_batch(job.id)\n    else:\n        raise","preventionTips":["Start polling batch operations soon after submission; don't let operation URLs age out.","Treat this error as non-retryable-with-same-input: fetch fresh state instead of hammering the same URL.","Note that the error deliberately omits the response body (SSRF mitigation) — debug by fetching the URL yourself with proper auth."],"tags":["azure","batch","polling","security","ssrf","api-error"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}