---
seo_title: SDK Swift per Conversione File: Client Async Await | EnConvert
meta_desc: SDK Swift ufficiale di EnConvert per macOS, iOS, tvOS e watchOS. Metodi async await per la conversione dei file e per percepire, scoprire e distillare pagine web.
keywords: sdk swift conversione file, convertire file in swift, url in pdf swift, api web scraping swift, docx in pdf swift, enconvert sdk swift, heic in webp swift, client api swift async await, api conversione file ios, libreria pdf swift package manager, screenshot sito web swift, estrazione dati strutturati swift
---

# SDK Swift per la conversione dei file

`Enconvert` è il client EnConvert ufficiale per Swift, distribuito tramite Swift Package Manager. È costruito su `URLSession` con `async`/`await`, non porta con sé alcuna dipendenza esterna e punta a Swift 5.9 e successivi su macOS 12, iOS 15, tvOS 15 e watchOS 8. Dodici metodi sul client coprono la conversione dei file e il rendering degli URL (da DOCX a PDF, da HEIC a WebP, da URL a PDF, da URL a Markdown, batch su interi siti), e il namespace `client.v2` aggiunge ventitré metodi di web intelligence per percepire, scoprire, cercare, distillare, ingerire e monitorare le pagine.

<div class="alert alert-info">
<strong>Pacchetto:</strong> <code>Enconvert</code> · <strong>Sorgente:</strong> <a href="https://github.com/conversionapi/swift-sdk">conversionapi/swift-sdk</a> · <strong>Swift:</strong> 5.9+ · <strong>Piattaforme:</strong> macOS 12+, iOS 15+, tvOS 15+, watchOS 8+ · <strong>Dipendenze:</strong> nessuna
</div>

---

## Installazione

Aggiungi il pacchetto, poi elenca il prodotto nel target che lo usa:

```swift
dependencies: [
    .package(url: "https://github.com/conversionapi/swift-sdk.git", from: "0.0.1")
],
targets: [
    .target(name: "MyApp", dependencies: [
        .product(name: "Enconvert", package: "swift-sdk")
    ])
]
```

In Xcode, usa **File > Add Package Dependencies** e incolla `https://github.com/conversionapi/swift-sdk.git`. Su Linux l'SDK importa `FoundationNetworking` in modo condizionale, quindi da parte tua non serve nulla di aggiuntivo.

---

## Avvio rapido

```swift
import Enconvert

let apiKey = ProcessInfo.processInfo.environment["ENCONVERT_API_KEY"] ?? ""
let client = try Enconvert(apiKey: apiKey)

let result = try await client.convertUrlToPdf("https://example.com", options: UrlToPdfOptions(saveTo: "page.pdf"))
print(result.filename, result.presignedUrl)
```

`Enconvert.init` è throwing, non failable: un `apiKey` vuoto solleva `EnconvertError.invalidArgument` prima che qualcosa tocchi la rete. Ogni metodo di richiesta è `async throws`, e i metodi di conversione sono marcati `@discardableResult`, così una chiamata effettuata solo per l'effetto collaterale di `saveTo` non genera warning.

---

## Che cosa espone il client

Dodici metodi pendono da `Enconvert` e si mappano 1:1 sugli endpoint REST:

| Metodo | Endpoint | Restituisce |
|--------|----------|---------|
| `convertUrlToPdf(_:options:)` | `POST /v1/convert/url-to-pdf` | `ConversionResult` |
| `convertUrlToScreenshot(_:options:)` | `POST /v1/convert/url-to-screenshot` | `ConversionResult` |
| `convertUrlToMarkdown(_:options:)` | `POST /v1/convert/url-to-markdown` | `ConversionResult` |
| `convertImage(_:options:)` | `POST /v1/convert/{from}-to-{to}` | `ConversionResult` |
| `convertDocument(_:options:)` | `POST /v1/convert/{from}-to-{to}` | `ConversionResult` |
| `convertToMarkdown(_:options:)` | `POST /v1/convert/anything-to-markdown` | `ConversionResult` |
| `convertToPdf(_:options:)` | `POST /v1/convert/anything-to-pdf` | `ConversionResult` |
| `convertWebsiteToPdf(_:options:)` | `POST /v1/convert/website-to-pdf` | `BatchSubmission` |
| `convertWebsiteToScreenshot(_:options:)` | `POST /v1/convert/website-to-screenshot` | `BatchSubmission` |
| `getJobStatus(_:)` | `GET /v1/convert/status/{jobId}` | `JobStatus` |
| `getBatchStatus(_:)` | `GET /v1/convert/batch/{batchId}` | `BatchStatus` |
| `waitForBatch(_:options:)` | `GET /v1/convert/batch/{batchId}` (con polling) | `BatchStatus` |

`client.v2` è un namespace `EnconvertV2` che contiene altri ventitré metodi distribuiti su sei gruppi di capacità:

| Gruppo | Metodi | Percorso base |
|-------|---------|-----------|
| Perceive | `perceive`, `perceiveDirect`, `getPerceiveOperation`, `downloadPerceiveArtifact`, `perceiveBatch`, `getPerceiveBatch` | `/v2/perceive` |
| Discover | `discover` | `/v2/discover` |
| Lookup | `lookup` | `/v2/lookup` |
| Distill | `distill` | `/v2/distill` |
| Ingest | `ingest`, `ingestFiles`, `getIngestJob`, `listIngestJobs`, `cancelIngestJob`, `retryIngestWebhook`, `getWebhookSecret`, `rotateWebhookSecret` | `/v2/ingest` |
| Watch | `createWatcher`, `listWatchers`, `getWatcher`, `getWatcherSnapshots`, `updateWatcher`, `deleteWatcher` | `/v2/watch` |

Le opzioni vengono passate come una struct con parametri di inizializzazione dotati di valori predefiniti, quindi `UrlToPdfOptions()` significa "tutti i valori predefiniti" e tu nomini solo i campi che ti interessano. Swift richiede gli argomenti etichettati nell'ordine di dichiarazione, quindi tieni `saveTo:` prima di `singlePage:` e `pdfOptions:` quando ne imposti diversi insieme.

---

## Conversione dei file

Gli upload accettano un `FileInput`:

| Caso | A cosa serve |
|------|-----------|
| `.path("report.docx")` | Un file su disco. Il nome base decide il formato di input e il tipo MIME. |
| `.data(bytes)` | Byte grezzi senza nome. Caricati come `upload.bin`, `application/octet-stream`. |
| `.wrapped(data: bytes, filename: "report.docx", contentType: nil)` | Byte grezzi più un nome file esplicito. Un `contentType` a `nil` viene dedotto dall'estensione. |

### convertUrlToPdf

Esegue il rendering di qualsiasi URL pubblico in un PDF.

```swift
let result = try await client.convertUrlToPdf("https://example.com", options: UrlToPdfOptions(
    viewportWidth: 1440,
    saveTo: "report.pdf",
    singlePage: false,
    pdfOptions: PdfOptions(pageSize: "A4", orientation: .landscape)
))
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `viewportWidth`, `viewportHeight` | `Int?` | `1920`, `1080` | Dimensione del viewport del browser in pixel. |
| `loadMedia`, `enableScroll` | `Bool?` | `true` | Attende immagini e video; scorre dall'alto verso il basso per attivare i caricamenti lazy. |
| `outputFilename` | `String?` | auto | Sovrascrive il nome file generato. |
| `auth`, `cookies`, `headers` | `HttpBasicAuth?`, `[BrowserCookie]?`, `[String: String]?` | nessuno | Credenziali HTTP Basic, cookie iniettati (massimo 50), header di richiesta aggiuntivi (massimo 20, hop-by-hop rifiutati). |
| `saveTo` | `String?` | nessuno | Percorso locale su cui scrivere il PDF. Le directory padre vengono create. |
| `singlePage` | `Bool?` | `true` | `true` produce una singola pagina continua. `false` impagina usando `pdfOptions.pageSize`. |
| `pdfOptions` | `PdfOptions?` | nessuno | Geometria di pagina. Vedi [Opzioni PDF](#opzioni-pdf). |

Le pagine dietro un login accettano credenziali, cookie o header:

```swift
_ = try await client.convertUrlToPdf("https://internal.example.com/report", options: UrlToPdfOptions(
    auth: HttpBasicAuth(username: "user", password: "pass"),
    cookies: [BrowserCookie(name: "session", value: "abc123", domain: "internal.example.com")],
    headers: ["X-Tenant": "acme"],
    saveTo: "report.pdf"
))
```

Non combinare `auth` con una voce `Authorization` in `headers`. L'API rifiuta il conflitto.

### convertUrlToScreenshot

Cattura un PNG di qualsiasi URL.

```swift
let shot = try await client.convertUrlToScreenshot(
    "https://example.com",
    options: UrlToScreenshotOptions(viewportWidth: 1440, saveTo: "shot.png")
)
```

`UrlToScreenshotOptions` accetta gli stessi campi di viewport, media, scroll, nome file e accesso al browser di `UrlToPdfOptions`, senza `singlePage` e `pdfOptions`.

### convertUrlToMarkdown

Estrae Markdown pulito in stile GitHub-Flavored da un URL. Il convertitore rimuove navigazione, footer, pubblicità e script, mantiene il corpo principale dell'articolo e antepone un frontmatter YAML con titolo, descrizione, url, link e immagini. Utile per pipeline RAG, per importare contenuti di terze parti in un CMS o per generare dati di addestramento.

```swift
_ = try await client.convertUrlToMarkdown("https://example.com/article", options: UrlToMarkdownOptions(saveTo: "article.md"))
```

### convertImage

Converte tra `jpeg`, `png`, `svg`, `heic` e `webp`, oppure rasterizza un PDF in JPEG.

```swift
let result = try await client.convertImage(.path("photo.heic"), options: ConvertImageOptions(outputFormat: "webp", saveTo: "photo.webp"))
```

Il formato di input viene ricavato dall'estensione del nome file (`.jpg`, `.jpeg`, `.png`, `.svg`, `.heic`, `.webp` e `.pdf` per la rasterizzazione). `outputFormat` è obbligatorio e accetta gli alias `jpg`, `yml`, `htm` e `md`.

| Opzione | Tipo | Obbligatorio | Descrizione |
|--------|------|----------|-------------|
| `outputFormat` | `String` | Sì | Formato di destinazione, ad esempio `"webp"`. |
| `saveTo` | `String?` | no | Percorso locale su cui scrivere il risultato. |
| `outputFilename` | `String?` | no | Sovrascrive il nome file generato. |

### convertDocument

Converte documenti e formati di testo strutturato. `outputFormat` vale `"pdf"` per impostazione predefinita.

```swift
// da docx a pdf
_ = try await client.convertDocument(.path("report.docx"), options: ConvertDocumentOptions(saveTo: "report.pdf"))

// da json a yaml
_ = try await client.convertDocument(.path("data.json"), options: ConvertDocumentOptions(outputFormat: "yaml", saveTo: "data.yaml"))

// da markdown a pdf con impostazioni di pagina personalizzate
_ = try await client.convertDocument(.path("README.md"), options: ConvertDocumentOptions(
    saveTo: "readme.pdf",
    pdfOptions: PdfOptions(pageSize: "A4", margins: PdfMargins(top: 20, bottom: 20))
))
```

**Input supportati:** `.doc`, `.docx`, `.xls`, `.xlsx`, `.ppt`, `.pptx`, `.html`, `.htm`, `.odt`, `.ods`, `.odp`, `.ots`, `.pages`, `.numbers`, `.md`, `.markdown`, `.csv`, `.json`, `.xml`, `.yaml`, `.yml`, `.toml`. EPUB non ha una coppia documentale dedicata, quindi passa invece i file `.epub` attraverso `convertToPdf` oppure `convertToMarkdown`.

L'SDK include la tabella completa delle conversioni del gateway e valida localmente ogni coppia `{input}-to-{output}`, così una coppia non supportata solleva `EnconvertError.invalidArgument` con l'elenco degli output validi invece di pagare un giro di rete destinato a fallire. Le coppie implementate sono 43:

| Input | Output |
|-------|---------|
| `json` | `csv`, `toml`, `xml`, `yaml` |
| `xml` | `csv`, `json` |
| `yaml` | `json` |
| `csv` | `json`, `xml` |
| `toml` | `json` |
| `markdown` | `html`, `pdf` |
| `html` | `pdf` |
| `doc`, `excel`, `ppt`, `odt`, `ods`, `odp`, `ots`, `pages`, `numbers` | `pdf` |
| `jpeg`, `png`, `svg`, `heic`, `webp` | ciascuno verso gli altri quattro (20 coppie ordinate) |
| `pdf` | `jpeg` |

Puoi interrogare tu stesso quella tabella senza effettuare alcuna richiesta:

```swift
validOutputsFor("json")                           // ["csv", "toml", "xml", "yaml"]
IMPLEMENTED_CONVERSIONS.contains("heic-to-webp")  // true
```

### convertToMarkdown

Converte in Markdown pulito un documento caricato di quasi qualsiasi formato, con il formato rilevato automaticamente lato server. Un buon primo stadio per una pipeline RAG.

```swift
_ = try await client.convertToMarkdown(.path("handbook.docx"), options: ConvertToMarkdownOptions(saveTo: "handbook.md"))
```

Sono accettati PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT e MD, e file office legacy o ODF. Le immagini no. Su questo endpoint non ci sono opzioni PDF.

### convertToPdf

Converte in PDF un file caricato di quasi qualsiasi formato: office, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, testo semplice, immagini raster, SVG, EPUB, oppure un PDF già esistente passato in passthrough e normalizzato.

```swift
_ = try await client.convertToPdf(.path("slides.pptx"), options: ConvertToPdfOptions(saveTo: "slides.pdf"))

// Passthrough di un PDF, convertito in scala di grigi
_ = try await client.convertToPdf(
    .path("scan.pdf"),
    options: ConvertToPdfOptions(saveTo: "scan-gray.pdf", pdfOptions: PdfOptions(grayscale: true))
)
```

<div class="alert alert-warning">
<strong>Qui viene rispettato solo <code>grayscale</code>.</strong> <code>convertToPdf</code> inoltra <code>pdfOptions</code>, ma l'endpoint anything-to-pdf legge <code>grayscale</code> e ignora il resto. Usa <code>convertDocument</code> oppure <code>convertUrlToPdf</code> quando ti servono dimensione della pagina, orientamento, margini, scala, intestazioni o piè di pagina.
</div>

### convertWebsiteToPdf e convertWebsiteToScreenshot

Individua ogni pagina di un sito, converte ciascuna in background e raccoglie tutto in un unico ZIP. Entrambi i metodi sono solo asincroni e richiedono una chiave API privata con accesso alla scansione.

```swift
let batch = try await client.convertWebsiteToPdf("https://example.com", options: WebsiteToPdfOptions(
    crawlMode: .sitemap,
    excludePatterns: ["/blog/tag/"]
))
print(batch.batchId, batch.urlCount, batch.discoveryMethod ?? "")

// Blocca finché il batch non si assesta e salva lo ZIP
let status = try await client.waitForBatch(batch.batchId, options: WaitForBatchOptions(saveTo: "site.zip"))
print("\(status.completed) of \(status.total) pages converted")

// Oppure interrogalo tu stesso
let snapshot = try await client.getBatchStatus(batch.batchId)
if snapshot.status != .processing {
    print(snapshot.zipDownloadUrl ?? "")
}
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `crawlMode` | `CrawlMode?` | `.auto` | `.auto`, `.sitemap` (solo sitemap.xml) oppure `.full` (sitemap più scansione BFS). |
| `includePatterns`, `excludePatterns` | `[String]?` | nessuno | Allowlist e poi denylist per gli URL individuati. Solo in modalità di scansione completa. |
| `notificationEmail` | `String?` | proprietario del progetto | Email avvisata quando il batch termina. |
| `callbackUrl` | `String?` | nessuno | Webhook chiamato in POST quando il batch termina. |
| `singlePage`, `pdfOptions` | `Bool?`, `PdfOptions?` | vedi sopra | Solo per i batch PDF. |

Entrambi i metodi accettano anche i campi di viewport, media, scroll e accesso al browser elencati sotto `convertUrlToPdf`, applicati a ogni pagina. `waitForBatch` interroga ogni 5 secondi con un limite di 30 minuti per impostazione predefinita; puoi sovrascriverlo con `WaitForBatchOptions(intervalMs:timeoutMs:saveTo:)`. Superare il limite solleva `EnconvertError.api(statusCode: 504, ...)`. `convertWebsiteToScreenshot` si comporta in modo identico e produce uno ZIP di PNG.

---

## Web intelligence (V2)

Ogni lettura V2 porta con sé un punteggio `renderQuality` da 0.0 a 1.0, esposto come `Double?` su `PerceiveResult`, `PerceiveDirectResult`, `DistillItem` e `WatcherSnapshot`. Un punteggio basso significa che la pagina non si è renderizzata onestamente: una sfida anti-bot, un muro di login, un banner dei cookie sopra un guscio SPA vuoto, uno stato di errore HTTP. Il contenuto torna comunque, segnalato, accanto a un dizionario `deductions` che dà un nome a ciascuna penalità scattata e a un array `warnings`, così una lettura sbagliata non entra mai in silenzio nel contesto del tuo agente. Verificalo prima di fidarti di qualsiasi cosa:

```swift
if let quality = op.renderQuality, quality < 0.6 {
    print("low quality read of \(op.url): \(op.deductions)")
}
```

### Perceive

Renderizza un URL negli artefatti che chiedi. Sincrono, con URL firmati agli artefatti validi 15 minuti. Ogni metodo V2 richiede una chiave API privata; le chiavi pubbliche vengono rifiutate.

```swift
let op = try await client.v2.perceive("https://example.com", options: PerceiveOptions(
    outputs: [.markdown, .screenshot, .structured],
    extract: [.tables, .metadata],
    viewport: PerceiveViewport(width: 1440)
))
print(op.operationId, op.renderQuality ?? 0, op.outputs["markdown"]?.url ?? "")
print(op.structured ?? [:], op.extractionTier ?? .heuristic)

// Rifirma più tardi gli URL degli artefatti
let again = try await client.v2.getPerceiveOperation(op.operationId)
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `outputs` | `[PerceiveOutputName]?` | `[.markdown, .structured]` | `.markdown`, `.htmlCleaned`, `.htmlRaw`, `.screenshot`, `.screenshotFullPage`, `.pdf`, `.links`, `.images`, `.structured`. |
| `extract` | `[PerceiveExtractName]?` | nessuno | `.tables`, `.prices`, `.contacts`, `.metadata`, `.mainContent`, `.headings`, `.structuredData`, `.technologies`, `.all`. |
| `schema` | `JSONObject?` | nessuno | Schema JSON per l'estrazione strutturata tramite il livello LLM. |
| `waitFor`, `waitTimeoutMs` | `String?`, `Int?` | nessuno, `30000` | Un selettore CSS (facoltativamente con prefisso `css:`) oppure `js:<expr>` da attendere, e il relativo budget in ms (da 0 a 60000). |
| `jsCode` | `String?` | nessuno | JavaScript eseguito dopo la navigazione, massimo 20000 caratteri. |
| `viewport` | `PerceiveViewport?` | 1920 per 1080 | `width` da 320 a 3840, `height` da 240 a 2160. |
| `headers`, `cookies`, `auth` | `[String: String]?`, `[BrowserCookie]?`, `HttpBasicAuth?` | nessuno | Header di richiesta, cookie iniettati, credenziali HTTP Basic. |
| `cacheMode` | `PerceiveCacheMode?` | `.enabled` | `.enabled` (cache di 1 ora), `.bypass`, `.refresh`. |
| `pdfOptions` | `PdfOptions?` | nessuno | Ha senso solo quando `outputs` include `.pdf`. |
| `blockResources` | `[PerceiveResourceType]?` | nessuno | `.image`, `.media`, `.font`, `.stylesheet`, `.script`, `.xhr`, `.fetch`, `.websocket`, `.manifest`, `.other`. |
| `respectRobots`, `mobile` | `Bool?` | predefinito del server | Rispetta `robots.txt`; emula un dispositivo mobile. |
| `onlyMainContent` | `Bool?` | `true` | Rimuove navigazione, header, footer e banner dei cookie dall'artefatto markdown e dall'estratto `main_content`. Imposta `false` per la pagina intera. |
| `directDownload` | `Bool?` | `false` | Trasmette byte grezzi invece di un envelope JSON. Preferisci `perceiveDirect`. |

<div class="alert alert-warning">
<strong>Tre opzioni sono dichiarate ma non ancora attive.</strong> <code>proxyUrl</code>, <code>geolocation</code> e <code>actionChain</code> esistono su <code>PerceiveOptions</code> e vengono serializzate sulla rete, ma al momento il server risponde <code>422</code> per tutte e tre. Lasciale a <code>nil</code>.
</div>

Trasmettere un singolo artefatto direttamente su disco salta l'envelope JSON e il giro dell'URL firmato. `perceiveDirect` verifica localmente che tu abbia chiesto esattamente un output che produca un artefatto, così un errore non costa nulla:

```swift
let direct = try await client.v2.perceiveDirect("https://example.com", options: PerceiveOptions(outputs: [.pdf]))
try direct.content.write(to: URL(fileURLWithPath: direct.filename ?? "page.pdf"))

// Riscarica più tardi un artefatto archiviato. Passa nil quando l'operazione ne ha prodotto uno solo.
let saved = try await client.v2.downloadPerceiveArtifact(direct.operationId, output: .pdf)
```

`PerceiveDirectResult` porta con sé `content`, `contentType`, `filename`, `operationId`, `objectKey`, `cacheHit`, `renderQuality`, `sourceStatusCode`, `contentHash` e `warningsCount`, tutti letti dagli header della risposta. Un `410` da `downloadPerceiveArtifact` significa che l'artefatto ha superato la propria finestra di conservazione.

I batch accettano fino a 1000 URL con un unico blocco di opzioni condiviso. I batch piccoli terminano inline; quelli più grandi tornano in coda e tu li interroghi:

```swift
let batch = try await client.v2.perceiveBatch(
    ["https://a.example", "https://b.example"],
    options: PerceiveBatchOptions(outputs: [.markdown], outputMode: .zip)
)
let done = try await client.v2.getPerceiveBatch(batch.jobId)
if done.status == .completed, let zip = done.zip {
    print(zip.url ?? "")
}
```

`outputMode` è `.manifest` (predefinito) oppure `.zip`. `directDownload` viene rifiutato con `422` sui batch.

### Discover

Enumera gli URL di un sito senza rendering nel browser. Veloce, e non esegue mai un rendering.

```swift
let opts = DiscoverOptions(mode: .hybrid, maxUrls: 200, excludePatterns: ["/tag/"])
let found = try await client.v2.discover("https://example.com", options: opts)
print(found.total, found.truncated, found.sources, found.urls)
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `mode` | `DiscoverMode?` | `.hybrid` | `.sitemap`, `.crawl` oppure `.hybrid` (sitemap più scansione HTTP). |
| `maxUrls`, `maxDepth` | `Int?` | `100`, `2` | Da 1 a 1000 URL; profondità di scansione da 1 a 5. |
| `includePatterns`, `excludePatterns` | `[String]?` | nessuno | Allowlist con espressioni regolari, poi denylist applicata dopo di essa. Massimo 50 pattern ciascuna. |
| `sameDomainOnly` | `Bool?` | `true` | Resta sul dominio dell'URL di partenza. |
| `respectRobots` | `Bool?` | predefinito del server | Rispetta `robots.txt`. |

`DiscoverResult` riporta anche `pagesCrawled`, `robotsRespected` e `warnings`, e `sources` contiene i conteggi grezzi per singola fonte rilevati prima della deduplicazione.

### Lookup

Esegue una ricerca web per categorie, renderizzando facoltativamente i primi risultati nella stessa chiamata.

```swift
let search = try await client.v2.lookup(
    "best static site generators",
    options: LookupOptions(category: .web, numResults: 10, perceiveTop: 3)
)
for hit in search.results {
    print(hit.position ?? 0, hit.title ?? "", hit.url ?? "", hit.perceive?.renderQuality ?? 0)
}
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `category` | `LookupCategory?` | `.web` | `.web`, `.news`, `.images`, `.scholar`, `.patents`, `.maps`. |
| `country`, `locale` | `String?` | nessuno | Codice paese `gl` di Google (`"us"`, `"in"`) e lingua dell'interfaccia `hl` (`"en"`). |
| `timeFilter` | `LookupTimeFilter?` | nessuno | `.hour`, `.day`, `.week`, `.month`, `.year`. |
| `numResults`, `page` | `Int?` | `10`, `1` | Da 1 a 100 risultati; pagina da 1 a 10. |
| `location`, `autocorrect` | `String?`, `Bool?` | nessuno, `true` | Località in testo libero come `"Austin, Texas"`; lascia che il provider corregga la query. |
| `perceiveTop` | `Int?` | `0` | Renderizza automaticamente i primi N URL dei risultati, da 0 a 10. Ciascuno esegue un rendering completo nel browser. |

`LookupResult` espone anche `answerBox`, `knowledgeGraph`, `perceiveOperationIds` e `credits`.

### Distill

Estrae dati strutturati dalle pagine seguendo uno schema che definisci tu.

```swift
let extraction = try await client.v2.distill(DistillOptions(
    urls: ["https://example.com/pricing"],
    schema: ["plans": .string("list of plan names with monthly prices")],
    cssSchema: CssSchema(baseSelector: ".plan-card", fields: [
        CssField(name: "name", type: .text, selector: "h3"),
        CssField(name: "price", type: .text, selector: ".price")
    ])
))

let first = extraction.results[0]
print(first.data ?? [:], first.extractionTier, first.fieldsFromCss, first.fieldsFromLlm)
```

`schema` è un `JSONObject`, cioè un `[String: JSONValue]`, quindi funzionano sia una mappa piatta `{field: description}` sia un oggetto JSON-Schema completo. Il `cssSchema` facoltativo viene eseguito per primo e risponde a tutto ciò che i semplici selettori riescono a raggiungere; solo i campi che gli sfuggono salgono al livello LLM, ed `extractionTier` riporta quali livelli hanno effettivamente risposto (`.css`, `.llm`, `.mixed` oppure `.none`). `CssField.type` è uno tra `.text`, `.attribute`, `.html`, `.regex`, `.nested`, `.list` e `.nestedList`, annidabile fino a 5 livelli di profondità.

Sostituisci `urls` con `discoverFrom` per individuare e poi distillare in un'unica chiamata. `DistillDiscoverFrom` accetta `url`, `mode` (predefinito `.hybrid`) e `maxPages` (da 1 a 50, predefinito 10, che limita sia la scoperta sia la distillazione):

```swift
_ = try await client.v2.distill(DistillOptions(
    discoverFrom: DistillDiscoverFrom(url: "https://example.com", mode: .sitemap, maxPages: 10),
    schema: ["title": .string("page title"), "summary": .string("one-line summary")]
))
```

Passare sia `urls` sia `discoverFrom`, o nessuno dei due, solleva `EnconvertError.invalidArgument` prima che venga inviata qualsiasi richiesta.

### Ingest

Trasforma un sito, una lista di URL o una pila di documenti caricati in JSONL suddiviso in chunk e pronto per il RAG. Sempre asincrono.

```swift
let job = try await client.v2.ingest(IngestOptions(
    mode: .sitemap,
    url: "https://docs.example.com",
    maxPages: 100,
    chunk: IngestChunkOptions(maxWords: 512, sentenceOverlap: 1),
    webhookUrl: "https://my.app/hooks/enconvert"
))

let status = try await client.v2.getIngestJob(job.jobId)
if status.status == .completed {
    print(status.totalChunks, status.outputUrl ?? "")
}
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `mode` | `IngestMode?` | `.urls` | `.urls`, `.sitemap` oppure `.crawl`. Il quarto caso, `.files`, è quello che `ingestFiles` riporta sul proprio job; non passarlo qui. |
| `url` | `String?` | nessuno | URL di partenza. Obbligatorio per `.sitemap` e `.crawl`, vietato per `.urls`. |
| `urls` | `[String]?` | nessuno | URL espliciti, massimo 1000. Obbligatori per `.urls`, vietati altrimenti. |
| `maxPages`, `maxDepth` | `Int?` | `50`, `2` | Limite di scoperta per `.sitemap` e `.crawl`, da 1 a 1000; profondità da 1 a 5. |
| `sameDomainOnly` | `Bool?` | `true` | Resta sul dominio dell'URL di partenza. |
| `includePatterns`, `excludePatterns` | `[String]?` | nessuno | Allowlist con espressioni regolari, poi denylist. |
| `respectRobots` | `Bool?` | predefinito del server | Rispetta `robots.txt`. |
| `waitFor`, `waitTimeoutMs` | `String?`, `Int?` | `30000` ms | Selettore o espressione `js:` attesa su ogni pagina, e il relativo budget (da 0 a 60000). |
| `chunk` | `IngestChunkOptions?` | nessuno | `maxWords` da 32 a 4000, predefinito 512. `sentenceOverlap` da 0 a 10, predefinito 1. |
| `webhookUrl` | `String?` | nessuno | Webhook di completamento, firmato in HMAC. |

Le regole su modalità e URL qui sopra vengono applicate lato client: `ingest` solleva `EnconvertError.invalidArgument` invece di effettuare una richiesta condannata se passi `urls` con `mode: .sitemap`. I file caricati attraversano la stessa pipeline e lo stesso ciclo di vita del job:

```swift
let files: [FileInput] = [.path("handbook.pdf"), .path("notes.docx")]
let fileJob = try await client.v2.ingestFiles(files, options: IngestFilesOptions(chunk: IngestChunkOptions(maxWords: 512)))
```

Sono accettati PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT e MD, e file office legacy o ODF, ed è richiesto almeno un file. Gestione dei job e impianto dei webhook:

```swift
let list = try await client.v2.listIngestJobs(V2ListOptions(limit: 20))
let canceled = try await client.v2.cancelIngestJob(job.jobId)   // idempotente

let secret = try await client.v2.getWebhookSecret()
print(secret.signatureHeader, secret.signatureScheme, secret.replayToleranceSeconds)
_ = try await client.v2.rotateWebhookSecret()                   // le vecchie firme smettono subito di essere valide

let retry = try await client.v2.retryIngestWebhook(job.jobId)
print(retry.delivered, retry.attempts, retry.detail)
```

`retryIngestWebhook` risponde `409` quando il job non è completato e `400` quando non ha alcun webhook configurato. `V2ListOptions` accetta `skip` e `limit` (da 1 a 100, predefinito 20).

### Watch

Rifà il rendering di una pagina a cadenza fissa e ti avvisa quando cambia.

```swift
let watcher = try await client.v2.createWatcher("https://example.com/pricing", options: WatchCreateOptions(
    frequencyMinutes: 60,
    diffMode: .auto,
    webhookUrl: "https://my.app/hooks/changes",
    notifyEmail: true
))

let history = try await client.v2.getWatcherSnapshots(watcher.watcherId, options: SnapshotListOptions(limit: 10))
for snapshot in history.snapshots where snapshot.hasChanges {
    print(snapshot.checkedAt, snapshot.changeCount, snapshot.similarity ?? 0)
}
```

| Opzione | Tipo | Predefinito | Descrizione |
|--------|------|---------|-------------|
| `frequencyMinutes` | `Int?` | `60` | Da 60 a 43200. Il minimo orario è invalicabile. |
| `diffMode` | `WatchDiffMode?` | `.auto` | `.auto`, `.text`, `.structured`, `.tables`, `.metadata`. |
| `trackFields` | `JSONObject?` | nessuno | Sottoinsieme di campi o selettori passato al motore di diff. |
| `webhookUrl` | `String?` | nessuno | Webhook di notifica delle modifiche, firmato in HMAC. |
| `notifyEmail` | `Bool?` | `true` | Invia un'email al proprietario del progetto quando ci sono cambiamenti. |

```swift
// Una stringa vuota azzera il webhook; nil lo lascia com'è.
_ = try await client.v2.updateWatcher(watcher.watcherId, updates: WatcherUpdate(webhookUrl: "", status: .paused))

_ = try await client.v2.listWatchers()
_ = try await client.v2.getWatcher(watcher.watcherId)
_ = try await client.v2.deleteWatcher(watcher.watcherId)   // cancellazione logica, idempotente
```

`updateWatcher` richiede almeno un campo e solleva `EnconvertError.invalidArgument` su un `WatcherUpdate` vuoto. `WatchUpdateStatus` accetta solo `.active` oppure `.paused`; la cancellazione passa da `deleteWatcher`, che restituisce il watcher archiviato con stato `.deleted`.

<div class="alert alert-warning">
<strong>I diff degli snapshot contengono contenuto di pagina non attendibile.</strong> <code>WatcherSnapshot.changes</code> è un array di oggetti JSON grezzi prelevati dalla pagina osservata. Effettua l'escape dei valori prima di renderizzarli da qualsiasi parte.
</div>

---

## Opzioni PDF

`PdfOptions` è condiviso da `convertUrlToPdf`, `convertDocument`, `convertWebsiteToPdf`, `PerceiveOptions` e (solo per `grayscale`) `convertToPdf`. Vengono inviati solo i campi che imposti.

```swift
let pdf = PdfOptions(
    pageSize: "A4",
    orientation: .landscape,
    margins: PdfMargins(top: 10, bottom: 10, left: 15, right: 15),
    scale: 0.9,
    grayscale: false,
    header: PdfHeaderFooter(content: "Quarterly Report", height: 15),
    footer: PdfHeaderFooter(content: "Confidential", height: 12)
)
```

| Campo | Tipo | Descrizione |
|-------|------|-------------|
| `pageSize` | `String?` | `"A4"`, `"A3"`, `"Letter"`, `"Legal"` e simili. |
| `pageWidth`, `pageHeight` | `Double?` | Hanno la precedenza su `pageSize` quando sono impostati entrambi insieme. |
| `orientation` | `PdfOrientation?` | `.portrait` oppure `.landscape`. Il valore predefinito è portrait. |
| `margins` | `PdfMargins?` | `top`, `bottom`, `left`, `right`, ciascuno un `Double?`. Tutti e quattro opzionali. |
| `scale` | `Double?` | Scala di rendering, ad esempio `0.9` per il 90%. |
| `grayscale` | `Bool?` | Post-elabora il PDF convertendolo in scala di grigi. |
| `header` | `PdfHeaderFooter?` | `content` (massimo 2000 caratteri) e `height`. |
| `footer` | `PdfHeaderFooter?` | Stessa forma di `header`. |

---

## Gestione degli errori

Swift riceve un solo tipo di errore, `EnconvertError`, modellato come enum invece che come gerarchia di classi. Intercettalo con i pattern di `catch`:

```swift
do {
    _ = try await client.v2.perceive("https://example.com")
} catch EnconvertError.authentication(let message) {
    print("invalid or missing API key: \(message)")
} catch EnconvertError.rateLimit(let message) {
    print("too many requests, back off and retry: \(message)")
} catch let error as EnconvertError {
    print("api error: \(error)")   // viene reso come "[<status>] <message>"
}
```

| Caso | Sollevato per | Codice di stato |
|------|-----------|-------------|
| `.authentication(message:)` | Chiave non valida, mancante o revocata | `401`, `403` (entrambi riportano `401`) |
| `.quota(message:)` | Qualsiasi risposta a cui l'API risponde con `402` | `402` |
| `.rateLimit(message:)` | Rate limit superato | `429` |
| `.api(statusCode:message:)` | Qualsiasi altro 4xx o 5xx | il codice effettivo |
| `.invalidArgument(_:)` | Validazione lato client, prima di qualsiasi richiesta | nessuno |

`EnconvertError` è conforme a `CustomStringConvertible` e `LocalizedError`, quindi `String(describing:)`, `localizedDescription` e l'interpolazione di stringa vengono tutti resi come `"[<status>] <message>"`. Due proprietà di comodo leggono gli stessi valori senza pattern matching: `error.statusCode` (`Int?`, `nil` per `.invalidArgument`) ed `error.message` (il testo senza il prefisso tra parentesi quadre). Una risposta 2xx ben formata a cui manca un campo richiesto dall'SDK emerge come `.api(statusCode: 0, ...)`, il che separa un payload malformato da un vero errore HTTP.

Coppie di conversione non supportate, una chiamata `distill` con sia `urls` sia `discoverFrom`, una chiamata `perceiveDirect` che chiede due artefatti e un `WatcherUpdate` vuoto sollevano tutti `.invalidArgument` prima che la rete venga toccata. I codici di risposta sono catalogati nel [riferimento dei codici di errore](/it/docs/error-codes).

---

## Recupero dei timeout

I rendering lunghi di URL e le conversioni di documenti di grandi dimensioni possono superare il tetto di 60-120 secondi di un reverse proxy anche quando il job termina correttamente sul server. L'SDK se ne tira fuori con il polling, senza che tu scriva codice:

1. Prima di ogni conversione di un singolo file e di un singolo URL, il client genera un UUIDv4, ne rimuove i trattini e lo invia come `job_id`.
2. Se quella richiesta torna con uno stato pari o superiore a 500, il client passa silenziosamente a `GET /v1/convert/status/{job_id}`, interrogandolo ogni 3 secondi. Un `404` lì significa "non ancora registrato" e mantiene attivo il ciclo.
3. Su `success` restituisce il risultato. Su `failed` solleva `.api(statusCode: 500, message:)` con il messaggio del server. Il limite di tempo per il polling è di 5 minuti, superato il quale ottieni `.api(statusCode: 504, message: "Conversion timed out")`.

`ConversionResult.jobId` viene riempito dal client anche quando il percorso sincrono è riuscito e la risposta lo ha omesso, così puoi passarlo tu stesso a `getJobStatus`:

```swift
let status = try await client.getJobStatus(result.jobId ?? "")
if status.status == .success {
    print(status.presignedUrl ?? "")
} else if status.status == .failed {
    print(status.error ?? "conversion failed")
}
```

<div class="alert alert-info">
<strong>I batch dei siti web ne restano fuori di proposito.</strong> <code>convertWebsiteToPdf</code> e <code>convertWebsiteToScreenshot</code> non hanno una riga per singolo job da interrogare, quindi un 5xx lì emerge subito invece di essere ritentato. Nemmeno i metodi V2 usano il fallback sui job.
</div>

---

## Configurazione

```swift
let client = try Enconvert(
    apiKey: ProcessInfo.processInfo.environment["ENCONVERT_API_KEY"] ?? "",
    baseURL: "https://api.enconvert.com",
    timeout: 300
)
```

| Parametro | Tipo | Predefinito | Descrizione |
|-----------|------|---------|-------------|
| `apiKey` | `String` | obbligatorio | Chiave API privata. Una stringa vuota solleva `EnconvertError.invalidArgument`. |
| `baseURL` | `String` | `https://api.enconvert.com` | Sovrascrittura per un gateway self-hosted. Gli slash finali vengono rimossi. |
| `timeout` | `TimeInterval` | `300` | Secondi. Imposta sia `timeoutIntervalForRequest` sia `timeoutIntervalForResource` sull'`URLSession` interna. |

La chiave viaggia come header `X-API-Key` su ogni chiamata all'API. I download presigned escono deliberatamente senza di essa, dato che un URL di storage firmato si autentica da solo e inoltrare la chiave a un host di storage la esporrebbe. Per annullare una singola chiamata, avvolgila in un `Task` e annulla quello: ogni metodo è una semplice funzione `async throws`. `Enconvert` conserva solo proprietà `let` su una `URLSession`, quindi costruisci un unico client all'avvio e riusalo; `client.v2` è un sottile namespace sullo stesso trasporto.

<div class="alert alert-warning">
<strong>Non inserire mai la chiave API direttamente nel codice, e non distribuirla mai in un bundle applicativo.</strong> Leggila dall'ambiente o dal tuo secret manager e tieni il client su un server che controlli. Il pacchetto compila per iOS, tvOS e watchOS così puoi condividere il codice dei modelli tra i target, ma il binario di un'app è un artefatto pubblico: chiunque estragga la tua chiave privata può eseguire conversioni a carico del tuo progetto. Fai chiamare al tuo backend dall'app, e lascia che sia il backend a chiamare EnConvert. Consulta l'[autenticazione](/it/docs/authentication) per i tipi di chiave e la rotazione.
</div>

---

## Struttura del risultato

Le conversioni di un singolo file e di un singolo URL restituiscono un `ConversionResult`:

```swift
public struct ConversionResult: Codable, Equatable, Sendable {
    public let presignedUrl: String
    public let objectKey: String
    public let filename: String
    public let fileSize: Int?
    public let conversionTimeSeconds: Double?
    public let jobId: String?
}
```

Gli URL presigned hanno vita breve. Passa `saveTo` per far trasmettere i byte su disco dall'SDK, che crea le directory padre quando serve, oppure recupera tu stesso l'URL e archivia il file nel tuo bucket per un accesso a lungo termine.

Gli artefatti V2 arrivano come valori `V2OutputArtifact` indicizzati per nome di output, ciascuno con `url` (`String?`, firmato per 15 minuti e rifirmato a ogni GET di stato), `objectKey`, `sizeBytes`, `contentType` ed `expiresIn` (secondi, 900 per impostazione predefinita). `PerceiveResult` li avvolge con i metadati di trasparenza: `renderQuality`, `statusCode`, `deductions`, `cacheHit`, `warnings`, `contentHash`, `urlFinal`, `structured`, `extractionTier`, `tokens`, `costCents`, `durationMs` e `optionsEcho`, che restituisce le opzioni effettivamente rispettate dal server con i segreti ridotti a booleani. I payload definiti dal chiamante (schemi di estrazione, `data` distillati, `trackFields` dei watcher, `changes` dei diff, `extra` delle ricerche) fanno andata e ritorno attraverso `JSONValue`, un enum con i casi `.null`, `.bool`, `.number`, `.string`, `.array` e `.object`, più l'alias `JSONObject` per `[String: JSONValue]`. Ogni tipo di risultato è `Codable`, `Equatable` e `Sendable`, quindi mettere in cache su disco un risultato già interpretato e ricaricarlo più tardi funziona senza alcun lavoro aggiuntivo.

---

## Sorgente e problemi

- **Pacchetto:** `Enconvert`, tramite Swift Package Manager. La versione è esposta a runtime nella costante a livello di modulo `VERSION`
- **GitHub:** [conversionapi/swift-sdk](https://github.com/conversionapi/swift-sdk)
- **Licenza:** MIT. Dipendenze: nessuna, solo `URLSession` e Foundation

Letture correlate: [tutti gli SDK](/it/docs/sdks), [panoramica V2](/it/docs/v2-overview), [perceive](/it/docs/v2-perceive), [discover](/it/docs/v2-discover), [lookup](/it/docs/v2-lookup), [distill](/it/docs/v2-distill), [ingest](/it/docs/v2-ingest), [watch](/it/docs/v2-watch), [panoramica degli endpoint](/it/docs/endpoints-overview), [parametri e opzioni](/it/docs/parameters-options), e la tua [dashboard](/it/dashboard) per le chiavi.

---

## Domande frequenti

### Come converto i file in Swift?

Aggiungi `https://github.com/conversionapi/swift-sdk.git` alle dipendenze del tuo `Package.swift`, costruisci un client con `try Enconvert(apiKey:)`, poi chiama un metodo tipizzato come `convertDocument`, `convertImage` oppure `convertUrlToPdf`. Passa `saveTo` nella struct delle opzioni e l'SDK trasmette il file finito direttamente su quel percorso, creando le directory padre quando serve.

### Come converto un URL in PDF in Swift?

Chiama `try await client.convertUrlToPdf("https://example.com", options: UrlToPdfOptions(saveTo: "page.pdf"))`. Imposta `singlePage: false` per impaginare invece di produrre una singola pagina continua, e passa `pdfOptions:` per dimensione della pagina, orientamento, margini, scala, scala di grigi, intestazioni e piè di pagina. Ricorda che Swift vuole le etichette nell'ordine di dichiarazione, quindi `saveTo:` viene prima di `singlePage:` e `pdfOptions:`.

### Come converto DOCX in PDF in Swift?

`try await client.convertDocument(.path("report.docx"), options: ConvertDocumentOptions(saveTo: "report.pdf"))`. Il formato di output vale `"pdf"` per impostazione predefinita, quindi `outputFormat` può essere omesso. Lo stesso metodo gestisce input XLSX, PPTX, ODT, ODS, ODP, OTS, Pages, Numbers, HTML, Markdown, CSV, JSON, XML, YAML e TOML.

### Come converto HEIC in WebP in Swift?

`try await client.convertImage(.path("photo.heic"), options: ConvertImageOptions(outputFormat: "webp", saveTo: "photo.webp"))`. Il formato di input viene letto dall'estensione del nome file, e tutte e 20 le coppie ordinate tra `jpeg`, `png`, `svg`, `heic` e `webp` funzionano allo stesso modo. Le coppie non supportate sollevano `EnconvertError.invalidArgument` localmente, prima che venga inviata qualsiasi richiesta.

### L'SDK Swift porta con sé dipendenze di terze parti?

No. `Package.swift` dichiara un array `dependencies` vuoto. Tutto gira su `URLSession`, `JSONSerialization` e `Foundation`, con `FoundationNetworking` importato in modo condizionale così il pacchetto compila anche su Linux oltre che sulle piattaforme Apple.

### Come estraggo Markdown pulito da una pagina web in Swift?

Ci sono due opzioni. `client.convertUrlToMarkdown` restituisce Markdown in stile GitHub-Flavored con frontmatter YAML ed è il percorso più semplice. `client.v2.perceive` con `outputs: [.markdown]` ti dà lo stesso Markdown più un punteggio `renderQuality`, una mappa `deductions`, dei `warnings` e la possibilità di aggiungere screenshot, link o estrazione strutturata nello stesso rendering.

### Che cosa significa la qualità del rendering e perché dovrei controllarla?

`renderQuality` è un `Double?` da 0.0 a 1.0 allegato a ogni lettura V2. Scende quando la pagina non si è renderizzata onestamente: una sfida anti-bot, un muro di login, un banner dei cookie sopra un guscio vuoto, oppure uno stato di errore HTTP. Il contenuto viene comunque restituito invece di essere scartato, quindi controlla il punteggio e il dizionario `deductions` che dà un nome a ciascuna penalità prima di dare il testo in pasto a un modello.

### Posso usare l'SDK Swift dentro un'app iOS o macOS?

Solo dietro al tuo backend. Il pacchetto compila per iOS 15, tvOS 15, watchOS 8 e macOS 12 così puoi condividere il codice dei modelli tra i target, ma si autentica con una chiave API privata e gli endpoint V2 rifiutano categoricamente le chiavi pubbliche. Distribuire quella chiave in un binario applicativo significa consegnarla a chiunque scompatti il bundle. Fai chiamare il tuo server dall'app, e chiama EnConvert dal server.

### Che cosa succede quando una conversione lunga incontra il timeout del proxy?

L'SDK invia un `job_id` generato dal client a ogni conversione di un singolo file e di un singolo URL. Se la richiesta restituisce 500 o più, interroga `GET /v1/convert/status/{job_id}` ogni 3 secondi per un massimo di 5 minuti, restituendo il risultato su `success` e sollevando `.api(statusCode: 500, ...)` su `failed`. Superare il limite di tempo produce `.api(statusCode: 504, message: "Conversion timed out")`. Gli invii batch dei siti web saltano deliberatamente questo fallback.

### Come faccio a sapere quali conversioni sono supportate prima di inviare una richiesta?

Chiama `validOutputsFor("json")` per gli output che un dato formato di input supporta, oppure verifica l'appartenenza a `IMPLEMENTED_CONVERSIONS`, l'insieme di tutti i 43 endpoint `{input}-to-{output}` implementati. `convertImage` e `convertDocument` eseguono internamente lo stesso controllo e sollevano `EnconvertError.invalidArgument` con gli output validi elencati per quell'input, prima che venga inviata qualsiasi richiesta.
