How to Get Football Data with C# (.NET) - Complete API Tutorial
Step-by-step C# and .NET tutorial for fetching football fixtures, match stats and odds via API using HttpClient. Working code examples you can copy.
C# and .NET are a strong choice for football data backends, ASP.NET APIs, and desktop or Unity apps. TheStatsAPI is a plain REST JSON API, so the built-in HttpClient and System.Text.Json are all you need - no NuGet package required.
This tutorial shows how to fetch football data with C#: competitions, fixtures, pagination, match stats, and odds.
Prerequisites
You need:
- .NET 6+ (the examples use top-level statements and
async/await) - A TheStatsAPI API key
Set your API key as an environment variable:
export THESTATSAPI_KEY="your_api_key"
If you do not have a key yet, sign up at thestatsapi.com for a 7-day free trial.
Create a Small API Client
Create FootballApi.cs. Note the trailing slash on BaseAddress and that endpoints are passed without a leading slash, so relative paths combine correctly.
using System.Net.Http.Headers;
using System.Text.Json;
public class FootballApi
{
private readonly HttpClient _client = new()
{
BaseAddress = new Uri("https://api.thestatsapi.com/api/")
};
public FootballApi(string apiKey)
{
_client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", apiKey);
_client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<JsonElement> GetAsync(string endpoint)
{
using var response = await _client.GetAsync(endpoint);
response.EnsureSuccessStatusCode();
await using var stream = await response.Content.ReadAsStreamAsync();
using var doc = await JsonDocument.ParseAsync(stream);
return doc.RootElement.Clone();
}
}
Cloning the root element lets you return it safely after the JsonDocument is disposed. Every example below reuses GetAsync.
Fetch Competitions
using System.Text.Json;
var api = new FootballApi(Environment.GetEnvironmentVariable("THESTATSAPI_KEY")!);
var result = await api.GetAsync("football/competitions?search=Premier%20League");
foreach (var competition in result.GetProperty("data").EnumerateArray())
{
Console.WriteLine($"{competition.GetProperty("id").GetString()} " +
$"{competition.GetProperty("name").GetString()} " +
$"{competition.GetProperty("country").GetString()}");
}
Typical output:
comp_3039 Premier League England
Save the competition ID for match and season queries. Use Uri.EscapeDataString to encode search terms with spaces.
Get Fixtures for a League
using System.Text.Json;
var result = await api.GetAsync("football/matches?competition_id=comp_3039&per_page=10");
foreach (var match in result.GetProperty("data").EnumerateArray())
{
var home = match.GetProperty("home_team").GetProperty("name").GetString();
var away = match.GetProperty("away_team").GetProperty("name").GetString();
var status = match.GetProperty("status").GetString();
var date = match.GetProperty("utc_date").GetString();
Console.WriteLine($"{date} - {home} vs {away} ({status})");
}
Add date_from, date_to, season_id, team_id, and status to filter. See the fixtures API.
Handle Pagination
List endpoints return a meta block with total_pages.
using System.Text.Json;
async Task<List<JsonElement>> GetAllMatches(string query)
{
var rows = new List<JsonElement>();
var page = 1;
while (true)
{
var result = await api.GetAsync($"football/matches?{query}&page={page}");
foreach (var match in result.GetProperty("data").EnumerateArray())
rows.Add(match.Clone());
var totalPages = result.TryGetProperty("meta", out var meta)
&& meta.TryGetProperty("total_pages", out var tp)
? tp.GetInt32()
: 1;
if (page >= totalPages) break;
page++;
}
return rows;
}
var matches = await GetAllMatches("competition_id=comp_3039");
Console.WriteLine($"Fetched {matches.Count} matches");
Run backfills in a hosted service or background worker and cache rows in your database.
Get Match Stats
using System.Text.Json;
var data = (await api.GetAsync("football/matches/mt_010249745/stats")).GetProperty("data");
var overview = data.GetProperty("overview");
var xg = overview.GetProperty("expected_goals").GetProperty("all");
var shots = overview.GetProperty("total_shots").GetProperty("all");
Console.WriteLine($"Shots: {shots.GetProperty("home")} - {shots.GetProperty("away")}");
Console.WriteLine($"xG: {xg.GetProperty("home")} - {xg.GetProperty("away")}");
See the match stats API and xG API.
Get Pre-Match Odds
using System.Text.Json;
var data = (await api.GetAsync("football/matches/mt_010249745/odds")).GetProperty("data");
foreach (var book in data.GetProperty("bookmakers").EnumerateArray())
{
if (book.GetProperty("markets").TryGetProperty("match_odds", out var matchOdds))
{
Console.WriteLine($"{book.GetProperty("bookmaker").GetString()}: {matchOdds}");
}
}
Use /football/matches/{match_id}/odds/live for in-play odds. See the Football Odds API.
Production Tips
- Keep the API key in environment variables, user secrets, or a key vault.
- Register
HttpClientwithIHttpClientFactoryin ASP.NET instead of creating new instances per request. - Cache responses (
IMemoryCacheor a distributed cache) to respect plan limits. - Use pagination for backfills inside hosted/background services.
- Check availability flags such as
xg_availableandlive_odds_available.
FAQ
Can I use C# with TheStatsAPI?
Yes. TheStatsAPI is a REST JSON API, so .NET's built-in HttpClient and System.Text.Json work directly. The examples above need no external packages.
Do I need a .NET SDK package?
No. The built-in libraries are enough. You can use System.Net.Http.Json helpers or Newtonsoft.Json if you prefer strongly typed models.
How do I fetch football odds in C#?
Use /football/matches/{match_id}/odds for pre-match odds and /football/matches/{match_id}/odds/live for live odds where available.
How do I get xG data in C#?
Call /football/matches/{match_id}/stats and read overview.expected_goals.all for team xG.
Ready to Power Your Sports App?
Start your 7-day free trial. All endpoints included on every plan.