{"record":{"id":"540448e3b6aad836","repo":"BerriAI/litellm","slug":"mavvrik-focus-destination-failed-to-get-signed-ur","errorCode":null,"errorMessage":"Mavvrik FOCUS destination: failed to get signed URL ({resp.status_code}): {resp.text[:200]}","messagePattern":"Mavvrik FOCUS destination: failed to get signed URL \\((.+?)\\): (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"litellm/integrations/focus/destinations/mavvrik_destination.py","lineNumber":148,"sourceCode":"        metrics_marker: Final = resp.json().get(\"metricsMarker\", 0)\n        verbose_logger.debug(\n            \"Mavvrik FOCUS destination: connector registered (metricsMarker=%s)\",\n            metrics_marker,\n        )\n        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","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/integrations/focus/destinations/mavvrik_destination.py#L130-L166","documentation":"Before uploading, the destination GETs the Mavvrik upload-url endpoint (params name/type=metrics/datetime) to obtain a GCS signed URL. Any status >= 400 raises RuntimeError embedding the status and truncated body. Unlike registration, this is not cached — it happens per delivery date.","triggerScenarios":"Calling deliver() when the API key lacks permission for the upload-url endpoint (403), the connection_id/date combination is unknown (404), Mavvrik rate-limits or errors (429/5xx), or the query params are rejected (400).","commonSituations":"Key valid for registration but scoped away from uploads; quota exhaustion on repeated exports; Mavvrik-side incidents; clock skew making the datetime param look out of range.","solutions":["Inspect the embedded status: 403 → grant upload permission to the key; 429 → back off and retry with jitter; 5xx → retry after a delay.","Verify the date passed (time_window) is sane and the connection is registered first.","Wrap delivery in a retry with exponential backoff for 429/5xx, since signed-URL fetch is idempotent.","Contact Mavvrik support with the body snippet if 4xx persists."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"import asyncio\n\nfor attempt in range(4):\n    try:\n        await mavvrik_dest.deliver(content=csv, time_window=tw, filename=\"f.csv\")\n        break\n    except RuntimeError as e:\n        if \"failed to get signed URL\" not in str(e) or attempt == 3:\n            raise\n        if any(s in str(e) for s in (\"(401)\", \"(403)\", \"(404)\")):\n            raise\n        await asyncio.sleep(2 ** attempt + 1)  # backoff for 429/5xx","preventionTips":["Back off on 429/5xx — the signed-URL fetch is safe to retry and is not cached.","Ensure registration succeeds before the first delivery attempt so upload-url calls carry valid state."],"tags":["focus","mavvrik","network","http","retryable"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}