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.