Networking

HTTP requests in Spotube plugins via the Spotr HTTP client.

Declare "NETWORK_REQUESTS" in plugin.json capabilities to enable networking.

Consuming

Networking is provided via the HttpClientAPI host service, but you never use it directly. Instead, obtain it in main() and wrap it in a SpotrClient, then pass the Spotr client to your service implementations:

import dev.krtirtho.plugin_interfaces.host_apis.HttpClientAPI
import dev.krtirtho.plugin_interfaces.host_apis.HttpClientAPI_SERVICE_NAME
import dev.krtirtho.plugin_interfaces.extras.spotor.SpotrClient

fun main() {
    val httpClient = zipline.take<HttpClientAPI>(HttpClientAPI_SERVICE_NAME)
    val spotr = SpotrClient(httpClient) {
        defaultUrl = "https://api.example.com"
    }

    zipline.bind<MetadataSearchAPI>(MetadataSearchAPI_SERVICE_NAME, RealMetadataSearchAPI(spotr))
}

Each service that needs HTTP receives the SpotrClient via its constructor. See the Spotr documentation for the full client API.

Spotr HTTP client

Spotr is the primary HTTP API for plugins. It wraps the underlying HttpClientAPI host service with a Ktor-like builder DSL, automatic kotlinx.serialization JSON support, and interceptor chains.

Never call HttpClientAPI.request() directly - always create a SpotrClient wrapper.

Setup

Create one client per API provider, or a shared client with a default base URL:

val client = SpotrClient(httpClient) {
    defaultUrl = "https://api.example.com"
    defaultHeaders {
        set("Authorization", "Bearer $token")
    }
}

Making requests

All HTTP methods are available with a builder DSL for URL, headers, body, and parameters:

val response = client.get {
    url { encodedPath = "/v1/search" }
    parameter("q", query)
    parameter("type", "track")
}

val data: SearchResponse = response.body()

POST with a JSON body:

val response = client.post {
    url("https://api.example.com/v1/tracks/save")
    jsonBody(SaveRequest(trackIds))
}

Response handling

SpotrHttpResponse provides three ways to access the body:

Method Returns Use case
bodyAsText() String Raw text or manual parsing
bodyAsBytes() ByteArray Binary data
body<T>() T JSON deserialization to a @Serializable type

The body<T>() method uses kotlinx.serialization. If you've set a default serializer on the client config, it uses that; otherwise pass one explicitly.

Interceptors

Interceptors sit between your request and the network. Common uses: logging, adding auth headers, retrying, measuring latency.

val client = SpotrClient(httpClient) {
    interceptor { context ->
        logger.d { "--> ${context.request.method} ${context.request.url}" }
        val response = context.proceed()
        logger.i { "<-- ${response.statusCode}" }
        response
    }

    interceptor { context ->
        context.request.headers.set("Authorization", "Bearer ${getAccessToken()}")
        context.proceed()
    }
}

Interceptors run in registration order. Call context.proceed() to continue the chain, or return early to short-circuit.

URL builder

For fine-grained URL construction:

client.get {
    url {
        protocol = "https"
        host = "api.example.com"
        encodedPath = "/v2/tracks"
        parameter("market", "US")
    }
}

Use url("...") for simple URLs, or url { ... } for the builder DSL.

Custom serialization

Spotr uses JsonContentSerializer by default (ignores unknown keys). To customize:

val client = SpotrClient(httpClient) {
    serializer = JsonContentSerializer(Json {
        ignoreUnknownKeys = true
        isLenient = true
        explicitNulls = false
    })
}

Implement ContentSerializer for non-JSON formats.