C# makes it straightforward to retrieve and process weather data using the built-in HTTP and JSON capabilities available in modern .NET.
In this tutorial, we’ll use the Visual Crossing Weather API to retrieve weather data with C#, then parse the returned JSON into strongly typed .NET objects.
The same Timeline Weather API can retrieve historical weather, current conditions, and forecast data, so the same C# code can support a wide range of weather applications. If you want to explore available datasets before writing code, you can also use Visual Crossing Weather Data.
This tutorial uses:
HttpClientasyncandawaitSystem.Text.Json- Environment variables for API-key storage
- The Visual Crossing Timeline Weather API
No third-party C# packages are required.
What you’ll build
We’ll create a simple C# console application that:
- Builds a Timeline Weather API request.
- Sends the request using
HttpClient. - Checks the HTTP response for errors.
- Parses the JSON response using
System.Text.Json. - Displays daily weather information.
We’ll start with a forecast request and then show how the same code can retrieve historical weather.
Prerequisites
You’ll need:
- A recent .NET SDK
- A C# development environment such as Visual Studio, Visual Studio Code, or JetBrains Rider
- A Visual Crossing Weather account. You can sign up for free.
- Your Visual Crossing Weather API key
If you’re new to the API, see Getting Started with the Weather API.
The complete API reference is available in the Timeline Weather API documentation.
1. Create a C# project
Create a new console application:
dotnet new console -n WeatherExample
cd WeatherExample
Because modern .NET includes both HttpClient and System.Text.Json, no additional NuGet packages are required.
2. Store your Weather API key
Avoid placing your API key directly in source code that may be committed to a repository.
For this example, we’ll store the key in an environment variable named:
VISUAL_CROSSING_API_KEY
On Windows PowerShell:
$env:VISUAL_CROSSING_API_KEY="YOUR_API_KEY"
On macOS or Linux:
export VISUAL_CROSSING_API_KEY="YOUR_API_KEY"
Your C# code can read the key using:
var apiKey =
Environment.GetEnvironmentVariable(
"VISUAL_CROSSING_API_KEY"
);
if (string.IsNullOrWhiteSpace(apiKey))
{
throw new InvalidOperationException(
"VISUAL_CROSSING_API_KEY is not set."
);
}
Visual Crossing plans include usage and request limits, so applications should be designed to operate within the limits of the account plan being used.
3. Understand the Timeline Weather API URL
The Timeline Weather API uses this basic structure:
/timeline/[location]/[date1]/[date2]
Only the location is required.
For example, a forecast request for Paris uses:
/timeline/Paris%2C%20France
A historical request for a single date uses:
/timeline/Paris%2C%20France/2026-07-01
A historical date range uses:
/timeline/Paris%2C%20France/2026-07-01/2026-07-07
When no date is included, the Timeline Weather API returns the available forecast data.
This unified request structure makes it possible to use the same C# method for forecast and historical weather queries.
4. Build the Weather API URL
Let’s create a method that constructs the request URL from a location and optional dates.
static string BuildWeatherUrl(
string location,
string apiKey,
string? startDate = null,
string? endDate = null)
{
var encodedLocation =
Uri.EscapeDataString(location);
var url =
"https://weather.visualcrossing.com/" +
"VisualCrossingWebServices/rest/services/timeline/" +
encodedLocation;
if (!string.IsNullOrWhiteSpace(startDate))
{
url += "/" +
Uri.EscapeDataString(startDate);
}
if (!string.IsNullOrWhiteSpace(endDate))
{
url += "/" +
Uri.EscapeDataString(endDate);
}
var parameters = new Dictionary<string, string>
{
["key"] = apiKey,
["unitGroup"] = "metric",
["include"] = "days",
["elements"] =
"datetime,tempmax,tempmin,precip,precipprob,conditions",
["contentType"] = "json"
};
var queryString = string.Join(
"&",
parameters.Select(
parameter =>
$"{Uri.EscapeDataString(parameter.Key)}=" +
$"{Uri.EscapeDataString(parameter.Value)}"
)
);
return $"{url}?{queryString}";
}
Uri.EscapeDataString() ensures that locations such as:
Paris, France
are safely encoded for use in the URL.
The include parameter tells the API that we only need daily weather data, while elements restricts the response to the fields our application actually uses.
For more request options, see the Timeline Weather API documentation.
5. Retrieve weather data using HttpClient
Modern .NET applications should generally use HttpClient for HTTP requests.
Create one reusable client:
static readonly HttpClient HttpClient = new();
Then create a method to retrieve weather data:
static async Task<string> GetWeatherJsonAsync(
string location,
string apiKey,
string? startDate = null,
string? endDate = null)
{
var url = BuildWeatherUrl(
location,
apiKey,
startDate,
endDate
);
using var response =
await HttpClient.GetAsync(url);
var responseBody =
await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(
$"Weather API request failed " +
$"({(int)response.StatusCode} " +
$"{response.ReasonPhrase}): " +
responseBody
);
}
return responseBody;
}
The important steps are:
await HttpClient.GetAsync(url);
which sends the request, and:
await response.Content.ReadAsStringAsync();
which retrieves the response body.
We check the HTTP status before attempting to parse the response. This makes authentication problems, invalid parameters, usage-limit errors, and other API errors easier to diagnose.
6. Create C# classes for the weather response
The Timeline Weather API returns JSON.
For this example, we’ll map the fields we need into two C# classes.
public class WeatherResponse
{
public string? ResolvedAddress { get; set; }
public string? Timezone { get; set; }
public List<WeatherDay> Days { get; set; } = [];
}
public class WeatherDay
{
public string? Datetime { get; set; }
public double? Tempmax { get; set; }
public double? Tempmin { get; set; }
public double? Precip { get; set; }
public double? Precipprob { get; set; }
public string? Conditions { get; set; }
}
The property names correspond to fields returned by the Timeline Weather API.
Because values such as temperature and precipitation are numeric, they should be represented as numeric .NET types rather than strings.
7. Parse the JSON using System.Text.Json
Modern .NET includes System.Text.Json, so we don’t need a separate JSON package.
Add:
using System.Text.Json;
Then create a parsing method:
static WeatherResponse ParseWeather(
string json)
{
var options =
new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
var weather =
JsonSerializer.Deserialize<WeatherResponse>(
json,
options
);
if (weather is null)
{
throw new InvalidOperationException(
"Unable to parse Weather API response."
);
}
return weather;
}
Setting:
PropertyNameCaseInsensitive = true
allows JSON fields such as:
resolvedAddress
tempmax
tempmin
to map naturally to C# properties such as:
ResolvedAddress
Tempmax
Tempmin
8. Retrieve and display a weather forecast
Now we can combine the request and parsing code.
var apiKey =
Environment.GetEnvironmentVariable(
"VISUAL_CROSSING_API_KEY"
);
if (string.IsNullOrWhiteSpace(apiKey))
{
throw new InvalidOperationException(
"VISUAL_CROSSING_API_KEY is not set."
);
}
var json =
await GetWeatherJsonAsync(
"Paris, France",
apiKey
);
var weather =
ParseWeather(json);
Console.WriteLine(
$"Weather for {weather.ResolvedAddress}"
);
foreach (var day in weather.Days)
{
Console.WriteLine(
$"{day.Datetime}: " +
$"{day.Conditions}, " +
$"High {day.Tempmax}, " +
$"Low {day.Tempmin}, " +
$"Precipitation probability " +
$"{day.Precipprob}%"
);
}
A typical output might look like:
Weather for Paris, Île-de-France, France
2026-08-21: Partially cloudy, High 24.3, Low 16.1, Precipitation probability 20%
2026-08-22: Clear, High 25.7, Low 15.8, Precipitation probability 5%
9. Retrieve historical weather
The same methods can retrieve historical weather by supplying a date.
For example:
var json =
await GetWeatherJsonAsync(
"Paris, France",
apiKey,
"2026-07-01"
);
To retrieve a date range:
var json =
await GetWeatherJsonAsync(
"Paris, France",
apiKey,
"2026-07-01",
"2026-07-07"
);
The response uses the same days structure as a forecast request, so the parsing code does not need to change.
If your goal is to explore or download historical datasets rather than integrate them directly into an application, Visual Crossing Weather Data provides interactive access to historical and forecast weather information.
10. Request current conditions
The Timeline Weather API can also return current weather conditions.
Change:
["include"] = "days"
to:
["include"] = "current,days"
Then add a current-conditions model:
public class CurrentConditions
{
public double? Temp { get; set; }
public double? Humidity { get; set; }
public string? Conditions { get; set; }
}
and update WeatherResponse:
public class WeatherResponse
{
public string? ResolvedAddress { get; set; }
public string? Timezone { get; set; }
public List<WeatherDay> Days { get; set; } = [];
public CurrentConditions? CurrentConditions
{
get;
set;
}
}
You can then access:
weather.CurrentConditions?.Temp
weather.CurrentConditions?.Humidity
weather.CurrentConditions?.Conditions
11. Retrieve hourly weather
Hourly data is available by including:
hours
in the include parameter.
For example:
["include"] = "days,hours"
Each daily record can then include an hours array.
You could extend WeatherDay with:
public List<WeatherHour> Hours { get; set; } = [];
and create:
public class WeatherHour
{
public string? Datetime { get; set; }
public double? Temp { get; set; }
public double? Precipprob { get; set; }
public string? Conditions { get; set; }
}
Hourly weather can then be processed using:
foreach (var hour in weather.Days[0].Hours)
{
Console.WriteLine(
$"{hour.Datetime}: " +
$"{hour.Temp}, " +
$"{hour.Conditions}"
);
}
12. Complete C# example
The following example combines the core functionality into one console application:
using System.Net.Http;
using System.Text.Json;
static readonly HttpClient HttpClient = new();
var apiKey =
Environment.GetEnvironmentVariable(
"VISUAL_CROSSING_API_KEY"
);
if (string.IsNullOrWhiteSpace(apiKey))
{
throw new InvalidOperationException(
"VISUAL_CROSSING_API_KEY is not set."
);
}
var json =
await GetWeatherJsonAsync(
"Paris, France",
apiKey
);
var weather =
ParseWeather(json);
Console.WriteLine(
$"Weather for {weather.ResolvedAddress}"
);
foreach (var day in weather.Days)
{
Console.WriteLine(
$"{day.Datetime}: " +
$"{day.Conditions}, " +
$"High {day.Tempmax}, " +
$"Low {day.Tempmin}, " +
$"Precip probability {day.Precipprob}%"
);
}
static string BuildWeatherUrl(
string location,
string apiKey,
string? startDate = null,
string? endDate = null)
{
var encodedLocation =
Uri.EscapeDataString(location);
var url =
"https://weather.visualcrossing.com/" +
"VisualCrossingWebServices/rest/services/timeline/" +
encodedLocation;
if (!string.IsNullOrWhiteSpace(startDate))
{
url += "/" +
Uri.EscapeDataString(startDate);
}
if (!string.IsNullOrWhiteSpace(endDate))
{
url += "/" +
Uri.EscapeDataString(endDate);
}
var parameters = new Dictionary<string, string>
{
["key"] = apiKey,
["unitGroup"] = "metric",
["include"] = "days",
["elements"] =
"datetime,tempmax,tempmin," +
"precip,precipprob,conditions",
["contentType"] = "json"
};
var queryString = string.Join(
"&",
parameters.Select(
parameter =>
$"{Uri.EscapeDataString(parameter.Key)}=" +
$"{Uri.EscapeDataString(parameter.Value)}"
)
);
return $"{url}?{queryString}";
}
static async Task<string> GetWeatherJsonAsync(
string location,
string apiKey,
string? startDate = null,
string? endDate = null)
{
var url = BuildWeatherUrl(
location,
apiKey,
startDate,
endDate
);
using var response =
await HttpClient.GetAsync(url);
var responseBody =
await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(
$"Weather API request failed " +
$"({(int)response.StatusCode} " +
$"{response.ReasonPhrase}): " +
responseBody
);
}
return responseBody;
}
static WeatherResponse ParseWeather(
string json)
{
var options =
new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
var weather =
JsonSerializer.Deserialize<WeatherResponse>(
json,
options
);
if (weather is null)
{
throw new InvalidOperationException(
"Unable to parse Weather API response."
);
}
return weather;
}
public class WeatherResponse
{
public string? ResolvedAddress { get; set; }
public string? Timezone { get; set; }
public List<WeatherDay> Days { get; set; } = [];
}
public class WeatherDay
{
public string? Datetime { get; set; }
public double? Tempmax { get; set; }
public double? Tempmin { get; set; }
public double? Precip { get; set; }
public double? Precipprob { get; set; }
public string? Conditions { get; set; }
}
13. Change the unit system
The example uses metric weather values:
["unitGroup"] = "metric"
For US units, use:
["unitGroup"] = "us"
The unit group affects values such as temperature, precipitation, wind speed, and other measurements.
14. Handle errors in production code
Weather API requests can fail for a variety of reasons, including:
- Invalid API keys
- Invalid locations or dates
- Invalid request parameters
- Account usage limits
- Temporary network problems
- HTTP timeouts
Your application should check the HTTP status code and inspect the API response body before attempting to process the weather data.
For applications that accept user-supplied locations or dates, validate those values before constructing the request.
You may also want to configure an HttpClient timeout appropriate for your application.
For example:
HttpClient.Timeout =
TimeSpan.FromSeconds(20);
15. Request only the data your application needs
The Timeline Weather API can return many weather elements.
Using:
include
and:
elements
allows your application to control the amount and type of data returned.
For example, an application that only needs maximum temperature, minimum temperature, and conditions can request:
elements=datetime,tempmax,tempmin,conditions
This keeps the response smaller and makes your C# models easier to maintain.
The complete list of available weather fields is documented in the Timeline Weather API documentation.
Going further
Once you can retrieve weather data with C#, the same approach can be used in:
- ASP.NET applications
- Web APIs
- Background services
- Desktop applications
- Business intelligence systems
- Data-processing applications
- Azure-hosted applications
- Console and command-line utilities
The Visual Crossing Weather API provides programmatic access to historical weather, current conditions, forecasts, alerts, and other weather information.
If you need to search, explore, or download datasets without building an API integration, use Visual Crossing Weather Data.
For detailed API request parameters and response fields, see the Timeline Weather API documentation.
Summary
Modern .NET includes everything required to retrieve and process Visual Crossing Weather API data without adding third-party packages.
The basic process is:
- Obtain a Visual Crossing Weather API key.
- Store the key outside your application source code.
- Build a Timeline Weather API request for the required location and dates.
- Send the request using
HttpClient. - Check the HTTP response for errors.
- Parse the JSON using
System.Text.Json. - Map the response to strongly typed C# classes.
- Use the daily, hourly, or current weather values in your application.
Because the Timeline Weather API uses a consistent response structure for historical, current, and forecast weather, the same C# integration can support a wide variety of weather-enabled applications.

