Contents
What you need
- JDK 17 or later
- Kotlin 1.9+ (via IntelliJ IDEA, Android Studio, or the Kotlin command-line compiler)
- A Sportmonks API token from your MySportmonks account
The Football API covers more than 2,200 leagues and cups, and every endpoint in this guide works on the free plan.
Setting up the project
Create a Gradle project and add OkHttp for the HTTP call and kotlinx.serialization for parsing the JSON response. In build.gradle.kts:
plugins {
kotlin("jvm") version "1.9.22"
kotlin("plugin.serialization") version "1.9.22"
}
dependencies {
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
}
OkHttp handles the request and response, and kotlinx.serialization turns the JSON body into Kotlin objects without an intermediate step through a generic map.
Modelling the response
Our Football API returns a data array of fixtures, and each fixture can be enriched with related entities using include. For this guide we will request participants (the two teams) and scores (the score per period). Model just the fields you need:
import kotlinx.serialization.Serializable
@Serializable
data class FixtureResponse(val data: List<Fixture>)
@Serializable
data class Fixture(
val id: Int,
val name: String,
val starting_at: String,
val participants: List<Participant> = emptyList(),
val scores: List<Score> = emptyList()
)
@Serializable
data class Participant(
val id: Int,
val name: String,
val meta: ParticipantMeta? = null
)
@Serializable
data class ParticipantMeta(val location: String? = null)
@Serializable
data class Score(
val description: String,
val score: ScoreValue
)
@Serializable
data class ScoreValue(val participant: String, val goals: Int)
Keeping the data classes narrow like this means the app only breaks if a field you actually use changes, not every time the API adds something new elsewhere in the response.
Making the request
The fixtures-by-date-range endpoint takes a start and end date in the URL path, plus your token and includes as query parameters:
import okhttp3.OkHttpClient
import okhttp3.Request
import kotlinx.serialization.json.Json
fun main() {
val token = "YOUR_API_TOKEN"
val startDate = "2026-09-12"
val endDate = "2026-09-14"
val url = "https://api.sportmonks.com/v3/football/fixtures/between/$startDate/$endDate" +
"?api_token=$token&include=participants;scores"
val client = OkHttpClient()
val request = Request.Builder().url(url).build()
client.newCall(request).execute().use { response ->
val body = response.body?.string() ?: return
val json = Json { ignoreUnknownKeys = true }
val fixtures = json.decodeFromString<FixtureResponse>(body).data
fixtures.forEach { fixture ->
val home = fixture.participants.firstOrNull { it.meta?.location == "home" }?.name ?: "Home"
val away = fixture.participants.firstOrNull { it.meta?.location == "away" }?.name ?: "Away"
println("${fixture.starting_at} - $home vs $away")
}
}
}
ignoreUnknownKeys = true matters here: the API response includes far more fields than the ones modelled above, and without that setting the deserialiser would fail on the first field it does not recognise. Semicolons separate multiple includes in the same request, so include=participants;scores fetches both in a single call rather than two round trips.
Reading the score
The scores array returns one entry per period (current, first half, second half, and so on), each tied to a participant ID rather than a team name. To print a final score, filter for the CURRENT description and match the participant ID back to the team you already resolved:
val current = fixture.scores.filter { it.description == "CURRENT" }
val homeGoals = current.firstOrNull { it.score.participant == "home" }?.score?.goals ?: 0
val awayGoals = current.firstOrNull { it.score.participant == "away" }?.score?.goals ?: 0
println("$home $homeGoals - $awayGoals $away")
This pattern, filtering an included array by a description or type field rather than assuming a fixed order, applies to most of the API’s related entities, including events, statistics and odds.
Handling errors and rate limits
A malformed token or an over-limit request returns a non-200 status with a JSON error body rather than throwing at the HTTP layer, so check response.isSuccessful before you try to decode anything:
if (!response.isSuccessful) {
println("Request failed: ${response.code} ${response.body?.string()}")
return
}
Your plan determines how many requests per hour you get and how many leagues you can query, and both are visible in your MySportmonks dashboard. If you are polling for live updates rather than pre-match fixtures, use the dedicated /livescores endpoints instead of repeatedly calling fixtures by date, since they are built for frequent polling and return only what changed.
Making the call non-blocking
The example above runs on the main thread, which is fine for a command-line script but not for an Android app, where a blocking network call on the main thread triggers an ANR. Wrap the same OkHttp call in a coroutine using Dispatchers.IO, and suspend rather than block while the request is in flight:
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
suspend fun fetchFixtures(startDate: String, endDate: String, token: String): List<Fixture> =
withContext(Dispatchers.IO) {
val url = "https://api.sportmonks.com/v3/football/fixtures/between/$startDate/$endDate" +
"?api_token=$token&include=participants;scores"
val client = OkHttpClient()
val request = Request.Builder().url(url).build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) return@withContext emptyList()
val body = response.body?.string() ?: return@withContext emptyList()
Json { ignoreUnknownKeys = true }.decodeFromString<FixtureResponse>(body).data
}
}
Call this from a ViewModel with viewModelScope.launch { }, or from a backend service with any coroutine scope you already have, and the parsing logic from the earlier sections stays exactly the same. Only where the call happens changes.
Where to take this next
The same request and parsing pattern extends to almost anything else in the Football API: swap the includes for predictions to add win probabilities, statistics for match stats, or standings on the leagues endpoint to build a table view. Our documentation covers every endpoint and include option in detail, and the Postman collection linked from each endpoint page is a fast way to check a response shape before you write the matching data class.
If you are building for Android specifically, this same OkHttp and kotlinx.serialization setup drops into a ViewModel or repository layer without changes, since neither library depends on anything JVM-server-specific.
Faq
- Create an account on My Sportmonks and get immediate access to our free plan.
- Subscribe to one of our paid plans and receive a one-time-only 14-day free trial.