Skip to main content

Go SDK

The official Go SDK for Capture API provides idiomatic Go interfaces for all capture operations including screenshots, PDFs, content extraction, and animated screenshots.

Installation

go mod init your-project
go get github.com/techulus/capture-go

Quick Start

package main

import (
"fmt"
"os"

"github.com/techulus/capture-go"
)

func main() {
// Initialize client
client := capture.NewClient("your-api-key", "your-api-secret")

// Take a screenshot
screenshot, err := client.Screenshot("https://example.com", nil)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}

// Save to file
err = os.WriteFile("screenshot.png", screenshot, 0644)
if err != nil {
fmt.Printf("Error saving file: %v\n", err)
}
}

Authentication

Basic Setup

package main

import (
"os"
"github.com/techulus/capture-go"
)

func main() {
client := capture.NewClient(
os.Getenv("CAPTURE_API_KEY"),
os.Getenv("CAPTURE_API_SECRET"),
)
}

Custom Configuration

client := capture.NewClientWithConfig(&capture.Config{
APIKey: "your-api-key",
Secret: "your-api-secret",
BaseURL: "https://edge.capture.page", // Use edge endpoint
Timeout: 60 * time.Second, // 60 second timeout
UserAgent: "MyApp/1.0",
})

Screenshots

Basic Screenshot

package main

import (
"fmt"
"os"
"github.com/techulus/capture-go"
)

func main() {
client := capture.NewClient("your-api-key", "your-api-secret")

// Simple screenshot
imageData, err := client.Screenshot("https://example.com", nil)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}

// Save to file
err = os.WriteFile("screenshot.png", imageData, 0644)
if err != nil {
fmt.Printf("Error saving: %v\n", err)
}
}

Advanced Screenshot Options

func advancedScreenshot() {
client := capture.NewClient("your-api-key", "your-api-secret")

options := &capture.ScreenshotOptions{
// Viewport settings
ViewportWidth: 1920,
ViewportHeight: 1080,
ScaleFactor: 2,

// Output options
Format: "png",
ResizeWidth: 800,
ResizeHeight: 600,

// Timing
Delay: 3,
WaitFor: ".content-loaded",

// Page enhancements
DarkMode: true,
BlockAds: true,
BlockCookieBanners: true,

// Capture area
FullPage: true,
Selector: ".main-content",

// Storage
FileName: "homepage-screenshot",
S3ACL: "public-read",
S3Redirect: true,
}

screenshot, err := client.Screenshot("https://example.com", options)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}

fmt.Printf("Screenshot captured: %d bytes\n", len(screenshot))
}

Device Emulation

func deviceEmulation() {
client := capture.NewClient("your-api-key", "your-api-secret")

// Mobile screenshot
mobileOptions := &capture.ScreenshotOptions{
EmulateDevice: "iphone_15_pro",
}
mobileScreenshot, err := client.Screenshot("https://example.com", mobileOptions)
if err != nil {
fmt.Printf("Mobile screenshot error: %v\n", err)
return
}

// Tablet screenshot
tabletOptions := &capture.ScreenshotOptions{
EmulateDevice: "ipad_air",
}
tabletScreenshot, err := client.Screenshot("https://example.com", tabletOptions)
if err != nil {
fmt.Printf("Tablet screenshot error: %v\n", err)
return
}

// Get available devices
devices, err := client.GetDevices()
if err != nil {
fmt.Printf("Error getting devices: %v\n", err)
return
}

fmt.Printf("Available devices: %d\n", len(devices.Devices))
}

Element-Specific Screenshots

func elementScreenshots() {
client := capture.NewClient("your-api-key", "your-api-secret")

// Capture specific element
elementOptions := &capture.ScreenshotOptions{
Selector: ".hero-section",
Transparent: true,
Format: "png",
}
elementScreenshot, err := client.Screenshot("https://example.com", elementOptions)
if err != nil {
fmt.Printf("Element screenshot error: %v\n", err)
return
}

// Capture by ID
idOptions := &capture.ScreenshotOptions{
SelectorID: "main-content",
}
idScreenshot, err := client.Screenshot("https://example.com", idOptions)
if err != nil {
fmt.Printf("ID screenshot error: %v\n", err)
return
}

fmt.Printf("Element screenshot: %d bytes\n", len(elementScreenshot))
fmt.Printf("ID screenshot: %d bytes\n", len(idScreenshot))
}

PDF Generation

Basic PDF

func basicPDF() {
client := capture.NewClient("your-api-key", "your-api-secret")

pdfData, err := client.PDF("https://example.com", nil)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}

// Save to file
err = os.WriteFile("document.pdf", pdfData, 0644)
if err != nil {
fmt.Printf("Error saving PDF: %v\n", err)
}
}

PDF with Custom Options

func customPDF() {
client := capture.NewClient("your-api-key", "your-api-secret")

options := &capture.PDFOptions{
// Paper settings
Format: "A4",
Landscape: false,

// Margins (all required together)
MarginTop: "1in",
MarginRight: "1in",
MarginBottom: "1in",
MarginLeft: "1in",

// Rendering
Scale: 0.8,
PrintBackground: true,

// Timing
Delay: 2,

// Storage
FileName: "report-2024",
S3ACL: "private",
}

pdf, err := client.PDF("https://example.com", options)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}

fmt.Printf("PDF generated: %d bytes\n", len(pdf))
}

Custom Paper Size

func customPaperPDF() {
client := capture.NewClient("your-api-key", "your-api-secret")

options := &capture.PDFOptions{
Width: "8.5in",
Height: "11in",
MarginTop: "0.5in",
MarginRight: "0.5in",
MarginBottom: "0.5in",
MarginLeft: "0.5in",
PrintBackground: true,
}

pdf, err := client.PDF("https://example.com", options)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}

err = os.WriteFile("custom-size.pdf", pdf, 0644)
if err != nil {
fmt.Printf("Error saving: %v\n", err)
}
}

Content Extraction

HTML Content

func extractContent() {
client := capture.NewClient("your-api-key", "your-api-secret")

content, err := client.Content("https://example.com", nil)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}

fmt.Printf("Success: %t\n", content.Success)
fmt.Printf("HTML length: %d\n", len(content.HTML))
fmt.Printf("Text content: %s\n", content.TextContent)
}

Content with Options

func contentWithOptions() {
client := capture.NewClient("your-api-key", "your-api-secret")

options := &capture.ContentOptions{
Delay: 3,
WaitFor: ".article-content",
UserAgent: "CustomBot/1.0",
}

content, err := client.Content("https://example.com", options)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}

// Process the content
cleanText := strings.TrimSpace(
strings.ReplaceAll(content.TextContent, "\n", " "),
)

fmt.Printf("Clean text: %s\n", cleanText)
}

Metadata Extraction

Basic Metadata

func extractMetadata() {
client := capture.NewClient("your-api-key", "your-api-secret")

metadata, err := client.Metadata("https://example.com")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}

fmt.Printf("Title: %s\n", metadata.Metadata.Title)
fmt.Printf("Description: %s\n", metadata.Metadata.Description)
fmt.Printf("Image: %s\n", metadata.Metadata.Image)
fmt.Printf("Author: %s\n", metadata.Metadata.Author)
fmt.Printf("Language: %s\n", metadata.Metadata.Lang)
}

Social Media Tags

func socialMetadata() {
client := capture.NewClient("your-api-key", "your-api-secret")

metadata, err := client.Metadata("https://blog.example.com/post")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}

// Open Graph data
fmt.Printf("OG Title: %s\n", metadata.Metadata.Title)
fmt.Printf("OG Description: %s\n", metadata.Metadata.Description)
fmt.Printf("OG Image: %s\n", metadata.Metadata.Image)

// Publisher info
fmt.Printf("Publisher: %s\n", metadata.Metadata.Publisher)
fmt.Printf("URL: %s\n", metadata.Metadata.URL)
}

Animated Screenshots

Basic Animation

func basicAnimation() {
client := capture.NewClient("your-api-key", "your-api-secret")

options := &capture.AnimatedOptions{
Format: "mp4",
Duration: 10,
FPS: 30,
}

animation, err := client.Animated("https://example.com", options)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}

// Save animation
err = os.WriteFile("animation.mp4", animation, 0644)
if err != nil {
fmt.Printf("Error saving animation: %v\n", err)
}
}

Scrolling Animation

func scrollingAnimation() {
client := capture.NewClient("your-api-key", "your-api-secret")

options := &capture.AnimatedOptions{
Format: "gif",
Duration: 15,
FPS: 24,
Scrolling: true,
ScrollSpeed: 300,
HideScrollbars: true,
}

animation, err := client.Animated("https://example.com", options)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}

err = os.WriteFile("scroll-animation.gif", animation, 0644)
if err != nil {
fmt.Printf("Error saving: %v\n", err)
}
}

Mobile Animation

func mobileAnimation() {
client := capture.NewClient("your-api-key", "your-api-secret")

options := &capture.AnimatedOptions{
EmulateDevice: "iphone_15_pro",
Format: "mp4",
Duration: 12,
FPS: 30,
DarkMode: true,
}

animation, err := client.Animated("https://example.com", options)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}

fmt.Printf("Mobile animation captured: %d bytes\n", len(animation))
}

Batch Operations

Parallel Screenshots

func parallelScreenshots() {
client := capture.NewClient("your-api-key", "your-api-secret")

urls := []string{
"https://example.com",
"https://example.com/about",
"https://example.com/contact",
}

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

type Result struct {
URL string
Data []byte
Err error
}

for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()

options := &capture.ScreenshotOptions{
ViewportWidth: 1200,
ViewportHeight: 800,
FileName: fmt.Sprintf("screenshot-%s", strings.ReplaceAll(u, "/", "-")),
S3ACL: "public-read",
}

data, err := client.Screenshot(u, options)
results <- Result{URL: u, Data: data, Err: err}
}(url)
}

wg.Wait()
close(results)

for result := range results {
if result.Err != nil {
fmt.Printf("Error capturing %s: %v\n", result.URL, result.Err)
} else {
fmt.Printf("Captured %s: %d bytes\n", result.URL, len(result.Data))
}
}
}

Sequential Processing with Rate Limiting

func sequentialProcessing() {
client := capture.NewClient("your-api-key", "your-api-secret")

urls := []string{
"https://example.com",
"https://example.com/about",
"https://example.com/contact",
}

results := make([][]byte, 0, len(urls))

for i, url := range urls {
screenshot, err := client.Screenshot(url, nil)
if err != nil {
fmt.Printf("Error capturing %s: %v\n", url, err)
continue
}

results = append(results, screenshot)

// Rate limiting - avoid overwhelming the server
if i < len(urls)-1 {
time.Sleep(1 * time.Second)
}
}

fmt.Printf("Captured %d screenshots\n", len(results))
}

Error Handling

Basic Error Handling

func handleErrors() {
client := capture.NewClient("your-api-key", "your-api-secret")

screenshot, err := client.Screenshot("https://example.com", nil)
if err != nil {
// Check if it's a Capture API error
if captureErr, ok := err.(*capture.Error); ok {
fmt.Printf("Capture API Error:\n")
fmt.Printf(" Status: %d\n", captureErr.StatusCode)
fmt.Printf(" Message: %s\n", captureErr.Message)
fmt.Printf(" Details: %s\n", captureErr.Details)
return
}

// Generic error
fmt.Printf("Generic error: %v\n", err)
return
}

fmt.Printf("Screenshot captured successfully: %d bytes\n", len(screenshot))
}

Retry Logic

func captureWithRetry(client *capture.Client, url string, options *capture.ScreenshotOptions, maxRetries int) ([]byte, error) {
var lastErr error

for attempt := 1; attempt <= maxRetries; attempt++ {
screenshot, err := client.Screenshot(url, options)
if err == nil {
return screenshot, nil
}

lastErr = err

// Check if it's worth retrying
if captureErr, ok := err.(*capture.Error); ok {
// Don't retry client errors (4xx)
if captureErr.StatusCode >= 400 && captureErr.StatusCode < 500 {
break
}
}

if attempt < maxRetries {
waitTime := time.Duration(attempt) * time.Second
fmt.Printf("Attempt %d failed, retrying in %v...\n", attempt, waitTime)
time.Sleep(waitTime)
}
}

return nil, fmt.Errorf("failed after %d attempts: %w", maxRetries, lastErr)
}

Comprehensive Error Types

func handleSpecificErrors() {
client := capture.NewClient("your-api-key", "your-api-secret")

_, err := client.Screenshot("https://invalid-url", nil)
if err != nil {
if captureErr, ok := err.(*capture.Error); ok {
switch captureErr.StatusCode {
case 400:
fmt.Println("Invalid request parameters")
case 401:
fmt.Println("Authentication failed - check API credentials")
case 403:
fmt.Println("Insufficient credits or forbidden")
case 429:
fmt.Println("Rate limit exceeded - slow down requests")
case 500:
fmt.Println("Server error - try again later")
default:
fmt.Printf("Unknown API error: %d - %s\n", captureErr.StatusCode, captureErr.Message)
}
} else {
fmt.Printf("Network or other error: %v\n", err)
}
}
}

Integration Examples

HTTP Server

package main

import (
"fmt"
"log"
"net/http"
"strconv"

"github.com/techulus/capture-go"
)

func main() {
client := capture.NewClient(
os.Getenv("CAPTURE_API_KEY"),
os.Getenv("CAPTURE_API_SECRET"),
)

http.HandleFunc("/screenshot", func(w http.ResponseWriter, r *http.Request) {
url := r.URL.Query().Get("url")
if url == "" {
http.Error(w, "Missing url parameter", http.StatusBadRequest)
return
}

widthStr := r.URL.Query().Get("width")
heightStr := r.URL.Query().Get("height")

options := &capture.ScreenshotOptions{}
if widthStr != "" {
if width, err := strconv.Atoi(widthStr); err == nil {
options.ViewportWidth = width
}
}
if heightStr != "" {
if height, err := strconv.Atoi(heightStr); err == nil {
options.ViewportHeight = height
}
}

screenshot, err := client.Screenshot(url, options)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "image/png")
w.Write(screenshot)
})

log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}

CLI Tool

package main

import (
"flag"
"fmt"
"os"

"github.com/techulus/capture-go"
)

func main() {
var (
apiKey = flag.String("key", os.Getenv("CAPTURE_API_KEY"), "API key")
apiSecret = flag.String("secret", os.Getenv("CAPTURE_API_SECRET"), "API secret")
url = flag.String("url", "", "URL to capture")
output = flag.String("output", "screenshot.png", "Output filename")
width = flag.Int("width", 1200, "Viewport width")
height = flag.Int("height", 800, "Viewport height")
format = flag.String("format", "png", "Output format")
device = flag.String("device", "", "Device to emulate")
)
flag.Parse()

if *url == "" {
fmt.Println("URL is required")
flag.Usage()
os.Exit(1)
}

client := capture.NewClient(*apiKey, *apiSecret)

options := &capture.ScreenshotOptions{
ViewportWidth: *width,
ViewportHeight: *height,
Format: *format,
}

if *device != "" {
options.EmulateDevice = *device
}

fmt.Printf("Capturing %s...\n", *url)
screenshot, err := client.Screenshot(*url, options)
if err != nil {
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}

err = os.WriteFile(*output, screenshot, 0644)
if err != nil {
fmt.Printf("Error saving file: %v\n", err)
os.Exit(1)
}

fmt.Printf("Screenshot saved to %s (%d bytes)\n", *output, len(screenshot))
}

Worker Pool

package main

import (
"fmt"
"sync"
"time"

"github.com/techulus/capture-go"
)

type Job struct {
URL string
Options *capture.ScreenshotOptions
}

type Result struct {
Job Job
Data []byte
Err error
}

type WorkerPool struct {
client *capture.Client
jobs chan Job
results chan Result
workers int
wg sync.WaitGroup
}

func NewWorkerPool(client *capture.Client, workers int) *WorkerPool {
return &WorkerPool{
client: client,
jobs: make(chan Job, workers*2),
results: make(chan Result, workers*2),
workers: workers,
}
}

func (p *WorkerPool) Start() {
for i := 0; i < p.workers; i++ {
p.wg.Add(1)
go p.worker()
}
}

func (p *WorkerPool) worker() {
defer p.wg.Done()

for job := range p.jobs {
// Rate limiting per worker
time.Sleep(200 * time.Millisecond)

data, err := p.client.Screenshot(job.URL, job.Options)
p.results <- Result{
Job: job,
Data: data,
Err: err,
}
}
}

func (p *WorkerPool) AddJob(url string, options *capture.ScreenshotOptions) {
p.jobs <- Job{URL: url, Options: options}
}

func (p *WorkerPool) Stop() {
close(p.jobs)
p.wg.Wait()
close(p.results)
}

func (p *WorkerPool) Results() <-chan Result {
return p.results
}

func main() {
client := capture.NewClient("your-api-key", "your-api-secret")
pool := NewWorkerPool(client, 5) // 5 workers

pool.Start()

// Add jobs
urls := []string{
"https://example.com",
"https://example.com/about",
"https://example.com/contact",
}

for _, url := range urls {
pool.AddJob(url, &capture.ScreenshotOptions{
ViewportWidth: 1200,
ViewportHeight: 800,
})
}

// Collect results
go func() {
for result := range pool.Results() {
if result.Err != nil {
fmt.Printf("Error capturing %s: %v\n", result.Job.URL, result.Err)
} else {
fmt.Printf("Captured %s: %d bytes\n", result.Job.URL, len(result.Data))
}
}
}()

pool.Stop()
}

Performance Optimization

Connection Reuse

// Create client once and reuse
var client = capture.NewClientWithConfig(&capture.Config{
APIKey: os.Getenv("CAPTURE_API_KEY"),
Secret: os.Getenv("CAPTURE_API_SECRET"),
Timeout: 30 * time.Second,
MaxIdleConns: 10, // Connection pool
KeepAlive: true,
})

func reuseConnection() {
// Reuse the same client for multiple requests
screenshot1, _ := client.Screenshot("https://example.com", nil)
screenshot2, _ := client.Screenshot("https://example.org", nil)

fmt.Printf("Screenshot 1: %d bytes\n", len(screenshot1))
fmt.Printf("Screenshot 2: %d bytes\n", len(screenshot2))
}

Memory Optimization

func memoryOptimized() {
client := capture.NewClient("your-api-key", "your-api-secret")

// Process large batches in chunks
urls := []string{/* ... many URLs ... */}
chunkSize := 10

for i := 0; i < len(urls); i += chunkSize {
end := i + chunkSize
if end > len(urls) {
end = len(urls)
}

chunk := urls[i:end]
processChunk(client, chunk)

// Force garbage collection between chunks
runtime.GC()
}
}

func processChunk(client *capture.Client, urls []string) {
for _, url := range urls {
screenshot, err := client.Screenshot(url, nil)
if err != nil {
continue
}

// Process immediately and don't store
processScreenshot(screenshot)

// screenshot goes out of scope and can be GC'd
}
}

Best Practices

1. Configuration Management

type Config struct {
CaptureAPIKey string `env:"CAPTURE_API_KEY"`
CaptureAPISecret string `env:"CAPTURE_API_SECRET"`
CaptureTimeout int `env:"CAPTURE_TIMEOUT" envDefault:"30"`
}

func loadConfig() *Config {
cfg := &Config{}
// Use a library like env to load from environment
return cfg
}

func main() {
cfg := loadConfig()

client := capture.NewClientWithConfig(&capture.Config{
APIKey: cfg.CaptureAPIKey,
Secret: cfg.CaptureAPISecret,
Timeout: time.Duration(cfg.CaptureTimeout) * time.Second,
})
}

2. Structured Logging

import "log/slog"

func captureWithLogging(client *capture.Client, url string) ([]byte, error) {
logger := slog.Default()

logger.Info("Starting capture", "url", url)
start := time.Now()

screenshot, err := client.Screenshot(url, nil)
duration := time.Since(start)

if err != nil {
logger.Error("Capture failed",
"url", url,
"error", err,
"duration", duration,
)
return nil, err
}

logger.Info("Capture successful",
"url", url,
"size", len(screenshot),
"duration", duration,
)

return screenshot, nil
}

3. Circuit Breaker Pattern

type CircuitBreaker struct {
client *capture.Client
failures int
lastAttempt time.Time
threshold int
timeout time.Duration
mutex sync.Mutex
}

func NewCircuitBreaker(client *capture.Client) *CircuitBreaker {
return &CircuitBreaker{
client: client,
threshold: 5,
timeout: 30 * time.Second,
}
}

func (cb *CircuitBreaker) Screenshot(url string, options *capture.ScreenshotOptions) ([]byte, error) {
cb.mutex.Lock()
defer cb.mutex.Unlock()

// Check if circuit is open
if cb.failures >= cb.threshold {
if time.Since(cb.lastAttempt) < cb.timeout {
return nil, fmt.Errorf("circuit breaker open")
}
// Reset after timeout
cb.failures = 0
}

cb.lastAttempt = time.Now()

screenshot, err := cb.client.Screenshot(url, options)
if err != nil {
cb.failures++
return nil, err
}

// Reset on success
cb.failures = 0
return screenshot, nil
}

Troubleshooting

Common Issues

Import Path Errors

// Correct import
import "github.com/techulus/capture-go"

// Make sure you've run: go mod tidy

Authentication Failures

func testAuth() {
client := capture.NewClient("your-api-key", "your-api-secret")

// Test with a simple request
_, err := client.Screenshot("https://example.com", nil)
if err != nil {
if captureErr, ok := err.(*capture.Error); ok && captureErr.StatusCode == 401 {
fmt.Println("Authentication failed - check your API credentials")
}
} else {
fmt.Println("Authentication successful")
}
}

Timeout Issues

// Increase timeout for slow pages
client := capture.NewClientWithConfig(&capture.Config{
APIKey: "your-api-key",
Secret: "your-api-secret",
Timeout: 120 * time.Second, // 2 minutes
})

See Also