Capture any website as PNG, JPEG, WebP, or PDF with one HttpClient request. No PuppeteerSharp, no Selenium, no Chromium download on the server. Works in ASP.NET Core and Azure Functions.
200 free screenshots/month. No credit card required.
The whole API is a GET request. Your key goes in the X-API-Key header, options go in query parameters, the image comes back as the response body.
using System.Net.Http;
using System.Web;
var query = HttpUtility.ParseQueryString(string.Empty);
query["url"] = "https://example.com";
query["format"] = "png";
query["width"] = "1280";
query["height"] = "800";
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(60) };
http.DefaultRequestHeaders.Add("X-API-Key", Environment.GetEnvironmentVariable("SNAPRENDER_API_KEY"));
var response = await http.GetAsync($"https://app.snap-render.com/v1/screenshot?{query}");
if (!response.IsSuccessStatusCode)
{
// Body is JSON: {"error":{"code":"...","message":"...","status":429}}
throw new InvalidOperationException(
$"SnapRender {(int)response.StatusCode}: {await response.Content.ReadAsStringAsync()}");
}
await File.WriteAllBytesAsync("screenshot.png", await response.Content.ReadAsByteArrayAsync());
using System.Net;
using System.Text.Json;
public sealed class SnapRenderClient
{
private readonly HttpClient _http;
public SnapRenderClient(HttpClient http, IConfiguration config)
{
_http = http;
_http.BaseAddress = new Uri("https://app.snap-render.com");
_http.DefaultRequestHeaders.Add("X-API-Key", config["SnapRender:ApiKey"]);
_http.Timeout = TimeSpan.FromSeconds(60);
}
public async Task<byte[]> CaptureAsync(string url, IDictionary<string, string>? options = null, CancellationToken ct = default)
{
var parts = new List<string> { $"url={Uri.EscapeDataString(url)}" };
foreach (var (key, value) in options ?? new Dictionary<string, string>())
parts.Add($"{key}={Uri.EscapeDataString(value)}");
var response = await _http.GetAsync($"/v1/screenshot?{string.Join("&", parts)}", ct);
if (response.IsSuccessStatusCode)
{
if (response.Headers.TryGetValues("X-Cache", out var cache))
Console.WriteLine($"cache: {cache.First()}");
return await response.Content.ReadAsByteArrayAsync(ct);
}
var json = await response.Content.ReadAsStringAsync(ct);
var error = JsonDocument.Parse(json).RootElement.GetProperty("error");
var code = error.GetProperty("code").GetString();
var message = error.GetProperty("message").GetString();
throw response.StatusCode switch
{
// RATE_LIMITED: per-minute burst, retry after a minute. QUOTA_EXCEEDED: monthly limit, do not retry.
HttpStatusCode.TooManyRequests => new InvalidOperationException($"{code}: {message}"),
// INVALID_URL, BLOCKED_URL, or a bad key: fix the input.
HttpStatusCode.BadRequest or HttpStatusCode.Unauthorized => new ArgumentException($"{code}: {message}"),
_ => new HttpRequestException($"SnapRender {(int)response.StatusCode} {code}: {message}"),
};
}
}
// Program.cs
builder.Services.AddHttpClient<SnapRenderClient>();
// Anywhere with DI
var jpeg = await snapRender.CaptureAsync("https://example.com", new Dictionary<string, string>
{
["format"] = "jpeg", ["quality"] = "85", ["full_page"] = "true",
["device"] = "iphone_15_pro", ["dark_mode"] = "true",
["cache"] = "true", ["cache_ttl"] = "3600",
});
app.MapGet("/reports/{id}/pdf", async (string id, SnapRenderClient snapRender) =>
{
var pdf = await snapRender.CaptureAsync($"https://example.com/reports/{id}", new Dictionary<string, string>
{
["format"] = "pdf", ["full_page"] = "true", ["width"] = "1024",
});
return Results.File(pdf, "application/pdf", $"report-{id}.pdf");
});
Ads and cookie banners are blocked by default. The PDF is a rendered page image, not a print-stylesheet layout. Full parameter reference in the docs.
PuppeteerSharp is a faithful port of Puppeteer, and it inherits the same operational weight. BrowserFetcher downloads a full Chromium build into your application directory on first run, which is slow on cold starts, blocked on many locked-down hosts, and awkward inside Azure Functions and App Service sandboxes. The browser then runs as a child process beside your .NET host, with its own memory footprint and its own way of hanging on a slow page.
With a screenshot API the browser runs on our side. .NET sends one request through the HttpClient it already has and receives the finished bytes. Cookie banners and ads are removed, full-page capture handles lazy loading, and your publish output does not grow by a browser.
Register the client with AddHttpClient<SnapRenderClient>() so connection pooling is handled by IHttpClientFactory. For background work, call it from a hosted service or an Azure Function timer trigger and write the bytes to Blob Storage. Repeat captures inside cache_ttl are served from the cache and are not billed.
Because the integration is a GET request, it runs anywhere .NET can open an outbound HTTPS connection: Azure App Service on Windows or Linux, Azure Functions, AWS Lambda for .NET, Windows services, and trimmed self-contained deployments where no browser could ever be shipped.
| SnapRender API | PuppeteerSharp in production | |
|---|---|---|
| Dependencies | System.Net.Http (BCL) | NuGet package plus Chromium download |
| Azure Functions / App Service | Yes, no setup | Sandbox restrictions, slow cold starts |
| Memory | Managed heap only | Browser child process |
| Cookie banners and ads | Blocked by default | Custom code per site |
| Caching | cache=true, hits are free | Build it yourself |
| Browser updates | Handled for you | Yours to test and ship |
Want to see the output first? Try the free website screenshot tool. It uses the same rendering engine as the API.
Send a GET request to the /v1/screenshot endpoint with HttpClient, the target in the url query parameter, and your key in the X-API-Key header, then read the body with ReadAsByteArrayAsync. The page renders in real Chrome on SnapRender's servers, so your .NET process never launches a browser.
No. HttpClient is part of the base class library, so there is no NuGet package, no Chromium download at startup, and no browser on the host. PuppeteerSharp's BrowserFetcher pulls a full Chromium build into your app directory, which is exactly the step this integration skips.
Yes. Both platforms allow outbound HTTPS and neither is a comfortable home for a headless browser. Register a typed HttpClient with dependency injection, read the key from configuration, and return the bytes from the function or controller.
Yes. Pass format=pdf and the response body is a PDF of the rendered page. It is a page render rather than a print stylesheet layout, so use full_page=true for long pages. Return it with File(bytes, "application/pdf") from a controller.
Yes. The free plan includes 200 screenshots per month with every feature: all formats including PDF, full-page capture, device emulation, dark mode, and caching. No credit card is required to sign up.
The same API has quickstarts for other languages.
Related: HTML to PDF API and serverless runtimes.
Or skip the setup entirely
Everything in this guide is one GET request with SnapRender. No browser to babysit, no timeouts to tune.
200 screenshots a month free. First render in under a minute.
Grab a key, paste the class, done. 200 free screenshots per month, no credit card.