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
- Calls
GET /api/v1/screenshot/createwith your API key and target URL. - Reads the JSON response, extracts
data.id, and pollsGET /api/v1/screenshot/infountildata.statusisfinishedorerror. - Downloads the image with
GET /api/v1/screenshot/thumbnail(full-resolution PNG when no width is set). - Writes
screenshot.pngnext 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
| Step | Method | Path | Notes |
|---|---|---|---|
| Create job | GET | /api/v1/screenshot/create | Returns immediately with processing or finished. |
| Status | GET | /api/v1/screenshot/info | Query id from create response. |
| Download | GET | /api/v1/screenshot/thumbnail | Binary 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.