Screenshot -- Go Code Examples

HTTP-based examples for integrating the Nodium Screenshot API with Go using the net/http standard library. No third-party SDK required.

Basic Screenshot (GET)

The simplest way to capture a screenshot using query parameters.

go
package main

import (
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
)

func main() {
	params := url.Values{}
	params.Set("url", "https://example.com")
	params.Set("format", "png")
	params.Set("viewport_width", "1280")
	params.Set("viewport_height", "1024")

	reqURL := "https://api.nodium.io/api/v1/screenshot/take?" + params.Encode()

	req, err := http.NewRequest("GET", reqURL, nil)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to create request: %v\n", err)
		os.Exit(1)
	}

	req.Header.Set("X-Access-Key", "YOUR_API_KEY")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Request failed: %v\n", err)
		os.Exit(1)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		fmt.Fprintf(os.Stderr, "API error (%d): %s\n", resp.StatusCode, body)
		os.Exit(1)
	}

	file, err := os.Create("screenshot.png")
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to create file: %v\n", err)
		os.Exit(1)
	}
	defer file.Close()

	written, err := io.Copy(file, resp.Body)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to write file: %v\n", err)
		os.Exit(1)
	}

	fmt.Printf("Screenshot saved (%d bytes)\n", written)
}

POST with JSON Body

Use POST for more complex configurations with a JSON request body.

go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
)

type ScreenshotRequest struct {
	URL            string `json:"url"`
	Format         string `json:"format"`
	ViewportWidth  int    `json:"viewport_width"`
	ViewportHeight int    `json:"viewport_height"`
	FullPage       bool   `json:"full_page"`
	ImageQuality   int    `json:"image_quality"`
	BlockAds       bool   `json:"block_ads"`
	Delay          int    `json:"delay,omitempty"`
}

type APIError struct {
	ErrorCode      string `json:"error_code"`
	Message        string `json:"message"`
	HTTPStatusCode int    `json:"http_status_code"`
}

func takeScreenshot(params ScreenshotRequest, apiKey string) ([]byte, error) {
	body, err := json.Marshal(params)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal request: %w", err)
	}

	req, err := http.NewRequest("POST", "https://api.nodium.io/api/v1/screenshot/take", bytes.NewReader(body))
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-Access-Key", apiKey)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("request failed: %w", err)
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		var apiErr APIError
		if err := json.Unmarshal(respBody, &apiErr); err == nil {
			return nil, fmt.Errorf("API error [%s]: %s", apiErr.ErrorCode, apiErr.Message)
		}
		return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, respBody)
	}

	return respBody, nil
}

func main() {
	data, err := takeScreenshot(ScreenshotRequest{
		URL:            "https://example.com",
		Format:         "png",
		ViewportWidth:  1440,
		ViewportHeight: 900,
		FullPage:       true,
		ImageQuality:   90,
		BlockAds:       true,
	}, "YOUR_API_KEY")

	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		os.Exit(1)
	}

	if err := os.WriteFile("screenshot.png", data, 0644); err != nil {
		fmt.Fprintf(os.Stderr, "Failed to save file: %v\n", err)
		os.Exit(1)
	}

	fmt.Printf("Screenshot saved (%d bytes)\n", len(data))
}

Error Handling with Retries

Implement exponential backoff for retryable server errors.

go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"math"
	"net/http"
	"os"
	"time"
)

func takeScreenshotWithRetry(payload []byte, apiKey string, maxRetries int) ([]byte, http.Header, error) {
	client := &http.Client{Timeout: 60 * time.Second}

	for attempt := 0; attempt <= maxRetries; attempt++ {
		req, err := http.NewRequest("POST", "https://api.nodium.io/api/v1/screenshot/take", bytes.NewReader(payload))
		if err != nil {
			return nil, nil, err
		}

		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("X-Access-Key", apiKey)

		resp, err := client.Do(req)
		if err != nil {
			if attempt < maxRetries {
				delay := time.Duration(math.Pow(2, float64(attempt))) * time.Second
				time.Sleep(delay)
				continue
			}
			return nil, nil, err
		}
		defer resp.Body.Close()

		body, err := io.ReadAll(resp.Body)
		if err != nil {
			return nil, nil, err
		}

		if resp.StatusCode == http.StatusOK {
			return body, resp.Header, nil
		}

		// Do not retry client errors (4xx)
		if resp.StatusCode >= 400 && resp.StatusCode < 500 {
			var apiErr struct {
				ErrorCode string `json:"error_code"`
				Message   string `json:"message"`
			}
			json.Unmarshal(body, &apiErr)
			return nil, nil, fmt.Errorf("[%s] %s", apiErr.ErrorCode, apiErr.Message)
		}

		// Retry server errors (5xx) with exponential backoff
		if attempt < maxRetries {
			delay := time.Duration(math.Pow(2, float64(attempt))) * time.Second
			fmt.Printf("Retry %d/%d after %v...\n", attempt+1, maxRetries, delay)
			time.Sleep(delay)
		}
	}

	return nil, nil, fmt.Errorf("max retries (%d) exceeded", maxRetries)
}

func main() {
	payload, _ := json.Marshal(map[string]any{
		"url":             "https://example.com",
		"format":          "png",
		"viewport_width":  1280,
		"viewport_height": 1024,
	})

	data, headers, err := takeScreenshotWithRetry(payload, "YOUR_API_KEY", 3)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		os.Exit(1)
	}

	os.WriteFile("screenshot.png", data, 0644)

	fmt.Printf("Saved (%s bytes, rendered in %ss)\n",
		headers.Get("X-Nodium-Size-Bytes"),
		headers.Get("X-Nodium-Rendering-Seconds"),
	)
}

Concurrent Screenshots with Goroutines

Capture multiple URLs concurrently using goroutines and channels.

go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"sync"
)

type CaptureResult struct {
	URL      string
	Filename string
	Size     int
	Err      error
}

func captureURL(client *http.Client, apiKey, targetURL, filename string) CaptureResult {
	payload, _ := json.Marshal(map[string]any{
		"url":             targetURL,
		"format":          "png",
		"viewport_width":  1280,
		"viewport_height": 1024,
	})

	req, err := http.NewRequest("POST", "https://api.nodium.io/api/v1/screenshot/take", bytes.NewReader(payload))
	if err != nil {
		return CaptureResult{URL: targetURL, Err: err}
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-Access-Key", apiKey)

	resp, err := client.Do(req)
	if err != nil {
		return CaptureResult{URL: targetURL, Err: err}
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return CaptureResult{URL: targetURL, Err: err}
	}

	if resp.StatusCode != http.StatusOK {
		return CaptureResult{URL: targetURL, Err: fmt.Errorf("HTTP %d: %s", resp.StatusCode, body)}
	}

	if err := os.WriteFile(filename, body, 0644); err != nil {
		return CaptureResult{URL: targetURL, Err: err}
	}

	return CaptureResult{URL: targetURL, Filename: filename, Size: len(body)}
}

func main() {
	apiKey := "YOUR_API_KEY"
	client := &http.Client{}

	urls := []string{
		"https://example.com",
		"https://example.org",
		"https://example.net",
		"https://httpbin.org/html",
	}

	var wg sync.WaitGroup
	results := make(chan CaptureResult, len(urls))

	for i, u := range urls {
		wg.Add(1)
		go func(index int, targetURL string) {
			defer wg.Done()
			filename := fmt.Sprintf("screenshot-%d.png", index)
			results <- captureURL(client, apiKey, targetURL, filename)
		}(i, u)
	}

	go func() {
		wg.Wait()
		close(results)
	}()

	for result := range results {
		if result.Err != nil {
			fmt.Printf("[FAIL] %s: %v\n", result.URL, result.Err)
		} else {
			fmt.Printf("[OK]   %s -> %s (%d bytes)\n", result.URL, result.Filename, result.Size)
		}
	}
}

Signed URL Generation

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

go
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"
)

func generateSignedURL(apiKey, secretKey string, params map[string]string, expiresIn time.Duration) string {
	// Set expiration timestamp
	expires := time.Now().Add(expiresIn).Unix()
	params["access_key"] = apiKey
	params["expires"] = strconv.FormatInt(expires, 10)

	// Sort parameter keys for consistent signing
	keys := make([]string, 0, len(params))
	for k := range params {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	// Build the string to sign
	var parts []string
	for _, k := range keys {
		parts = append(parts, k+"="+params[k])
	}
	stringToSign := strings.Join(parts, "&")

	// Compute HMAC-SHA256 signature
	mac := hmac.New(sha256.New, []byte(secretKey))
	mac.Write([]byte(stringToSign))
	signature := hex.EncodeToString(mac.Sum(nil))

	// Build the final URL
	query := url.Values{}
	for _, k := range keys {
		query.Set(k, params[k])
	}
	query.Set("signature", signature)

	return "https://api.nodium.io/api/v1/screenshot/take?" + query.Encode()
}

func main() {
	signedURL := generateSignedURL(
		"YOUR_API_KEY",
		"YOUR_SECRET_KEY",
		map[string]string{
			"url":             "https://example.com",
			"format":          "png",
			"viewport_width":  "1280",
			"viewport_height": "1024",
		},
		1*time.Hour,
	)

	fmt.Println(signedURL)
}

JSON Response Mode

Request a JSON response instead of raw binary to get the screenshot URL and metadata.

go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

type JSONResponse struct {
	ScreenshotURL string `json:"screenshot_url"`
	CacheURL      string `json:"cache_url"`
	Metadata      struct {
		ImageSize struct {
			Width  int `json:"width"`
			Height int `json:"height"`
		} `json:"image_size"`
		PageTitle string `json:"page_title"`
	} `json:"metadata"`
}

func main() {
	payload, _ := json.Marshal(map[string]any{
		"url":           "https://example.com",
		"format":        "png",
		"response_type": "json",
	})

	req, _ := http.NewRequest("POST", "https://api.nodium.io/api/v1/screenshot/take", bytes.NewReader(payload))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-Access-Key", "YOUR_API_KEY")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Request failed: %v\n", err)
		os.Exit(1)
	}
	defer resp.Body.Close()

	var result JSONResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		fmt.Fprintf(os.Stderr, "Failed to decode response: %v\n", err)
		os.Exit(1)
	}

	fmt.Printf("Screenshot URL: %s\n", result.ScreenshotURL)
	fmt.Printf("Page title: %s\n", result.Metadata.PageTitle)
	fmt.Printf("Dimensions: %dx%d\n", result.Metadata.ImageSize.Width, result.Metadata.ImageSize.Height)
}

Next Steps