{"record":{"id":"0f0a8c6c430731f3","repo":"BerriAI/litellm","slug":"mavvrik-focus-destination-response-missing-url","errorCode":null,"errorMessage":"Mavvrik FOCUS destination: response missing 'url' field: {resp.json()}","messagePattern":"Mavvrik FOCUS destination: response missing 'url' field: (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"litellm/integrations/focus/destinations/mavvrik_destination.py","lineNumber":153,"sourceCode":"        return metrics_marker\n\n    async def _get_signed_url(self, date_str: str) -> str:\n        \"\"\"GET upload-url endpoint → GCS signed URL for the given date.\"\"\"\n        params: Final = {\"name\": date_str, \"type\": \"metrics\", \"datetime\": date_str}\n        resp: Final = await self._http.client.request(\n            method=\"GET\",\n            url=self._upload_url_endpoint,\n            headers=self._auth_headers,\n            params=params,\n            timeout=30.0,\n        )\n        if resp.status_code >= 400:\n            raise RuntimeError(\n                f\"Mavvrik FOCUS destination: failed to get signed URL ({resp.status_code}): {resp.text[:200]}\"\n            )\n        signed_url: Final = resp.json().get(\"url\")\n        if not signed_url:\n            raise RuntimeError(f\"Mavvrik FOCUS destination: response missing 'url' field: {resp.json()}\")\n        _validate_gcs_url(signed_url, \"signed URL\")\n        verbose_logger.debug(\"Mavvrik FOCUS destination: got signed URL for date %s\", date_str)\n        return signed_url\n\n    async def _upload_to_gcs(self, signed_url: str, content: bytes) -> None:\n        \"\"\"Upload gzip-compressed CSV to GCS via chunked resumable upload.\n\n        The full CSV is gzip-compressed first, then uploaded in _GCS_CHUNK_SIZE\n        chunks using the GCS resumable upload protocol. GCS assembles the chunks\n        server-side into a single complete object — the bucket receives one file\n        regardless of how many chunks were sent.\n\n        Intermediate chunks: Content-Range: bytes X-Y/*   → expect 308\n        Final chunk:         Content-Range: bytes X-Y/T   → expect 200/201\n\n        This handles exports larger than available memory for a single PUT while\n        keeping the destination code self-contained (no changes to the FOCUS\n        pipeline upstream).","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/integrations/focus/destinations/mavvrik_destination.py#L135-L171","documentation":"The upload-url endpoint returned 2xx but its JSON body has no 'url' field (or it's null/empty). This indicates a Mavvrik API contract change or an unexpected (e.g. HTML) response that happened to return 200 — the code dumps resp.json() into the message for diagnosis.","triggerScenarios":"Calling _get_signed_url when the Mavvrik API responds 200 with a payload like {\"signedUrl\": ...} (renamed field), an empty object, or a JSON error envelope without 'url'.","commonSituations":"Mavvrik API version change renaming the field; a gateway returning 200 with an error body; proxy health-check intercepting the path; older/newer LiteLLM expecting a different response shape.","solutions":["Read the embedded body in the message to see what fields were actually returned.","Upgrade LiteLLM to the latest version — response-shape fixes are usually shipped quickly after API changes.","If the field was renamed on Mavvrik's side, report it / pin a compatible Mavvrik API version if available.","Retry once to rule out a transient gateway glitch."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    await mavvrik_dest.deliver(content=csv, time_window=tw, filename=\"f.csv\")\nexcept RuntimeError as e:\n    if \"missing 'url' field\" in str(e):\n        log.error(\"Mavvrik API contract drift: %s\", e)\n        # one retry in case of gateway glitch, then open a support ticket\n    raise","preventionTips":["Pin a LiteLLM version tested against your Mavvrik tenant; upgrade deliberately when APIs change.","Log the full response body (already embedded in the message) into your error tracker for quick diagnosis."],"tags":["focus","mavvrik","api-contract","json","network"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}