Screenshot API C# Guide 2026

Integrate SnapAPI screenshot and PDF capture into C# and .NET applications with HttpClient, async/await, ASP.NET Core, and Azure Functions.

Get Free API Key

Calling SnapAPI from C# with HttpClient

The .NET HttpClient provides a clean async/await interface for making HTTP requests to SnapAPI. Register HttpClient as a singleton or through IHttpClientFactory in your dependency injection container to benefit from connection pooling. SnapAPI accepts Bearer token authentication via the Authorization header and returns binary image bytes or JSON responses, both of which HttpClient handles cleanly. The examples below use .NET 8 and C# 12 features including primary constructors and collection expressions, and are fully compatible with .NET 6 and 7 with minor syntax adjustments.

// SnapApiClient.cs — .NET HttpClient wrapper
using System.Net.Http.Headers;

public class SnapApiClient(HttpClient httpClient, IConfiguration config)
{
    private readonly string _apiKey = config["SnapApi:Key"]
        ?? throw new InvalidOperationException("SnapApi:Key not configured");

    public async Task<byte[]> ScreenshotAsync(
        string url,
        string format = "png",
        bool fullPage = false,
        int viewportWidth = 1280,
        CancellationToken ct = default)
    {
        var qs = new Dictionary<string, string>
        {
            ["url"] = url,
            ["format"] = format,
            ["full_page"] = fullPage.ToString().ToLower(),
            ["viewport_width"] = viewportWidth.ToString(),
        };
        var uri = QueryHelpers.AddQueryString("https://api.snapapi.pics/screenshot", qs);
        using var req = new HttpRequestMessage(HttpMethod.Get, uri);
        req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey);

        var resp = await httpClient.SendAsync(req, ct);
        resp.EnsureSuccessStatusCode();
        return await resp.Content.ReadAsByteArrayAsync(ct);
    }

    public async Task<string> ScrapeAsync(string url, CancellationToken ct = default)
    {
        var uri = QueryHelpers.AddQueryString("https://api.snapapi.pics/scrape",
            new Dictionary<string, string> { ["url"] = url });
        using var req = new HttpRequestMessage(HttpMethod.Get, uri);
        req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey);

        var resp = await httpClient.SendAsync(req, ct);
        resp.EnsureSuccessStatusCode();
        return await resp.Content.ReadAsStringAsync(ct);
    }
}
// Program.cs — register SnapApiClient with IHttpClientFactory
builder.Services.AddHttpClient<SnapApiClient>(client =>
{
    client.Timeout = TimeSpan.FromSeconds(60);
});
builder.Configuration.AddEnvironmentVariables();

ASP.NET Core Controller: Screenshot Proxy

An ASP.NET Core minimal API or MVC controller can proxy screenshot requests to SnapAPI, protecting the API key from client exposure and adding authentication, rate limiting, and caching. The controller calls SnapAPI with the requested URL, receives the image bytes, and returns them as a FileContentResult with the appropriate MIME type. Add response caching middleware to cache frequently-requested screenshot URLs at the application level.

// ScreenshotController.cs — ASP.NET Core MVC controller
[ApiController]
[Route("api/[controller]")]
public class ScreenshotController(SnapApiClient snapApi) : ControllerBase
{
    [HttpGet]
    [ResponseCache(Duration = 3600, VaryByQueryKeys = ["url"])]
    public async Task<IActionResult> Get(
        [FromQuery] string url,
        [FromQuery] string format = "png",
        CancellationToken ct = default)
    {
        if (string.IsNullOrWhiteSpace(url))
            return BadRequest("url is required");

        try
        {
            var bytes = await snapApi.ScreenshotAsync(url, format, ct: ct);
            var contentType = format switch
            {
                "jpeg" => "image/jpeg",
                "webp" => "image/webp",
                "pdf" => "application/pdf",
                _ => "image/png"
            };
            return File(bytes, contentType);
        }
        catch (HttpRequestException ex)
        {
            return StatusCode(502, $"Screenshot failed: {ex.Message}");
        }
    }
}

Concurrent Screenshots with Task.WhenAll

C# async/await and Task.WhenAll make concurrent screenshot capture straightforward. Launch multiple screenshot tasks simultaneously and await all of them in parallel. This captures N pages in the time of the slowest single capture rather than the sum of all captures. Use SemaphoreSlim to limit concurrency to a configurable maximum to avoid overwhelming the API with too many simultaneous requests.

public class BatchScreenshotService(SnapApiClient client)
{
    public async Task<Dictionary<string, byte[]>> CaptureAllAsync(
        IEnumerable<string> urls,
        int maxConcurrency = 10,
        CancellationToken ct = default)
    {
        var semaphore = new SemaphoreSlim(maxConcurrency);
        var results = new System.Collections.Concurrent.ConcurrentDictionary<string, byte[]>();

        var tasks = urls.Select(async url =>
        {
            await semaphore.WaitAsync(ct);
            try
            {
                var bytes = await client.ScreenshotAsync(url, ct: ct);
                results.TryAdd(url, bytes);
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"Failed {url}: {ex.Message}");
            }
            finally
            {
                semaphore.Release();
            }
        });

        await Task.WhenAll(tasks);
        return new Dictionary<string, byte[]>(results);
    }
}

Azure Functions: Serverless Screenshot Generation

Azure Functions with C# are a natural deployment target for screenshot generation tasks that run on a schedule or in response to events. A timer-triggered Azure Function can run a screenshot monitoring job every 30 minutes, capturing screenshots of monitored URLs and storing them in Azure Blob Storage. An HTTP-triggered function can serve as a screenshot proxy endpoint. Both function types share the same SnapApiClient implementation registered in the function app's dependency injection container.

// ScreenshotTimerFunction.cs — scheduled screenshot monitoring
public class ScreenshotTimerFunction(SnapApiClient client, BlobServiceClient blob)
{
    [Function("ScreenshotMonitor")]
    public async Task Run(
        [TimerTrigger("0 */30 * * * *")] TimerInfo timer,
        CancellationToken ct)
    {
        var urls = new[] { "https://example.com", "https://status.example.com" };
        var container = blob.GetBlobContainerClient("screenshots");
        await container.CreateIfNotExistsAsync(cancellationToken: ct);

        foreach (var url in urls)
        {
            try
            {
                var bytes = await client.ScreenshotAsync(url, "png", fullPage: true, ct: ct);
                var blobName = $"{Uri.EscapeDataString(url)}/{DateTime.UtcNow:yyyyMMddHHmmss}.png";
                await container.UploadBlobAsync(blobName,
                    new BinaryData(bytes), ct);
                Console.WriteLine($"Captured {url}: {bytes.Length} bytes");
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"Failed {url}: {ex.Message}");
            }
        }
    }
}

Configuration and Secret Management in .NET

Store the SnapAPI key in .NET user secrets during development and in Azure Key Vault, AWS Secrets Manager, or environment variables in production. Access it through IConfiguration with a key like SnapApi:Key, which maps to the SNAPAPI__KEY environment variable name using .NET's double-underscore environment variable separator convention. For Azure deployments, use the Azure Key Vault configuration provider to load the key from a Key Vault secret at application startup, keeping the API key out of application settings files and CI/CD pipeline logs. For Docker deployments, inject the key as a Docker secret or environment variable in the container definition. This approach follows the standard .NET configuration provider pattern and works with all deployment targets without code changes, since the SnapApiClient reads from IConfiguration regardless of where the value was loaded from.

Polly Retry Policies for .NET

Polly is the standard resilience and transient-fault-handling library for .NET, providing retry policies, circuit breakers, and bulkhead isolation. Integrate Polly with HttpClient using the Microsoft.Extensions.Http.Polly NuGet package to add automatic retry behavior to the SnapApiClient. Define a retry policy that retries on HttpRequestException and on 503, 429, and 502 HTTP status codes, with exponential backoff between attempts. Register the policy using AddHttpClient().AddPolicyHandler() in your service registration code. Polly's WaitAndRetryAsync policy handles the delay calculation automatically, and the circuit breaker policy prevents repeated calls to a failing API endpoint by opening the circuit after a configured number of consecutive failures. These resilience patterns are idiomatic in .NET applications and integrate naturally with the HttpClient factory pattern, keeping retry logic separated from business code.

PDF Generation in C# and ASP.NET Core

Generating PDFs from web content in .NET traditionally requires third-party libraries like iTextSharp, PdfSharp, or Playwright for .NET with headless Chrome. SnapAPI eliminates these dependencies by providing PDF generation as an HTTP API: call the screenshot endpoint with format=pdf and receive PDF bytes in the response. For PDF invoice generation in ASP.NET Core, define a PdfService that calls SnapApiClient.ScreenshotAsync with format="pdf", then return the bytes from a controller action with content type application/pdf and a Content-Disposition header for browser download. For background PDF generation in Azure Functions or .NET worker services, store the generated PDF bytes in Azure Blob Storage and update the relevant database record with the blob URL. This approach avoids Playwright's large binary dependency in deployment packages, wkhtmltopdf's Linux-only compatibility issues, and the license costs of commercial .NET PDF libraries, making it suitable for serverless deployment environments where binary dependencies are limited.

Getting Started with SnapAPI in .NET

Install the Microsoft.AspNetCore.WebUtilities NuGet package for QueryHelpers.AddQueryString, and optionally Microsoft.Extensions.Http.Polly for resilience policies. Store the API key in user secrets for local development with dotnet user-secrets set SnapApi:Key YOUR_KEY, and in environment variables or Azure Key Vault for production. Register SnapApiClient in Program.cs with AddHttpClient as shown above. Make the first test call to the screenshot endpoint from a unit test or a quick console app to verify the connection and response format. The SnapAPI documentation at snapapi.pics/docs includes C# examples for all endpoints. The free tier at 200 requests per month is available indefinitely for evaluation with no credit card required, giving you time to build and test the integration in your real application before upgrading to a paid plan.

Unit Testing with Mocked HttpClient

Unit testing C# code that calls SnapAPI requires mocking the HttpClient to return controlled responses without making real network calls. Use Moq or NSubstitute to mock HttpMessageHandler, which is the low-level handler that HttpClient delegates HTTP calls to. Create a mock handler that returns a preconfigured response for the screenshot endpoint, inject it into an HttpClient instance, and pass that HttpClient to your SnapApiClient in the test. Assert that the correct URL was called, the correct Authorization header was sent, and that the response bytes were processed correctly by your service layer. For integration tests that verify real API responses, use the SNAPAPI_KEY environment variable as a skip condition: if the key is not present, skip the test. This approach keeps the test suite fast in local development while enabling real API verification in CI. Use xUnit, NUnit, or MSTest according to your team's preference — all three support these patterns equally well.