{"record":{"id":"467354b1f431c01e","repo":"microg/GmsCore","slug":"server-responded-with-status-response-status","errorCode":null,"errorMessage":"Server responded with status ${response.status}","messagePattern":"Server responded with status (.+?)","errorType":"http","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"vending-app/src/main/java/org/microg/vending/billing/core/HttpClient.kt","lineNumber":121,"sourceCode":"        }\n    }\n\n    suspend fun <O> get(\n        url: String,\n        headers: Map<String, String> = emptyMap(),\n        params: Map<String, String> = emptyMap(),\n        adapter: ProtoAdapter<O>,\n        cache: Boolean = true\n    ): O {\n\n        val response = (if (cache) clientWithCache else client).get(url.asUrl(params)) {\n            headers {\n                headers.forEach {\n                    append(it.key, it.value)\n                }\n            }\n        }\n        if (response.status != HttpStatusCode.OK) throw IOException(\"Server responded with status ${response.status}\")\n        else return adapter.decode(response.body<ByteArray>())\n    }\n\n    /**\n     * Post empty body.\n     */\n    suspend fun <I : Message<I, *>, O> post(\n        url: String,\n        headers: Map<String, String> = emptyMap(),\n        params: Map<String, String> = emptyMap(),\n        adapter: ProtoAdapter<O>,\n        cache: Boolean = false\n    ): O {\n        val response = (if (cache) clientWithCache else client).post(url.asUrl(params)) {\n            setBody(ByteArray(0))\n            headers {\n                headers.forEach {\n                    append(it.key, it.value)","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/microg/GmsCore/blob/157c9d86ac46c195a86c2f15ab55c84036223f95/vending-app/src/main/java/org/microg/vending/billing/core/HttpClient.kt#L103-L139","documentation":"HttpClient.get throws this IOException when the server responds with any HTTP status other than 200 OK. The decoder expects a 200 body; any redirect, auth failure, rate limit, or server error is surfaced as this exception carrying the actual status code.","triggerScenarios":"Any GET through HttpClient.get where the Google/Vending endpoint returns 401/403 (bad or expired auth headers), 404 (wrong URL), 429 (rate limited), or 5xx.","commonSituations":"Expired Google account auth data so HeaderProvider.getDefaultHeaders emits stale tokens; wrong base URL or API version parameter; server-side outage; device clock skew invalidating auth tokens.","solutions":["Inspect response.status in the message and handle 401/403 by refreshing auth credentials/tokens","Verify the request URL and query params (bav/apiVersion) are correct for the current API version","Retry with backoff on 429/5xx; check Google service status on widespread 5xx","Check device date/time correctness, which can break signed auth headers"],"exampleFix":"// before\nval data = httpClient.get(url, headers)\n// after\nval data = try {\n    httpClient.get(url, headers)\n} catch (e: IOException) {\n    if (e.message?.contains(\"401\") == true) refreshAuthAndRetry() else throw e\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    httpClient.get(url, headers)\n} catch (e: IOException) {\n    val status = Regex(\"status (\\\\d+)\").find(e.message ?: \"\")?.groupValues?.get(1)\n    when (status) {\n        \"401\", \"403\" -> refreshAuth()\n        \"429\", \"500\", \"503\" -> retryWithBackoff()\n        else -> throw e\n    }\n}","preventionTips":["Refresh auth tokens proactively before they expire","Keep device clock accurate — clock skew breaks auth headers","Validate request URLs and API version params","Implement backoff for 429/5xx responses"],"tags":["http","network","status-code"],"backgroundTag":"http-error-response","analyzedSha":"157c9d86ac46c195a86c2f15ab55c84036223f95","analyzedAt":"2026-09-06T17:27:33.892Z","contentChangedAt":"2026-09-06T17:27:33.892Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}