{"record":{"id":"4ee49cc43fb1abb4","repo":"PrefectHQ/fastmcp","slug":"http-error-response-status-code-response-reaso","errorCode":null,"errorMessage":"HTTP error {response.status_code}: {response.reason_phrase} - {error_data}","messagePattern":"HTTP error (.+?): (.+?) - (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/providers/openapi/components.py","lineNumber":74,"sourceCode":"logger = get_logger(__name__)\n\n# Default MIME type when no response content type can be inferred\n_DEFAULT_MIME_TYPE = \"application/json\"\n\n\ndef _raise_for_status(response: httpx2.Response) -> None:\n    \"\"\"Raise an OpenAPI-formatted error without relying on client exception types.\"\"\"\n    if 200 <= response.status_code < 300:\n        return\n\n    error_message = f\"HTTP error {response.status_code}: {response.reason_phrase}\"\n    try:\n        error_data = response.json()\n        error_message += f\" - {error_data}\"\n    except (json.JSONDecodeError, ValueError):\n        if response.text:\n            error_message += f\" - {response.text}\"\n    raise ValueError(error_message)\n\n\nasync def _send_request(\n    client: httpx2.AsyncClient,\n    request: httpx2.Request,\n) -> httpx2.Response:\n    \"\"\"Send a request while preserving transitional legacy-client errors.\"\"\"\n    try:\n        return await client.send(request)\n    except Exception as exc:\n        if is_timeout_error(exc):\n            raise ValueError(f\"HTTP request timed out ({type(exc).__name__})\") from exc\n        if is_request_error(exc):\n            raise ValueError(f\"Request error ({type(exc).__name__}): {exc!s}\") from exc\n        raise\n\n\ndef _extract_mime_type_from_route(route: HTTPRoute) -> str:","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/providers/openapi/components.py#L56-L92","documentation":"_raise_for_status in the OpenAPI provider converts non-2xx HTTP responses from the upstream API into a ValueError: \"HTTP error {status}: {reason} - {body}\". The message includes the parsed JSON error body (or raw text if the body is not JSON) so the developer can see exactly what the remote API rejected. It is raised from OpenAPI component run/read paths when the remote server returns an error.","triggerScenarios":"Calling a tool/resource backed by an OpenAPI operation when the upstream HTTP API returns 4xx/5xx: bad or expired auth credentials, malformed request parameters generated from the tool call arguments, a wrong base_url in the OpenAPI config, rate limiting (429), or the downstream service being down (5xx).","commonSituations":"Expired API keys after rotation; calling tools whose required parameters were omitted or sent with wrong types; pointing fastmcp at a staging URL that no longer exists (404); gateway/proxy errors (502/503); the response body revealing auth or validation details from the upstream service.","solutions":["Read the appended error_data in the message — it usually states the upstream cause (invalid key, missing field, etc.)","Verify and refresh the API credentials/config used by the OpenAPI provider","Check that tool call arguments satisfy the operation's required parameters and types","Confirm the base_url/server URL in the OpenAPI spec config is correct and reachable","Retry with backoff on transient 429/5xx responses"],"exampleFix":"// before\nresult = await tool.run({\"city\": city})  # ValueError: HTTP error 401: Unauthorized - {...}\n// after\nheaders = {\"Authorization\": f\"Bearer {os.environ['API_KEY']}\"}\nprovider = OpenAPIProvider(openapi_spec=spec, client=client_with_headers(headers))\n# then retry the call once credentials are fixed","handlingStrategy":"try-catch","validationCode":"resp = await client.get(f\"{base_url}/health\")\nif resp.status_code != 200:\n    raise RuntimeError(f\"upstream API unhealthy: {resp.status_code}\")","typeGuard":"def is_retryable_status(status_code: int) -> bool:\n    return status_code == 429 or status_code >= 500","tryCatchPattern":"try:\n    result = await tool.run(args)\nexcept ValueError as e:\n    if \"HTTP error 429\" in str(e) or \"HTTP error 5\" in str(e):\n        await asyncio.sleep(backoff)\n        result = await tool.run(args)\n    else:\n        logger.error(f\"upstream rejected request: {e}\")\n        raise","preventionTips":["Inspect the error body appended to the message for the upstream cause","Rotate/verify API credentials before they expire","Validate tool call arguments against the operation's schema before invoking","Confirm base_url in the OpenAPI config points at a reachable environment","Add retry with backoff for 429/5xx responses only"],"tags":["http","openapi","upstream-api","network"],"backgroundTag":"upstream-http-error","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}