{"record":{"id":"8cb9c8ddd713d685","repo":"BerriAI/litellm","slug":"service-name-returned-non-dict-json-type-resul","errorCode":null,"errorMessage":"{service_name} returned non-dict JSON ({type(result).__name__}); expected OpenAI chat completion shape or empty object.","messagePattern":"(.+?) returned non-dict JSON \\((.+?)\\); expected OpenAI chat completion shape or empty object\\.","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"litellm/integrations/rubrik.py","lineNumber":1032,"sourceCode":"    # -- Webhook services ------------------------------------------------------\n\n    async def _post_json(self, endpoint: str, payload: Mapping[str, object], service_name: str) -> Mapping[str, Any]:\n        \"\"\"POST ``payload`` to a Rubrik webhook and return its dict response.\n\n        Raises:\n            Exception: If the service is unavailable or returns an error.\n            TypeError: If the response JSON is not a dict.\n        \"\"\"\n        verbose_logger.debug(\"Sending request to %s: %s\", service_name, endpoint)\n        http_response: Final = await self.moderation_client.post(\n            endpoint,\n            json=payload,\n            headers=self._headers,\n        )\n        http_response.raise_for_status()\n        result: Final[object] = http_response.json()\n        if not isinstance(result, dict):\n            raise TypeError(\n                f\"{service_name} returned non-dict JSON \"\n                f\"({type(result).__name__}); expected OpenAI chat completion \"\n                \"shape or empty object.\"\n            )\n        return result\n\n    async def _post_to_response_moderation_endpoint(\n        self,\n        response_data: Mapping[str, object],\n        request_data: Mapping[str, object],\n    ) -> Mapping[str, Any]:\n        \"\"\"Post the ``{request, response}`` envelope to the after_completion\n        webhook and return its (possibly rewritten) response.\n\n        Args:\n            response_data: The OpenAI-formatted response payload to send.\n            request_data: Original LLM request data to include alongside\n                the response for additional context. Empty dict if unavailable.","sourceCodeStart":1014,"sourceCodeEnd":1050,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/integrations/rubrik.py#L1014-L1050","documentation":"The shared moderation POST (_post_to_endpoint style) does raise_for_status() and then requires the parsed JSON body to be a dict — the expected shape is an OpenAI-chat-completion-like object or {}; any other top-level JSON type (list, string, number) raises TypeError naming the service and the actual type. A 2xx with a JSON array therefore fails here even though the HTTP call succeeded.","triggerScenarios":"RUBRIK_WEBHOOK_URL pointing at an endpoint that returns a JSON list (a collection/list API instead of the moderation endpoint); an intermediary (ingress, BFF) reshaping bodies; a Rubrik event API version whose success responses are not objects.","commonSituations":"Wrong path in the webhook URL (copied from a browser list view); gateway transformations on the response path; mismatch between the configured URL and the API version the guardrail expects.","solutions":["Reproduce with curl: POST a sample {request, response} envelope to the configured URL and inspect the top-level JSON type of the reply","Correct RUBRIK_WEBHOOK_URL to the moderation endpoint that answers with an object (or empty object)","Remove/adjust intermediaries that convert the body into a list or wrap it differently","Confirm the Rubrik event API version matches what this litellm integration expects (upgrade litellm if needed)"],"exampleFix":"# before\n# RUBRIK_WEBHOOK_URL=https://rubrik.example.com/api/v1/events  (returns a JSON list)\n# -> TypeError: rubrik returned non-dict JSON (list); expected OpenAI chat completion shape\n\n# after\n# RUBRIK_WEBHOOK_URL=https://rubrik.example.com/api/v1/moderation  (returns an object)\nresult = http_response.json()\nassert isinstance(result, dict), f'unexpected JSON type: {type(result).__name__}'","handlingStrategy":"type-guard","validationCode":"import httpx\n\nresp = httpx.post(webhook_url, json=sample_envelope, headers=headers, timeout=10)\nassert resp.status_code < 400, f'webhook unhealthy: {resp.status_code}'\nbody = resp.json()\nassert isinstance(body, dict), f'webhook must return a JSON object, got {type(body).__name__}'","typeGuard":"def parse_moderation_json(text: str) -> dict:\n    import json\n    result = json.loads(text)\n    if not isinstance(result, dict):\n        raise TypeError(f'expected JSON object from moderation service, got {type(result).__name__}')\n    return result","tryCatchPattern":"try:\n    result = await client.post(endpoint, json=payload, headers=headers)\nexcept TypeError as e:\n    if 'non-dict JSON' in str(e):\n        logger.error('webhook URL likely wrong (returns %s); check RUBRIK_WEBHOOK_URL', str(e))\n    raise","preventionTips":["Smoke-test the webhook with a sample envelope at deploy time and assert the reply is a JSON object","Point RUBRIK_WEBHOOK_URL only at the moderation endpoint, not list/collection APIs","Keep intermediaries from reshaping response bodies on the webhook path"],"tags":["rubrik","guardrail","webhook","json","type-error"],"backgroundTag":"unexpected-response-shape","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}