Screenshot API for Swift: URLSession, async/await & Combine

Capture screenshots, scrape web pages, and generate PDFs from Swift apps. Works with iOS, macOS, watchOS, and Swift on the server with Vapor or Hummingbird — no headless browser required.

Start Free — 200 captures/moView Docs

Screenshot API for Swift — URLSession Quickstart

Swift's URLSession with async/await makes calling SnapAPI clean and readable. The following function returns screenshot data as Data and throws on API errors:

import Foundation

struct SnapAPI {
    let apiKey: String
    let session: URLSession

    init(apiKey: String, session: URLSession = .shared) {
        self.apiKey = apiKey
        self.session = session
    }

    func screenshot(url: String, fullPage: Bool = true) async throws -> Data {
        var components = URLComponents(string: "https://api.snapapi.pics/v1/screenshot")!
        components.queryItems = [
            URLQueryItem(name: "url", value: url),
            URLQueryItem(name: "full_page", value: fullPage ? "true" : "false"),
            URLQueryItem(name: "format", value: "jpeg"),
            URLQueryItem(name: "block_ads", value: "true")
        ]
        var request = URLRequest(url: components.url!)
        request.setValue(apiKey, forHTTPHeaderField: "X-Api-Key")
        request.timeoutInterval = 30

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

// Usage
let snap = SnapAPI(apiKey: ProcessInfo.processInfo.environment["SNAPAPI_KEY"]!)
let imageData = try await snap.screenshot(url: "https://example.com")
// imageData is a JPEG you can display with UIImage(data:) or NSImage(data:)

The async/await pattern integrates naturally with SwiftUI's .task modifier and UIKit's async contexts. The returned Data can be passed directly to UIImage(data:) on iOS or NSImage(data:) on macOS.

Combine Integration

For codebases using Combine, wrap the URLSession dataTaskPublisher:

import Combine

func screenshotPublisher(url: String, apiKey: String) -> AnyPublisher<Data, Error> {
    var components = URLComponents(string: "https://api.snapapi.pics/v1/screenshot")!
    components.queryItems = [URLQueryItem(name: "url", value: url)]
    var request = URLRequest(url: components.url!)
    request.setValue(apiKey, forHTTPHeaderField: "X-Api-Key")

    return URLSession.shared.dataTaskPublisher(for: request)
        .tryMap { data, response in
            guard (response as? HTTPURLResponse)?.statusCode == 200 else {
                throw URLError(.badServerResponse)
            }
            return data
        }
        .eraseToAnyPublisher()
}

// Usage with SwiftUI ObservableObject
screenshotPublisher(url: "https://example.com", apiKey: key)
    .receive(on: DispatchQueue.main)
    .sink(receiveCompletion: { _ in }, receiveValue: { data in
        self.screenshotImage = UIImage(data: data)
    })
    .store(in: &cancellables)

The publisher completes with the raw JPEG data. Chain .map { UIImage(data: $0) } to convert it for display. The receive(on: DispatchQueue.main) call ensures UI updates happen on the main thread.

Swift Server-Side Integration with Vapor

On the server, Vapor's async route handlers call SnapAPI naturally. Register a route that accepts a URL parameter and returns a screenshot:

import Vapor

func routes(_ app: Application) throws {
    app.get("capture") { req async throws -> Response in
        guard let target = req.query[String.self, at: "url"] else {
            throw Abort(.badRequest, reason: "Missing url parameter")
        }
        let snap = SnapAPI(apiKey: Environment.get("SNAPAPI_KEY")!)
        let imageData = try await snap.screenshot(url: target)

        var headers = HTTPHeaders()
        headers.add(name: .contentType, value: "image/jpeg")
        return Response(status: .ok, headers: headers, body: .init(data: imageData))
    }
}

This route proxies screenshot requests, adding your API key server-side so it is never exposed to clients. The Vapor route streams the response directly, keeping memory usage low even for large full-page captures.

SwiftUI Preview Image Component

Building a link preview feature in your iOS app is straightforward with SnapAPI. Create a SwiftUI view that loads a screenshot asynchronously and caches it with NSCache:

struct WebPreview: View {
    let url: String
    @State private var image: UIImage?

    var body: some View {
        Group {
            if let image {
                Image(uiImage: image)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
            } else {
                ProgressView()
                    .frame(width: 300, height: 200)
            }
        }
        .task {
            let snap = SnapAPI(apiKey: Secrets.snapAPIKey)
            if let data = try? await snap.screenshot(url: url, fullPage: false) {
                image = UIImage(data: data)
            }
        }
    }
}

The .task modifier cancels the async call automatically when the view disappears, preventing dangling network requests. Add a cache layer with NSCache<NSString, UIImage> to avoid re-fetching screenshots for URLs that have already been loaded in the current session.

Data Extraction and Scraping in Swift

SnapAPI's extract endpoint returns structured JSON, which decodes cleanly with Swift's Codable protocol. The response includes the matched text, element attributes, and an array of all matched elements for multi-element selectors:

struct ExtractRequest: Encodable {
    let url: String
    let selector: String
    let waitFor: String?
    enum CodingKeys: String, CodingKey {
        case url, selector
        case waitFor = "wait_for"
    }
}

struct ExtractResponse: Decodable {
    let text: String
    let elements: [String]?
}

func extractText(from url: String, selector: String, apiKey: String) async throws -> String {
    var request = URLRequest(url: URL(string: "https://api.snapapi.pics/v1/extract")!)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.setValue(apiKey, forHTTPHeaderField: "X-Api-Key")
    request.httpBody = try JSONEncoder().encode(ExtractRequest(url: url, selector: selector, waitFor: selector))

    let (data, _) = try await URLSession.shared.data(for: request)
    let result = try JSONDecoder().decode(ExtractResponse.self, from: data)
    return result.text
}

Swift's Codable with CodingKeys handles the snake_case to camelCase conversion automatically. The waitFor parameter tells the API to wait until the selector is present in the DOM, handling JavaScript-rendered content that would otherwise return empty results.

Register at snapapi.pics for 200 free captures per month — no credit card required. The official SnapAPI Swift SDK is available on GitHub at github.com/Sleywill/snapapi-swift and supports all API endpoints with a clean Swift interface.

Batch Screenshot Capture in Swift with TaskGroup

Swift's structured concurrency with TaskGroup makes parallel capture jobs clean and safe. The following pattern captures multiple URLs concurrently while respecting a concurrency limit:

func captureAll(urls: [String], apiKey: String) async throws -> [String: Data] {
    var results: [String: Data] = [:]
    try await withThrowingTaskGroup(of: (String, Data).self) { group in
        for url in urls {
            group.addTask {
                let snap = SnapAPI(apiKey: apiKey)
                let data = try await snap.screenshot(url: url)
                return (url, data)
            }
        }
        for try await (url, data) in group {
            results[url] = data
        }
    }
    return results
}

TaskGroup respects Swift's cooperative cancellation model — if any child task throws, the group cancels remaining tasks automatically. For large batches, use withThrowingTaskGroup with a semaphore or actor-based queue to cap concurrency at 5 or 10 requests at a time.

Saving Screenshots to the iOS Photo Library

On iOS, save captured screenshots directly to the photo library with Photos framework permission. This is useful for apps that let users archive webpages as visual references:

import Photos

func saveToPhotoLibrary(imageData: Data) async throws {
    guard await PHPhotoLibrary.requestAuthorization(for: .addOnly) == .authorized else {
        throw PhotoLibraryError.notAuthorized
    }
    try await PHPhotoLibrary.shared().performChanges {
        guard let image = UIImage(data: imageData) else { return }
        PHAssetChangeRequest.creationRequestForAsset(from: image)
    }
}

Request only the .addOnly authorization level — it avoids the more invasive full photo library access permission that users are reluctant to grant. The resulting asset appears in the Recents album and is available across all devices via iCloud Photos.

PDF Generation and AI Analysis from Swift

SnapAPI exposes six endpoint categories accessible from Swift: screenshot, scrape, extract, PDF, video, and AI analysis. The PDF endpoint generates print-quality documents from URLs or raw HTML templates. The analyze endpoint sends a page to a language model and returns structured insights — useful for iOS apps that summarize or classify web content for users.

For document generation in a macOS app, capture a PDF from a URL and open it inline with PDFView:

import PDFKit

func loadPDF(from url: String, apiKey: String) async throws -> PDFDocument {
    var request = URLRequest(url: URL(string: "https://api.snapapi.pics/v1/pdf")!)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.setValue(apiKey, forHTTPHeaderField: "X-Api-Key")
    request.httpBody = try JSONSerialization.data(withJSONObject: [
        "url": url, "page_size": "A4", "print_background": true
    ])
    let (data, _) = try await URLSession.shared.data(for: request)
    guard let pdf = PDFDocument(data: data) else {
        throw NSError(domain: "SnapAPI", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid PDF"])
    }
    return pdf
}

PDFDocument accepts raw PDF binary data directly and provides page navigation, text search, and annotation support. Display it with PDFView in SwiftUI using UIViewRepresentable on iOS or natively on macOS.

Pricing and Getting Started

SnapAPI offers 200 free captures per month with no credit card required — enough to build and test a complete Swift integration. The Starter plan at $19 per month provides 5,000 captures. Pro at $79 provides 50,000, and Business at $299 provides 500,000 captures per month with higher concurrency limits.

Register at snapapi.pics, copy your API key from the dashboard, and make your first Swift screenshot call in under five minutes. The official Swift SDK is available at github.com/Sleywill/snapapi-swift with SPM support — add it as a package dependency and get a fully typed Swift interface to all API endpoints.

SnapAPI is compatible with every Apple platform: iOS 15 and later, macOS 12 and later, watchOS 8, tvOS 15, and visionOS. All endpoints use standard URLSession under the hood, which is available on every Apple platform without additional dependencies. Swift Package Manager integration is supported — add the package URL and import SnapAPISwift in any target.