Screenshot -- C# / .NET Code Examples

HTTP-based examples for integrating the Nodium Screenshot API with C# using HttpClient and System.Text.Json. Targets .NET 6+ and ASP.NET Core.

Basic Screenshot (GET)

The simplest way to capture a screenshot using query parameters.

csharp
using System.Web;

const string ApiKey = "YOUR_API_KEY";
const string BaseUrl = "https://api.nodium.io/api/v1/screenshot/take";

var query = HttpUtility.ParseQueryString(string.Empty);
query["url"] = "https://example.com";
query["format"] = "png";
query["viewport_width"] = "1280";
query["viewport_height"] = "1024";

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Access-Key", ApiKey);

var response = await client.GetAsync($"{BaseUrl}?{query}");

if (response.IsSuccessStatusCode)
{
    var bytes = await response.Content.ReadAsByteArrayAsync();
    await File.WriteAllBytesAsync("screenshot.png", bytes);
    Console.WriteLine($"Screenshot saved ({bytes.Length} bytes)");
}
else
{
    var error = await response.Content.ReadAsStringAsync();
    Console.Error.WriteLine($"API error ({(int)response.StatusCode}): {error}");
}

POST with JSON Body

Use POST for complex configurations with a JSON request body.

csharp
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;

const string ApiKey = "YOUR_API_KEY";
const string BaseUrl = "https://api.nodium.io/api/v1/screenshot/take";

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Access-Key", ApiKey);
client.Timeout = TimeSpan.FromSeconds(60);

var payload = new
{
    url = "https://example.com",
    format = "png",
    viewport_width = 1440,
    viewport_height = 900,
    full_page = true,
    image_quality = 90,
    block_ads = true,
    delay = 1000
};

var response = await client.PostAsJsonAsync(BaseUrl, payload);

if (response.IsSuccessStatusCode)
{
    var bytes = await response.Content.ReadAsByteArrayAsync();
    await File.WriteAllBytesAsync("screenshot.png", bytes);

    Console.WriteLine("Screenshot saved");

    if (response.Headers.TryGetValues("x-nodium-rendering-seconds", out var renderTime))
        Console.WriteLine($"Rendering time: {renderTime.First()}s");

    if (response.Headers.TryGetValues("x-nodium-size-bytes", out var sizeBytes))
        Console.WriteLine($"File size: {sizeBytes.First()} bytes");
}
else
{
    var error = await response.Content.ReadAsStringAsync();
    Console.Error.WriteLine($"API error ({(int)response.StatusCode}): {error}");
}

Reusable Screenshot Client

A helper class that wraps the API with typed models, error handling, and retries.

csharp
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;

public class NodiumClient : IDisposable
{
    private const string BaseUrl = "https://api.nodium.io/api/v1/screenshot";

    private readonly HttpClient _httpClient;
    private readonly int _maxRetries;

    public NodiumClient(string apiKey, int maxRetries = 3, int timeoutSeconds = 60)
    {
        _maxRetries = maxRetries;
        _httpClient = new HttpClient
        {
            Timeout = TimeSpan.FromSeconds(timeoutSeconds)
        };
        _httpClient.DefaultRequestHeaders.Add("X-Access-Key", apiKey);
    }

    public Task<byte[]> TakeAsync(object parameters)
        => RequestWithRetryAsync($"{BaseUrl}/take", parameters);

    public Task<byte[]> AnimateAsync(object parameters)
        => RequestWithRetryAsync($"{BaseUrl}/animate", parameters);

    private async Task<byte[]> RequestWithRetryAsync(string url, object parameters)
    {
        for (int attempt = 0; attempt <= _maxRetries; attempt++)
        {
            var response = await _httpClient.PostAsJsonAsync(url, parameters);

            if (response.IsSuccessStatusCode)
            {
                return await response.Content.ReadAsByteArrayAsync();
            }

            var errorBody = await response.Content.ReadAsStringAsync();

            // Do not retry client errors (4xx)
            if ((int)response.StatusCode >= 400 && (int)response.StatusCode < 500)
            {
                var apiError = JsonSerializer.Deserialize<ApiError>(errorBody);
                throw new NodiumApiException(
                    apiError?.ErrorCode ?? "unknown",
                    apiError?.Message ?? errorBody,
                    (int)response.StatusCode
                );
            }

            // Retry server errors (5xx) with exponential backoff
            if (attempt < _maxRetries)
            {
                var delay = (int)Math.Pow(2, attempt) * 1000;
                Console.WriteLine($"Retry {attempt + 1}/{_maxRetries} after {delay}ms...");
                await Task.Delay(delay);
            }
        }

        throw new NodiumApiException("max_retries_exceeded",
            $"Max retries ({_maxRetries}) exceeded", 500);
    }

    public void Dispose() => _httpClient.Dispose();
}

public record ApiError(
    [property: JsonPropertyName("error_code")] string ErrorCode,
    [property: JsonPropertyName("message")] string Message,
    [property: JsonPropertyName("http_status_code")] int HttpStatusCode
);

public class NodiumApiException : Exception
{
    public string ErrorCode { get; }
    public int HttpStatus { get; }
    public bool IsRetryable => HttpStatus >= 500;

    public NodiumApiException(string errorCode, string message, int httpStatus)
        : base(message)
    {
        ErrorCode = errorCode;
        HttpStatus = httpStatus;
    }
}

Using the Client

csharp
using var client = new NodiumClient("YOUR_API_KEY");

try
{
    var screenshot = await client.TakeAsync(new
    {
        url = "https://example.com",
        format = "png",
        viewport_width = 1280,
        viewport_height = 1024,
        full_page = true
    });

    await File.WriteAllBytesAsync("screenshot.png", screenshot);
    Console.WriteLine($"Screenshot saved ({screenshot.Length} bytes)");
}
catch (NodiumApiException ex)
{
    Console.Error.WriteLine($"API error [{ex.ErrorCode}]: {ex.Message}");
    Console.Error.WriteLine($"Retryable: {ex.IsRetryable}");
}

Error Handling

Handle specific error codes to take appropriate action.

csharp
try
{
    var screenshot = await client.TakeAsync(new
    {
        url = "https://example.com",
        format = "png"
    });

    await File.WriteAllBytesAsync("screenshot.png", screenshot);
}
catch (NodiumApiException ex) when (ex.ErrorCode == "screenshots_limit_reached")
{
    Console.Error.WriteLine("Monthly quota exceeded. Upgrade your plan.");
}
catch (NodiumApiException ex) when (ex.ErrorCode == "concurrency_limit_reached")
{
    Console.Error.WriteLine("Too many simultaneous requests. Try again shortly.");
}
catch (NodiumApiException ex) when (ex.ErrorCode is "access_key_required" or "access_key_invalid")
{
    Console.Error.WriteLine($"Authentication failed: {ex.Message}");
}
catch (NodiumApiException ex) when (ex.IsRetryable)
{
    Console.Error.WriteLine($"Server error (retryable): {ex.Message}");
}
catch (NodiumApiException ex)
{
    Console.Error.WriteLine($"API error [{ex.ErrorCode}]: {ex.Message}");
}
catch (TaskCanceledException)
{
    Console.Error.WriteLine("Request timed out.");
}
catch (HttpRequestException ex)
{
    Console.Error.WriteLine($"Network error: {ex.Message}");
}

ASP.NET Core Integration

Configuration

json
// appsettings.json
{
  "Nodium": {
    "ApiKey": "YOUR_API_KEY",
    "Timeout": 60,
    "MaxRetries": 3
  }
}

Settings Class

csharp
public class NodiumSettings
{
    public string ApiKey { get; set; } = string.Empty;
    public int Timeout { get; set; } = 60;
    public int MaxRetries { get; set; } = 3;
}

Dependency Injection Setup

Register NodiumClient as a singleton service in Program.cs:

csharp
// Program.cs
var builder = WebApplication.CreateBuilder(args);

var nodiumSettings = builder.Configuration
    .GetSection("Nodium")
    .Get<NodiumSettings>()!;

builder.Services.AddSingleton(
    new NodiumClient(nodiumSettings.ApiKey, nodiumSettings.MaxRetries, nodiumSettings.Timeout)
);

builder.Services.AddControllers();

var app = builder.Build();
app.MapControllers();
app.Run();

Controller

csharp
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class ScreenshotsController : ControllerBase
{
    private readonly NodiumClient _nodium;

    public ScreenshotsController(NodiumClient nodium)
    {
        _nodium = nodium;
    }

    [HttpGet("capture")]
    public async Task<IActionResult> Capture([FromQuery] string url)
    {
        if (string.IsNullOrWhiteSpace(url))
            return BadRequest(new { error = "url parameter is required" });

        try
        {
            var screenshot = await _nodium.TakeAsync(new
            {
                url,
                format = "png",
                viewport_width = 1280,
                viewport_height = 1024,
            });

            return File(screenshot, "image/png", "screenshot.png");
        }
        catch (NodiumApiException ex)
        {
            return StatusCode(ex.HttpStatus, new
            {
                error = ex.ErrorCode,
                message = ex.Message,
            });
        }
    }

    [HttpPost("capture")]
    public async Task<IActionResult> CapturePost([FromBody] CaptureRequest request)
    {
        try
        {
            var screenshot = await _nodium.TakeAsync(new
            {
                url = request.Url,
                format = request.Format ?? "png",
                viewport_width = request.ViewportWidth ?? 1280,
                viewport_height = request.ViewportHeight ?? 1024,
                full_page = request.FullPage ?? false,
            });

            var contentType = request.Format switch
            {
                "jpeg" => "image/jpeg",
                "webp" => "image/webp",
                "pdf"  => "application/pdf",
                _      => "image/png",
            };

            return File(screenshot, contentType, $"screenshot.{request.Format ?? "png"}");
        }
        catch (NodiumApiException ex)
        {
            return StatusCode(ex.HttpStatus, new
            {
                error = ex.ErrorCode,
                message = ex.Message,
            });
        }
    }
}

public record CaptureRequest
{
    public string Url { get; init; } = string.Empty;
    public string? Format { get; init; }
    public int? ViewportWidth { get; init; }
    public int? ViewportHeight { get; init; }
    public bool? FullPage { get; init; }
}

Signed URL Generation

Generate a signed URL by computing an HMAC-SHA256 signature over the query parameters.

csharp
using System.Security.Cryptography;
using System.Text;
using System.Web;

static string GenerateSignedUrl(
    string apiKey,
    string secretKey,
    Dictionary<string, string> parameters,
    TimeSpan expiresIn)
{
    var expires = DateTimeOffset.UtcNow.Add(expiresIn).ToUnixTimeSeconds();

    parameters["access_key"] = apiKey;
    parameters["expires"] = expires.ToString();

    // Sort parameters for consistent signing
    var sorted = parameters.OrderBy(p => p.Key).ToList();
    var stringToSign = string.Join("&", sorted.Select(p => $"{p.Key}={p.Value}"));

    // Compute HMAC-SHA256 signature
    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secretKey));
    var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign));
    var signature = Convert.ToHexString(hash).ToLower();

    // Build the final URL
    var query = HttpUtility.ParseQueryString(string.Empty);
    foreach (var (key, value) in sorted)
        query[key] = value;
    query["signature"] = signature;

    return $"https://api.nodium.io/api/v1/screenshot/take?{query}";
}

// Usage
var signedUrl = GenerateSignedUrl(
    "YOUR_API_KEY",
    "YOUR_SECRET_KEY",
    new Dictionary<string, string>
    {
        ["url"] = "https://example.com",
        ["format"] = "png",
        ["viewport_width"] = "1280",
        ["viewport_height"] = "1024",
    },
    TimeSpan.FromHours(1)
);

Console.WriteLine(signedUrl);

Next Steps