Screenshot API Swift Guide 2026

Capture screenshots and generate PDFs from Swift and iOS apps using URLSession, async/await, and SwiftUI with the SnapAPI REST API.

Get Free API Key

Calling SnapAPI from Swift

Swift's modern concurrency model with async/await and URLSession makes calling REST APIs clean and readable. SnapAPI accepts standard HTTP GET requests with a Bearer token Authorization header and returns binary image data or JSON. Both Swift iOS apps and Swift server-side applications using Vapor or Hummingbird can call SnapAPI using the same URLSession-based client code shown below. Add the API key to your app's Info.plist or read it from a configuration file rather than hardcoding it in source code.

// SnapAPIClient.swift
import Foundation

struct SnapAPIClient {
    let apiKey: String
    private let session = URLSession.shared

    func screenshot(
        url: String,
        format: String = "png",
        fullPage: Bool = false,
        viewportWidth: Int = 1280
    ) async throws -> Data {
        var components = URLComponents(string: "https://api.snapapi.pics/screenshot")!
        components.queryItems = [
            URLQueryItem(name: "url", value: url),
            URLQueryItem(name: "format", value: format),
            URLQueryItem(name: "full_page", value: String(fullPage)),
            URLQueryItem(name: "viewport_width", value: String(viewportWidth)),
        ]
        var request = URLRequest(url: components.url!)
        request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
        request.timeoutInterval = 60

        let (data, response) = try await session.data(for: request)
        guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        return data
    }

    func scrape(url: String) async throws -> [String: Any] {
        var components = URLComponents(string: "https://api.snapapi.pics/scrape")!
        components.queryItems = [URLQueryItem(name: "url", value: url)]
        var request = URLRequest(url: components.url!)
        request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")

        let (data, _) = try await session.data(for: request)
        return try JSONSerialization.jsonObject(with: data) as? [String: Any] ?? [:]
    }
}

// Usage
let client = SnapAPIClient(apiKey: ProcessInfo.processInfo.environment["SNAPAPI_KEY"] ?? "")
let imageData = try await client.screenshot(url: "https://example.com", format: "jpeg")
try imageData.write(to: URL(fileURLWithPath: "screenshot.jpg"))

SwiftUI: Display Screenshot in View

In a SwiftUI application, load a screenshot asynchronously on view appear and display it using Image(uiImage:). Use @State to hold the loaded image and a loading state indicator while the screenshot is being captured. This pattern works in iOS apps that need to show live previews of external web pages without embedding a WKWebView.

import SwiftUI

struct ScreenshotView: View {
    let targetURL: String
    @State private var image: UIImage?
    @State private var isLoading = true
    @State private var errorMessage: String?

    let client = SnapAPIClient(apiKey: Bundle.main.infoDictionary?["SNAPAPI_KEY"] as? String ?? "")

    var body: some View {
        Group {
            if isLoading {
                ProgressView("Capturing screenshot...")
            } else if let img = image {
                Image(uiImage: img)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
            } else {
                Text(errorMessage ?? "Failed to load screenshot")
                    .foregroundColor(.secondary)
            }
        }
        .task {
            do {
                let data = try await client.screenshot(
                    url: targetURL,
                    format: "jpeg",
                    viewportWidth: 1280
                )
                image = UIImage(data: data)
            } catch {
                errorMessage = error.localizedDescription
            }
            isLoading = false
        }
    }
}

#Preview {
    ScreenshotView(targetURL: "https://example.com")
}

Concurrent Screenshots with TaskGroup

Swift's structured concurrency model with TaskGroup enables safe concurrent screenshot capture. TaskGroup spawns child tasks and collects their results, automatically cancelling remaining tasks if any task throws an error. Use withThrowingTaskGroup to capture multiple URLs concurrently with configurable parallelism.

func captureAll(urls: [String]) async throws -> [(String, Data)] {
    try await withThrowingTaskGroup(of: (String, Data).self) { group in
        for url in urls {
            group.addTask {
                let data = try await client.screenshot(url: url, format: "png")
                return (url, data)
            }
        }
        var results: [(String, Data)] = []
        for try await result in group {
            results.append(result)
        }
        return results
    }
}

// Usage
let urls = ["https://example.com", "https://swift.org", "https://apple.com"]
let screenshots = try await captureAll(urls: urls)
for (url, data) in screenshots {
    print("\(url): \(data.count) bytes")
}

Vapor Server-Side Swift Integration

Vapor is the most popular Swift server-side framework, enabling Swift developers to build backend APIs and services that run on Linux servers. Vapor's async/await support and the built-in HTTP client make calling SnapAPI from a Vapor route handler straightforward. Define a screenshot service as a Vapor service, register it in the application configuration, and inject it into route handlers via the request context.

// Sources/App/Services/ScreenshotService.swift
import Vapor

struct ScreenshotService {
    let client: Client
    let apiKey: String

    func capture(_ url: String, format: String = "png") async throws -> ByteBuffer {
        let uri = URI(string: "https://api.snapapi.pics/screenshot")
        let resp = try await client.get(uri) { req in
            req.headers.bearerAuthorization = .init(token: apiKey)
            req.url.query = "url=\(url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? url)&format=\(format)"
        }
        guard resp.status == .ok else {
            throw Abort(.badGateway, reason: "Screenshot API returned \(resp.status)")
        }
        return resp.body ?? ByteBuffer()
    }
}

// Sources/App/routes.swift
func routes(_ app: Application) throws {
    let screenshotService = ScreenshotService(
        client: app.client,
        apiKey: Environment.get("SNAPAPI_KEY") ?? ""
    )
    app.get("screenshot") { req async throws -> Response in
        guard let url = req.query[String.self, at: "url"] else {
            throw Abort(.badRequest, reason: "url required")
        }
        let buffer = try await screenshotService.capture(url)
        var headers = HTTPHeaders()
        headers.add(name: .contentType, value: "image/png")
        return Response(status: .ok, headers: headers, body: .init(buffer: buffer))
    }
}

iOS App Distribution and API Key Security

When building iOS applications that call SnapAPI directly from the client, API key security requires careful attention. Embedding an API key in an iOS app binary is not secure: tools like strings and reverse engineering can extract the key from the binary, allowing unauthorized users to consume your API quota. The recommended approach for iOS apps is to proxy all SnapAPI calls through a backend service that you control: the iOS app makes requests to your backend, your backend adds the SnapAPI key and forwards the request to SnapAPI, and the screenshot bytes are returned to the iOS app through your backend. This keeps the API key on the server side where it cannot be extracted. For internal enterprise iOS apps distributed via MDM where the user base is trusted and limited, embedding the key with obfuscation is a lower-risk approach, but still carries the risk of key extraction if a device is compromised. For consumer iOS apps, always use a backend proxy pattern.

Error Handling and Retry in Swift

Swift's async/await and error handling model make explicit retry logic clean and readable. Implement a retry wrapper function that calls the screenshot function up to a configurable number of times, sleeping between attempts using Task.sleep(nanoseconds:) with exponential backoff. Differentiate retryable errors (URLError.timedOut, URLError.networkConnectionLost, HTTP 503) from non-retryable errors (HTTP 400, HTTP 401) to avoid unnecessary retries. Swift's typed error system with custom error enums enables precise catch clauses that handle each error category appropriately. For iOS apps, handle the URLError.notConnectedToInternet case separately to show an appropriate offline state in the UI rather than treating it as a generic failure. Log each retry attempt with os_log at the debug level for development debugging, and omit retry logging in release builds using compile-time DEBUG flags to keep production logs clean.

Caching Screenshots in Swift Applications

Screenshots fetched from SnapAPI should be cached in memory and on disk to avoid redundant API calls when the same URL is displayed multiple times. NSCache provides a memory cache that automatically evicts entries under memory pressure, making it safe to use in iOS apps where the system reclaims memory aggressively. For persistent disk caching, store screenshot data in the app's Caches directory using FileManager, keyed by the URL's SHA256 hash. Check the disk cache before making an API call and return the cached data if it is present and not expired. Set a TTL of one hour to one day depending on how frequently the target pages change. For Vapor server-side applications, use an in-memory cache backed by a dictionary with a timestamp, or a Redis cache for distributed deployments where multiple Vapor instances need to share a screenshot cache. Proper caching dramatically reduces API consumption for applications where the same URLs are displayed frequently, such as link preview services and bookmark applications where popular URLs appear many times across many users.

Getting Started with SnapAPI in Swift

Register for a free API key at snapapi.pics/register, add the URLSession-based SnapAPIClient shown above to your Xcode project, and store the key in your scheme's environment variables for local development. Make the first screenshot call from a SwiftUI preview or an XCTest unit test to verify the connection. The free tier at 200 requests per month is available with no credit card and gives you enough quota to evaluate screenshot quality against your target URLs, build the SwiftUI display components, and validate the caching and error handling code before upgrading to a paid plan. The documentation at snapapi.pics/docs covers all Swift-compatible endpoints with examples. For Vapor server-side Swift projects, the same SnapAPIClient code runs unchanged on Linux with Swift 5.9 or later, making it straightforward to share client code between an iOS app and a Vapor backend in a Swift Package Manager workspace.