microg/GmsCore · error · IOException

Server responded with status ${response.status}

Error message

Server responded with status ${response.status}

What it means

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.

Source

Thrown at vending-app/src/main/java/org/microg/vending/billing/core/HttpClient.kt:121

        }
    }

    suspend fun <O> get(
        url: String,
        headers: Map<String, String> = emptyMap(),
        params: Map<String, String> = emptyMap(),
        adapter: ProtoAdapter<O>,
        cache: Boolean = true
    ): O {

        val response = (if (cache) clientWithCache else client).get(url.asUrl(params)) {
            headers {
                headers.forEach {
                    append(it.key, it.value)
                }
            }
        }
        if (response.status != HttpStatusCode.OK) throw IOException("Server responded with status ${response.status}")
        else return adapter.decode(response.body<ByteArray>())
    }

    /**
     * Post empty body.
     */
    suspend fun <I : Message<I, *>, O> post(
        url: String,
        headers: Map<String, String> = emptyMap(),
        params: Map<String, String> = emptyMap(),
        adapter: ProtoAdapter<O>,
        cache: Boolean = false
    ): O {
        val response = (if (cache) clientWithCache else client).post(url.asUrl(params)) {
            setBody(ByteArray(0))
            headers {
                headers.forEach {
                    append(it.key, it.value)

View on GitHub (pinned to 157c9d86ac)

Solutions

  1. Inspect response.status in the message and handle 401/403 by refreshing auth credentials/tokens
  2. Verify the request URL and query params (bav/apiVersion) are correct for the current API version
  3. Retry with backoff on 429/5xx; check Google service status on widespread 5xx
  4. Check device date/time correctness, which can break signed auth headers

Example fix

// before
val data = httpClient.get(url, headers)
// after
val data = try {
    httpClient.get(url, headers)
} catch (e: IOException) {
    if (e.message?.contains("401") == true) refreshAuthAndRetry() else throw e
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
    httpClient.get(url, headers)
} catch (e: IOException) {
    val status = Regex("status (\\d+)").find(e.message ?: "")?.groupValues?.get(1)
    when (status) {
        "401", "403" -> refreshAuth()
        "429", "500", "503" -> retryWithBackoff()
        else -> throw e
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of microg/GmsCore@157c9d86ac (2026-09-06). Data as JSON: /api/errors/467354b1f431c01e. Report an issue: GitHub.