C#
Mapricot's API is plain HTTPS and JSON, so .NET's HttpClient and System.Text.Json cover it, with no NuGet package to install. This page calls each API: geocoding, places, routing, and IP geolocation, and shows where the free map tiles fit. It was tested with .NET 8, and works with .NET 6 and later.
Set up
Create a server key in the dashboard with the products you'll use: Places for geocoding and places, Routing, and IP geolocation. Set it as an environment variable, so it stays out of your code:
export MAPRICOT_KEY="mapricot_sk_…"
Make a console app with dotnet new console, and replace Program.cs with an HttpClient that sends the key with every request, and a function that retries when the API is busy and throws on errors. It sends a POST when there's a body, and returns the response as a JsonNode. The examples below go after it, in the same file:
using System.Globalization;
using System.Net;
using System.Net.Http.Json;
using System.Text.Json.Nodes;
var http = new HttpClient { BaseAddress = new Uri("https://api.mapricot.com"), Timeout = TimeSpan.FromSeconds(30) };
http.DefaultRequestHeaders.Authorization = new("Bearer", Environment.GetEnvironmentVariable("MAPRICOT_KEY"));
async Task<JsonNode> Mapricot(string path, Dictionary<string, object>? query = null, object? body = null)
{
if (query is not null)
{
path += "?" + string.Join("&", query.Select(p =>
$"{p.Key}={Uri.EscapeDataString(Convert.ToString(p.Value, CultureInfo.InvariantCulture)!)}"));
}
for (var attempt = 1; ; attempt++)
{
using var response = body is null ? await http.GetAsync(path) : await http.PostAsJsonAsync(path, body);
if (response.StatusCode is HttpStatusCode.TooManyRequests or HttpStatusCode.ServiceUnavailable && attempt < 4)
{
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
continue;
}
var json = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(
$"Mapricot returned {(int)response.StatusCode}: {json["error"]?["message"]}", null, response.StatusCode);
}
return json;
}
}
Geocode an address
/v1/search turns an address or place name into coordinates:
var search = await Mapricot("/v1/search", new() { ["q"] = "128 SW 3rd Ave, Portland", ["limit"] = 1 });
foreach (var result in search["results"]!.AsArray())
{
Console.WriteLine($"{result!["label"]} {result["lat"]} {result["lon"]}");
}
128 SW 3rd Avenue, Portland, OR, USA 45.521736 -122.673276
Each result also has its address in parts under address, and a type, such as address, street, or locality, that says how exact the match is. To limit results to some countries, add ["country"] = "US,CA". Geocoding lists every parameter and field.
Find the address at a point
/v1/reverse turns coordinates into the nearest address:
var nearest = (await Mapricot("/v1/reverse", new() { ["lat"] = 45.5217, ["lon"] = -122.6733, ["limit"] = 1 }))["results"]![0]!;
Console.WriteLine($"{nearest["label"]} {nearest["distanceMeters"]}");
128 SW 3rd Avenue, Portland, OR, USA 4
Suggest places as someone types
/v1/autocomplete takes the text typed so far. focus ranks results near a point first:
var suggestion = (await Mapricot("/v1/autocomplete", new() { ["q"] = "stumpt", ["focus"] = "45.52,-122.68", ["limit"] = 1 }))["results"]![0]!;
Console.WriteLine($"{suggestion["label"]} {suggestion["id"]}");
Stumptown Coffee Roasters, Portland, OR, USA mp_b3BlbnN0cmVldG1hcDp2ZW51ZTpub2RlLzExOTg2MjI2NjYw
Find places nearby
/v1/nearby lists places of a category around a point, nearest first:
var nearby = await Mapricot("/v1/nearby", new()
{
["lat"] = 45.52, ["lon"] = -122.68, ["category"] = "food:coffee_shop", ["radius"] = 1000, ["limit"] = 1,
});
foreach (var place in nearby["results"]!.AsArray())
{
Console.WriteLine($"{place!["name"]} {place["distanceMeters"]}");
}
Public Domain 37
Look up a place by ID
Every result has an id. Save it, and /v1/places/{id} fetches that place again later:
var saved = (await Mapricot($"/v1/places/{suggestion["id"]}"))["result"]!;
Console.WriteLine(saved["label"]);
Stumptown Coffee Roasters, Portland, OR, USA
Get a route
/v1/route takes a JSON body with two to 20 waypoints and a mode of drive, walk, or bicycle:
var route = (await Mapricot("/v1/route", body: new
{
waypoints = new[] { new { lat = 43.7396, lon = 7.4275 }, new { lat = 43.734, lon = 7.4222 } },
mode = "walk",
}))["routes"]![0]!;
Console.WriteLine($"{route["distanceMeters"]} {route["durationSeconds"]}");
1044 739
Distances are in meters and times in seconds. When no path connects the waypoints, routes is empty. The route's line is in polyline, encoded with six decimal places, so decode it with precision 6, as decoding a route polyline explains.
Look up an IP address
/v1/ip/{ip} gives the approximate location of an IPv4 or IPv6 address:
var location = await Mapricot("/v1/ip/8.8.8.8");
Console.WriteLine($"{location["city"]} {location["countryCode"]}");
Mountain View US
Show a map
Map tiles are free and need no key. MapLibre GL JS draws them in the browser from https://tiles.mapricot.com/styles/light.json, so .NET on a server doesn't fetch them: send your results to a web page, and draw them there, as Map tiles shows. MapLibre takes coordinates as [lon, lat].
Errors and retries
Mapricot throws an HttpRequestException for any error response, with the API's message and the status in StatusCode:
try
{
await Mapricot("/v1/search", new() { ["q"] = "" });
}
catch (HttpRequestException error)
{
Console.WriteLine(error.Message);
}
Mapricot returned 400: The service rejected the request.
It retries 429 and 503 responses up to three times, waiting 2, 4, then 8 seconds. Errors aren't billed, so retrying them costs nothing. A 429 that keeps coming back can also mean the account is out of credit. Each account has a limit on requests in progress at once, so send requests one at a time or a few at once, rather than with Task.WhenAll over a long list. The API reference lists every status.