{"record":{"id":"b3f409c2a8b5cc4b","repo":"invoke-ai/InvokeAI","slug":"gemini-rate-limit-exceeded-f-retry-after-retry","errorCode":null,"errorMessage":"Gemini rate limit exceeded. {f'Retry after {retry_after:.0f}s.' if retry_after else 'Please try again later.'}","messagePattern":"Gemini rate limit exceeded\\. (.+?)s\\.' if retry_after else 'Please try again later\\.'\\}","errorType":"exception","errorClass":"ExternalProviderRateLimitError","httpStatus":429,"severity":"warning","filePath":"invokeai/app/services/external_generation/providers/gemini.py","lineNumber":116,"sourceCode":"        payload: dict[str, object] = {\n            \"systemInstruction\": {\"parts\": [{\"text\": system_instruction}]},\n            \"contents\": [{\"role\": \"user\", \"parts\": request_parts}],\n            \"generationConfig\": generation_config,\n        }\n        if \"thinking_level\" in opts:\n            payload[\"thinkingConfig\"] = {\"thinkingLevel\": opts[\"thinking_level\"].upper()}\n\n        response = requests.post(\n            endpoint,\n            params={\"key\": api_key},\n            json=payload,\n            timeout=120,\n        )\n\n        if not response.ok:\n            if response.status_code == 429:\n                retry_after = _parse_retry_after(response.headers.get(\"retry-after\"))\n                raise ExternalProviderRateLimitError(\n                    f\"Gemini rate limit exceeded. {f'Retry after {retry_after:.0f}s.' if retry_after else 'Please try again later.'}\",\n                    retry_after=retry_after,\n                )\n            raise ExternalProviderRequestError(\n                f\"Gemini request failed with status {response.status_code} for model '{model_id}': {response.text}\"\n            )\n\n        data = response.json()\n        if not isinstance(data, dict):\n            raise ExternalProviderRequestError(\"Gemini response payload was not a JSON object\")\n        images: list[ExternalGeneratedImage] = []\n        text_parts: list[str] = []\n        finish_messages: list[str] = []\n        candidates = data.get(\"candidates\")\n        if not isinstance(candidates, list):\n            raise ExternalProviderRequestError(\"Gemini response payload missing candidates\")\n        for candidate in candidates:\n            if not isinstance(candidate, dict):","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/app/services/external_generation/providers/gemini.py#L98-L134","documentation":"ExternalProviderRateLimitError raised by GeminiProvider.generate when Google's Gemini API responds with HTTP 429. The provider parses the Retry-After header (via _parse_retry_after) and includes a human-readable 'Retry after Ns' hint when available; otherwise it says 'Please try again later.' The retry_after value is attached to the exception for programmatic backoff.","triggerScenarios":"POST to {base_url}/models/{model_id}:generateContent returns status 429. Happens when per-minute/per-day quotas for the API key are exhausted, free-tier RPM limits are hit, or the model (e.g. image-generation models) has stricter quotas than the account allows.","commonSituations":"Batch generating many images in a loop; free-tier Gemini key with low RPM; multiple InvokeAI clients sharing one API key; image models with very low quota; burst of retrying clients amplifying load.","solutions":["Catch ExternalProviderRateLimitError and wait at least e.retry_after seconds before retrying (exponential backoff if unset).","Reduce request rate: serialize generations, add delays, or lower candidateCount/num_images per request.","Upgrade the Google AI Studio plan or request a quota increase for the generateContent/image quota.","Rotate to a different API key or region if multiple clients share one quota."],"exampleFix":"// before: blind retry amplifies 429s\nwhile True:\n    try:\n        return provider.generate(request)\n    except Exception:\n        continue\n// after: honor Retry-After from the typed rate-limit error\nimport time\ntry:\n    return provider.generate(request)\nexcept ExternalProviderRateLimitError as e:\n    time.sleep(e.retry_after if e.retry_after else 30)\n    return provider.generate(request)","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"import time\nfor attempt in range(3):\n    try:\n        return provider.generate(request)\n    except ExternalProviderRateLimitError as e:\n        wait = e.retry_after if e.retry_after else 2 ** attempt * 5\n        time.sleep(wait)\nraise  # give up after 3 attempts","preventionTips":["Throttle generation requests below your Gemini quota (requests/min).","Queue image generations instead of firing them in parallel loops.","Monitor quota usage in Google AI Studio and request increases proactively.","Handle the typed ExternalProviderRateLimitError separately from generic request errors so Retry-After is honored."],"tags":["rate-limit","http-429","gemini","retry-after","quota"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}