Skip to content

Go Screenshot API: End-to-End Example

Build a small Go program that requests a screenshot from ScreenshotCenter, polls until it finishes, downloads the PNG, and handles API errors — using only the standard library.

Why Go and the REST API?

Go shines for CLI tools, workers, and microservices that need reliable HTTP, clear error handling, and a single static binary. The ScreenshotCenter API is plain HTTPS JSON plus binary download endpoints, so you do not need a framework — net/http and encoding/json are enough.

For higher-level helpers (typed parameters, retries), see the official Go SDK. This article walks through the raw API so you understand every step. Parameter reference lives in the developer documentation.

What the program does

  1. Calls GET /api/v1/screenshot/create with your API key and target URL.
  2. Reads the JSON response, extracts data.id, and polls GET /api/v1/screenshot/info until data.status is finished or error.
  3. Downloads the image with GET /api/v1/screenshot/thumbnail (full-resolution PNG when no width is set).
  4. Writes screenshot.png next to the binary and exits non-zero on failure.

Prerequisites

Install Go 1.21 or newer, export a valid API key, and ensure outbound HTTPS is allowed from the machine running the binary. The example uses a two-second poll interval and a five-minute upper bound — tune both constants for slower pages or stricter SLAs.

Endpoints you will use

StepMethodPathNotes
Create jobGET/api/v1/screenshot/createReturns immediately with processing or finished.
StatusGET/api/v1/screenshot/infoQuery id from create response.
DownloadGET/api/v1/screenshot/thumbnailBinary PNG body; use width to resize.

Complete program

Save as main.go, set SCREENSHOTCENTER_API_KEY, then run go run . https://example.com.

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"strconv"
	"time"
)

const apiBase = "https://api.screenshotcenter.com/api/v1"

type envelope struct {
	Success bool            `json:"success"`
	Data    json.RawMessage `json:"data"`
	Message string          `json:"message"`
}

type screenshotData struct {
	ID     int64  `json:"id"`
	Status string `json:"status"`
	Error  string `json:"error"`
}

func mustEnv(key string) string {
	v := os.Getenv(key)
	if v == "" {
		fmt.Fprintf(os.Stderr, "missing env %s\n", key)
		os.Exit(1)
	}
	return v
}

func decodeData(raw json.RawMessage, into any) error {
	if len(raw) == 0 {
		return fmt.Errorf("empty data")
	}
	return json.Unmarshal(raw, into)
}

func createScreenshot(client *http.Client, key, page string) (int64, error) {
	q := url.Values{}
	q.Set("key", key)
	q.Set("url", page)
	q.Set("country", "us")
	req, err := http.NewRequest(http.MethodGet, apiBase+"/screenshot/create?"+q.Encode(), nil)
	if err != nil {
		return 0, err
	}
	resp, err := client.Do(req)
	if err != nil {
		return 0, err
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return 0, err
	}
	if resp.StatusCode != http.StatusOK {
		return 0, fmt.Errorf("create: HTTP %d: %s", resp.StatusCode, string(body))
	}
	var env envelope
	if err := json.Unmarshal(body, &env); err != nil {
		return 0, err
	}
	if !env.Success {
		return 0, fmt.Errorf("create: success=false: %s", string(body))
	}
	var data screenshotData
	if err := decodeData(env.Data, &data); err != nil {
		return 0, err
	}
	if data.ID == 0 {
		return 0, fmt.Errorf("create: missing id")
	}
	return data.ID, nil
}

func screenshotInfo(client *http.Client, key string, id int64) (*screenshotData, error) {
	q := url.Values{}
	q.Set("key", key)
	q.Set("id", strconv.FormatInt(id, 10))
	req, err := http.NewRequest(http.MethodGet, apiBase+"/screenshot/info?"+q.Encode(), nil)
	if err != nil {
		return nil, err
	}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("info: HTTP %d: %s", resp.StatusCode, string(body))
	}
	var env envelope
	if err := json.Unmarshal(body, &env); err != nil {
		return nil, err
	}
	if !env.Success {
		return nil, fmt.Errorf("info: success=false: %s", string(body))
	}
	var data screenshotData
	if err := decodeData(env.Data, &data); err != nil {
		return nil, err
	}
	return &data, nil
}

func waitFinished(client *http.Client, key string, id int64, poll time.Duration, max time.Duration) (*screenshotData, error) {
	deadline := time.Now().Add(max)
	for time.Now().Before(deadline) {
		info, err := screenshotInfo(client, key, id)
		if err != nil {
			return nil, err
		}
		switch info.Status {
		case "finished":
			return info, nil
		case "error":
			if info.Error != "" {
				return nil, fmt.Errorf("screenshot error: %s", info.Error)
			}
			return nil, fmt.Errorf("screenshot error")
		case "processing":
			time.Sleep(poll)
		default:
			time.Sleep(poll)
		}
	}
	return nil, fmt.Errorf("timeout waiting for screenshot %d", id)
}

func downloadThumbnail(client *http.Client, key string, id int64, dest string) error {
	q := url.Values{}
	q.Set("key", key)
	q.Set("id", strconv.FormatInt(id, 10))
	req, err := http.NewRequest(http.MethodGet, apiBase+"/screenshot/thumbnail?"+q.Encode(), nil)
	if err != nil {
		return err
	}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		b, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("thumbnail: HTTP %d: %s", resp.StatusCode, string(b))
	}
	f, err := os.Create(dest)
	if err != nil {
		return err
	}
	defer f.Close()
	if _, err := io.Copy(f, resp.Body); err != nil {
		return err
	}
	return nil
}

func main() {
	key := mustEnv("SCREENSHOTCENTER_API_KEY")
	target := "https://example.com"
	if len(os.Args) > 1 {
		target = os.Args[1]
	}
	client := &http.Client{Timeout: 120 * time.Second}

	id, err := createScreenshot(client, key, target)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	fmt.Printf("requested screenshot id=%d\n", id)

	info, err := waitFinished(client, key, id, 2*time.Second, 5*time.Minute)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	fmt.Printf("status=%s final_url field available via extended JSON if needed\n", info.Status)

	out := "screenshot.png"
	if err := downloadThumbnail(client, key, id, out); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	fmt.Printf("wrote %s\n", out)
}

Error handling notes

Always check HTTP status before unmarshalling. The API wraps payloads as { success, data, message }; treat success: false as an application-level error even when HTTP is 200.

For production, add structured logging, metrics, and exponential backoff on 5xx responses. Consider the Go SDK if you prefer typed request builders over manual query strings.

Production checklist

  • Redact API keys from logs; print only screenshot IDs.
  • Cap concurrent workers so you stay inside plan rate limits.
  • Persist IDs if you need to resume downloads after process restarts.