Official SDK
Swift
Swift Package Manager support for iOS and server-side Swift. Modern async/await patterns with comprehensive error handling.
Rename PDFs, split multi-page documents, and extract structured data using the official Swift SDK for Renamed.to.
- 1Add the package via Swift Package Manager or Xcode
- 2Use async/await with rename(), pdfSplit(), or extract() on Data objects
- 3Deploy to iOS 15+, macOS 12+, or server-side Swift with typed error handling
MIT licensed, open source on GitHub, published on Swift Package Manager.
On this page
Installation
.package(url: "https://github.com/renamed-to/renamed-sdk", from: "0.1.0")Quickstart
Initialize the client and start making API calls.
import Renamedimport Foundation// Initialize the clientlet client = RenamedClient( apiKey: ProcessInfo.processInfo.environment["RENAMED_API_KEY"]!)// Rename a PDFlet pdfData = try Data(contentsOf: URL(fileURLWithPath: "invoice.pdf"))let result = try await client.rename(pdfData)print(result.suggestedFilename)// -> "2024-01-15_AcmeCorp_INV-1234.pdf"// Split a multi-page PDFlet docData = try Data(contentsOf: URL(fileURLWithPath: "documents.pdf"))let job = try await client.pdfSplit(docData, options: .init(mode: .smart))print(job.documents)// -> [Document(filename: "...", downloadUrl: "..."), ...]// Extract structured datalet receiptData = try Data(contentsOf: URL(fileURLWithPath: "receipt.pdf"))let extracted = try await client.extract(receiptData)print(extracted)// -> ["vendor": "...", "date": "...", "amount": "..."]Rename a PDF
Pass PDF data to get an AI-generated filename. Use custom instructions to control the naming format.
import Renamedimport Foundation// Initialize the clientlet client = RenamedClient( apiKey: ProcessInfo.processInfo.environment["RENAMED_API_KEY"]!, configuration: .init( timeout: 30.0, maxRetries: 3 ))// Read the PDF filelet pdfURL = URL(fileURLWithPath: "./documents/invoice.pdf")let pdfData = try Data(contentsOf: pdfURL)// Rename the PDFlet result = try await client.rename(pdfData)print("Suggested filename: \(result.suggestedFilename)")print("Confidence: \(result.confidence)")// Output:// Suggested filename: 2024-01-15_AcmeCorp_INV-1234.pdf// Confidence: 0.95// Access extracted metadataif let metadata = result.metadata { print("Vendor: \(metadata["vendor"] ?? "Unknown")") print("Date: \(metadata["date"] ?? "Unknown")") print("Type: \(metadata["type"] ?? "Unknown")")}// Use custom naming instructionslet customResult = try await client.rename(pdfData, options: RenameOptions( instructions: "Format: YYYY-MM-DD_VendorName_Amount"))print(customResult.suggestedFilename)// -> "2024-01-15_AcmeCorp_$1250.pdf"// Rename from a URLlet urlResult = try await client.rename( from: URL(string: "https://example.com/document.pdf")!)// Using with SwiftUI@MainActorclass DocumentViewModel: ObservableObject { @Published var suggestedFilename: String? @Published var isLoading = false @Published var error: Error? private let client = RenamedClient( apiKey: ProcessInfo.processInfo.environment["RENAMED_API_KEY"]! ) func renameDocument(_ data: Data) async { isLoading = true defer { isLoading = false } do { let result = try await client.rename(data) suggestedFilename = result.suggestedFilename } catch { self.error = error } }}Split a PDF
Split multi-page PDFs into separate documents. The API uses async jobs for large files—poll for completion or use the built-in wait helper.
import Renamedimport Foundationlet client = RenamedClient( apiKey: ProcessInfo.processInfo.environment["RENAMED_API_KEY"]!, configuration: .init(timeout: 300.0) // 5 minutes for large files)// Read a multi-page PDFlet pdfURL = URL(fileURLWithPath: "./documents/combined.pdf")let pdfData = try Data(contentsOf: pdfURL)// Submit the split job with smart mode (AI detects document boundaries)let job = try await client.pdfSplit(pdfData, options: SplitOptions(mode: .smart))print("Job ID: \(job.jobId)")print("Status: \(job.state)")// Option 1: Wait for completion (built-in polling)let completed = try await job.waitForCompletion()// Option 2: Manual polling for progress updates// var status = job// while status.state == .processing {// print("Progress: \(status.progress)%")// try await Task.sleep(nanoseconds: 1_000_000_000) // 1 second// status = try await client.getJob(status.jobId)// }// Process the split documentsprint("Split into \(completed.documents.count) documents:")for (i, doc) in completed.documents.enumerated() { let pages = doc.pages.map { String($0) }.joined(separator: ", ") print(" [\(i + 1)] \(doc.filename) (pages \(pages))") // Output: // [1] invoice_1.pdf (pages 1, 2) // [2] invoice_2.pdf (pages 3, 4) // Download each split document let docData = try await client.downloadFile(from: doc.downloadUrl) // Save to output directory let outputDir = URL(fileURLWithPath: "./output") try FileManager.default.createDirectory(at: outputDir, withIntermediateDirectories: true) let outputURL = outputDir.appendingPathComponent(doc.filename) try docData.write(to: outputURL) print(" Saved: \(outputURL.path)")}// With a custom timeoutlet completedWithTimeout = try await job.waitForCompletion(timeout: 300) // 5 minutesExtract Data
Extract structured data from invoices, receipts, and other documents. Use Codable structs for type-safe extraction.
import Renamedimport Foundation// Define typed structs for invoice extractionstruct LineItem: Codable { let description: String let quantity: Int let unitPrice: Double}struct Invoice: Codable { let invoiceNumber: String let vendor: String let date: String let lineItems: [LineItem] let subtotal: Double let tax: Double let total: Double}// Initialize the clientlet client = RenamedClient( apiKey: ProcessInfo.processInfo.environment["RENAMED_API_KEY"]!)// Read the PDF filelet pdfURL = URL(fileURLWithPath: "./documents/invoice.pdf")let pdfData = try Data(contentsOf: pdfURL)// Basic extraction (returns [String: Any])let data = try await client.extract(pdfData)print("Vendor: \(data["vendor"] ?? "Unknown")")print("Date: \(data["date"] ?? "Unknown")")print("Total: \(data["total"] ?? "Unknown")")// Output:// Vendor: Acme Corp// Date: 2024-01-15// Total: 1250.0// Typed extraction: decode to a Codable structlet jsonData = try JSONSerialization.data(withJSONObject: data)let decoder = JSONDecoder()let invoice = try decoder.decode(Invoice.self, from: jsonData)// Now you have fully typed accessprint("Invoice #\(invoice.invoiceNumber) from \(invoice.vendor)")print("Line items: \(invoice.lineItems.count)")for item in invoice.lineItems { print(" - \(item.description): \(item.quantity) x $\(String(format: "%.2f", item.unitPrice))")}print("Total: $\(String(format: "%.2f", invoice.total))")// Output:// Invoice #INV-2024-1234 from Acme Corp// Line items: 2// - Widget Pro: 5 x $199.00// - Support Plan: 1 x $342.50// Total: $1337.50// Using with a custom schema for typed extractionlet typedInvoice: Invoice = try await client.extract(pdfData, as: Invoice.self)Framework Integrations
Common integration patterns for popular frameworks.
Vapor Route Handler
Handle PDF uploads in a Vapor web server with proper file handling and error responses.
import Vaporimport Renamed// Configure the Renamed clientfunc configure(_ app: Application) throws { // Register the client as a service app.renamed = RenamedClient( apiKey: Environment.get("RENAMED_API_KEY")!, configuration: .init( timeout: 300.0, maxRetries: 3 ) ) // Register routes try routes(app)}// Extension to store client on Applicationextension Application { private struct RenamedClientKey: StorageKey { typealias Value = RenamedClient } var renamed: RenamedClient { get { storage[RenamedClientKey.self]! } set { storage[RenamedClientKey.self] = newValue } }}// Route handlersfunc routes(_ app: Application) throws { // Rename endpoint app.post("api", "rename") { req async throws -> RenameResponse in let file = try req.content.decode(FileUpload.self) guard file.file.contentType == .pdf else { throw Abort(.badRequest, reason: "Only PDF files are supported") } var buffer = file.file.data guard let data = buffer.readData(length: buffer.readableBytes) else { throw Abort(.badRequest, reason: "Failed to read file data") } do { let result = try await req.application.renamed.rename(data) return RenameResponse( suggestedFilename: result.suggestedFilename, confidence: result.confidence, metadata: result.metadata ) } catch RenamedError.rateLimited(let retryAfter) { throw Abort(.tooManyRequests, headers: ["Retry-After": "\(retryAfter)"]) } catch { throw Abort(.badGateway, reason: error.localizedDescription) } } // Split endpoint app.post("api", "split") { req async throws -> SplitResponse in let input = try req.content.decode(SplitInput.self) var buffer = input.file.data guard let data = buffer.readData(length: buffer.readableBytes) else { throw Abort(.badRequest, reason: "Failed to read file data") } let mode: SplitMode = input.mode.flatMap { SplitMode(rawValue: $0) } ?? .smart do { let job = try await req.application.renamed.pdfSplit( data, options: SplitOptions(mode: mode) ) let completed = try await job.waitForCompletion() return SplitResponse( jobId: completed.jobId, documents: completed.documents.map { doc in SplitDocument( filename: doc.filename, pages: doc.pages, downloadUrl: doc.downloadUrl ) } ) } catch { throw Abort(.badGateway, reason: error.localizedDescription) } } // Extract endpoint app.post("api", "extract") { req async throws -> [String: AnyCodable] in let file = try req.content.decode(FileUpload.self) var buffer = file.file.data guard let data = buffer.readData(length: buffer.readableBytes) else { throw Abort(.badRequest, reason: "Failed to read file data") } do { let result = try await req.application.renamed.extract(data) return result.mapValues { AnyCodable($0) } } catch { throw Abort(.badGateway, reason: error.localizedDescription) } }}// DTOsstruct FileUpload: Content { let file: File}struct SplitInput: Content { let file: File let mode: String?}struct RenameResponse: Content { let suggestedFilename: String let confidence: Double let metadata: [String: Any]? enum CodingKeys: String, CodingKey { case suggestedFilename, confidence, metadata }}struct SplitResponse: Content { let jobId: String let documents: [SplitDocument]}struct SplitDocument: Content { let filename: String let pages: [Int] let downloadUrl: String}SwiftUI Integration
Use the SDK in SwiftUI apps with proper state management and async handling.
import SwiftUIimport Renamedimport UniformTypeIdentifiers// View Model@MainActorclass DocumentRenameViewModel: ObservableObject { @Published var suggestedFilename: String? @Published var confidence: Double? @Published var metadata: [String: Any]? @Published var isLoading = false @Published var error: String? private let client: RenamedClient init() { // In production, use a secure method to store the API key guard let apiKey = ProcessInfo.processInfo.environment["RENAMED_API_KEY"] else { fatalError("RENAMED_API_KEY not set") } self.client = RenamedClient(apiKey: apiKey) } func renameDocument(_ data: Data) async { isLoading = true error = nil defer { isLoading = false } do { let result = try await client.rename(data) suggestedFilename = result.suggestedFilename confidence = result.confidence metadata = result.metadata } catch RenamedError.rateLimited(let retryAfter) { error = "Rate limited. Please try again in \(retryAfter) seconds." } catch RenamedError.invalidFile(let message) { error = "Invalid file: \(message)" } catch { error = error.localizedDescription } } func reset() { suggestedFilename = nil confidence = nil metadata = nil error = nil }}// SwiftUI Viewstruct DocumentRenameView: View { @StateObject private var viewModel = DocumentRenameViewModel() @State private var isShowingFilePicker = false @State private var selectedFileData: Data? var body: some View { NavigationStack { VStack(spacing: 20) { // File picker button Button { isShowingFilePicker = true } label: { Label("Select PDF", systemImage: "doc.badge.plus") .font(.headline) } .buttonStyle(.borderedProminent) .disabled(viewModel.isLoading) // Loading indicator if viewModel.isLoading { ProgressView("Analyzing PDF...") } // Results if let filename = viewModel.suggestedFilename { VStack(alignment: .leading, spacing: 12) { Text("Suggested Filename") .font(.headline) Text(filename) .font(.body) .padding() .background(Color.secondary.opacity(0.1)) .cornerRadius(8) if let confidence = viewModel.confidence { Text("Confidence: \(Int(confidence * 100))%") .font(.subheadline) .foregroundColor(.secondary) } } .padding() } // Error display if let error = viewModel.error { Text(error) .foregroundColor(.red) .padding() } Spacer() } .padding() .navigationTitle("Rename PDF") .fileImporter( isPresented: $isShowingFilePicker, allowedContentTypes: [UTType.pdf] ) { result in handleFileSelection(result) } } } private func handleFileSelection(_ result: Result<URL, Error>) { switch result { case .success(let url): do { // Start accessing the security-scoped resource guard url.startAccessingSecurityScopedResource() else { viewModel.error = "Cannot access file" return } defer { url.stopAccessingSecurityScopedResource() } let data = try Data(contentsOf: url) selectedFileData = data Task { await viewModel.renameDocument(data) } } catch { viewModel.error = error.localizedDescription } case .failure(let error): viewModel.error = error.localizedDescription } }}#Preview { DocumentRenameView()}Error Handling
The SDK throws typed errors that you can catch and handle appropriately using do-catch blocks.
import Renamedimport Foundationlet client = RenamedClient( apiKey: ProcessInfo.processInfo.environment["RENAMED_API_KEY"]!)do { let pdfURL = URL(fileURLWithPath: "invoice.pdf") let pdfData = try Data(contentsOf: pdfURL) let result = try await client.rename(pdfData) print("Renamed to: \(result.suggestedFilename)")} catch RenamedError.rateLimited(let retryAfter) { // Rate limited - wait and retry print("Rate limited. Retry after \(retryAfter) seconds") try await Task.sleep(nanoseconds: UInt64(retryAfter) * 1_000_000_000) // Retry the request...} catch RenamedError.invalidFile(let message) { // File is not a valid PDF or is corrupted print("Invalid file: \(message)")} catch RenamedError.authentication(let message) { // API key is invalid or expired print("Authentication failed: \(message)")} catch RenamedError.apiError(let code, let message, let details) { // Other API errors print("API Error [\(code)]: \(message)") if let details = details { print("Details: \(details)") }} catch RenamedError.networkError(let underlyingError) { // Network/connection errors print("Network error: \(underlyingError.localizedDescription)")} catch RenamedError.timeout { // Request timed out print("Request timed out")} catch { // Other errors (file I/O, etc.) print("Unexpected error: \(error)")}// Helper function with retry logicfunc renameWithRetry( client: RenamedClient, data: Data, maxRetries: Int = 3) async throws -> RenameResult { var retries = 0 while true { do { return try await client.rename(data) } catch RenamedError.rateLimited(let retryAfter) { retries += 1 guard retries <= maxRetries else { throw RenamedError.rateLimited(retryAfter: retryAfter) } let sleepTime = retryAfter > 0 ? retryAfter : Int(pow(2.0, Double(retries))) print("Rate limited, retrying in \(sleepTime)s (attempt \(retries)/\(maxRetries))") try await Task.sleep(nanoseconds: UInt64(sleepTime) * 1_000_000_000) } catch RenamedError.apiError(let code, let message, _) where code >= 500 { retries += 1 guard retries <= maxRetries else { throw RenamedError.apiError(code: code, message: message, details: nil) } let sleepTime = Int(pow(2.0, Double(retries))) print("Server error, retrying in \(sleepTime)s (attempt \(retries)/\(maxRetries))") try await Task.sleep(nanoseconds: UInt64(sleepTime) * 1_000_000_000) } }}Full documentation on GitHub
For more examples, advanced usage patterns, and detailed API documentation, see the full Swift SDK README on GitHub.
Read the Swift SDK docsFrequently asked questions
- What Swift versions are supported?
- The Swift SDK supports Swift 5.9 and above. It uses modern Swift concurrency with async/await.
- Does it work on iOS?
- Yes, the SDK works on iOS 15+, macOS 12+, tvOS 15+, watchOS 8+, and server-side Swift (Linux). It supports all Apple platforms.
- How do I install it?
- Add the package to your Package.swift dependencies or use Xcode to add the package URL directly via File > Add Package Dependencies.
- Is it compatible with Vapor?
- Yes, the SDK works great with Vapor and other server-side Swift frameworks. Use async/await in your route handlers.
- How do I handle background uploads on iOS?
- For background uploads, combine the SDK with URLSession background tasks. The SDK methods can be called when your app returns to the foreground.