Go File Conversion SDK#

github.com/conversionapi/go-sdk is the official EnConvert client for Go 1.21 and newer. It imports as package enconvert and carries zero third-party dependencies: everything runs on net/http, encoding/json, and mime/multipart. Twelve methods on the client cover file conversion, URL rendering, and whole-site batches (DOCX to PDF, HEIC to WebP, URL to PDF, URL to Markdown), and the client.V2 namespace adds twenty-three web intelligence methods for perceiving, discovering, looking up, distilling, ingesting, and watching pages. Every call takes a context.Context first, so cancellation and deadlines stay in your hands.

Module: github.com/conversionapi/go-sdk · Package: enconvert · Source: conversionapi/go-sdk · Go: 1.21+ · Dependencies: none

Install#

go get github.com/conversionapi/go-sdk

The module path ends in go-sdk but the package is named enconvert, so import it under an explicit name: import enconvert "github.com/conversionapi/go-sdk".


Quick start#

package main

import (
    "context"
    "fmt"
    "log"
    "os"

    enconvert "github.com/conversionapi/go-sdk"
)

func main() {
    client, err := enconvert.New(os.Getenv("ENCONVERT_API_KEY"))
    if err != nil {
        log.Fatal(err)
    }

    ctx := context.Background()

    result, err := client.ConvertURLToPDF(ctx, "https://example.com", enconvert.URLToPDFOptions{SaveTo: "page.pdf"})
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(result.Filename, result.PresignedURL)
}

New returns an error only when the API key is empty; see Configuration for the functional options. Reading a page the way an agent should, with a quality score attached, is one call on the V2 namespace:

op, err := client.V2.Perceive(ctx, "https://example.com", enconvert.PerceiveOptions{
    Outputs: []enconvert.PerceiveOutputName{enconvert.PerceiveOutputMarkdown},
})
fmt.Println(op.Outputs["markdown"].URL, *op.RenderQuality) // e.g. 0.93

Every snippet below assumes a client and a ctx built exactly like this.


What the client exposes#

Twelve methods hang off *enconvert.Client and map 1:1 to REST endpoints. Options are always passed as a struct value, never a pointer, so the zero value (enconvert.URLToPDFOptions{}) means "all defaults", and optional numbers and booleans are pointer fields throughout: use the enconvert.Int, enconvert.Bool, enconvert.Float64, and enconvert.String helpers instead of a throwaway local variable.

Method Endpoint Returns
ConvertURLToPDF(ctx, url, opts) POST /v1/convert/url-to-pdf ConversionResult
ConvertURLToScreenshot(ctx, url, opts) POST /v1/convert/url-to-screenshot ConversionResult
ConvertURLToMarkdown(ctx, url, opts) POST /v1/convert/url-to-markdown ConversionResult
ConvertImage(ctx, file, opts) POST /v1/convert/{from}-to-{to} ConversionResult
ConvertDocument(ctx, file, opts) POST /v1/convert/{from}-to-{to} ConversionResult
ConvertToMarkdown(ctx, file, opts) POST /v1/convert/anything-to-markdown ConversionResult
ConvertToPDF(ctx, file, opts) POST /v1/convert/anything-to-pdf ConversionResult
ConvertWebsiteToPDF(ctx, url, opts) POST /v1/convert/website-to-pdf BatchSubmission
ConvertWebsiteToScreenshot(ctx, url, opts) POST /v1/convert/website-to-screenshot BatchSubmission
GetJobStatus(ctx, jobID) GET /v1/convert/status/{jobID} JobStatus
GetBatchStatus(ctx, batchID) GET /v1/convert/batch/{batchID} BatchStatus
WaitForBatch(ctx, batchID, opts) GET /v1/convert/batch/{batchID} (polled) BatchStatus

client.V2 holds twenty-three more methods across six capability groups:

Group Methods Base path
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

File conversion#

Uploads accept any FileSource: enconvert.FilePath("report.docx") for a path on disk (the basename decides the input format and MIME type), enconvert.FileBytes(buf) for raw bytes with no name (uploaded as upload.bin, application/octet-stream), or enconvert.FileInput{Data: buf, Filename: "report.docx"} for raw bytes with an explicit filename and optional ContentType.

ConvertURLToPDF#

Render any reachable URL to PDF.

result, err := client.ConvertURLToPDF(ctx, "https://example.com", enconvert.URLToPDFOptions{
    SinglePage:       enconvert.Bool(false),
    PDFOptions:       &enconvert.PDFOptions{PageSize: "A4", Orientation: "landscape"},
    URLRenderOptions: enconvert.URLRenderOptions{ViewportWidth: enconvert.Int(1440)},
    SaveTo:           "report.pdf",
})
Option Type Default Description
SaveTo string -- Local path to stream the PDF to. Parent directories are created for you.
SinglePage *bool true true renders one continuous page. false paginates using PDFOptions.PageSize.
PDFOptions *PDFOptions -- Page size, orientation, margins, scale, grayscale, header, footer. See PDF options.
ViewportWidth, ViewportHeight *int 1920, 1080 Browser viewport size in pixels.
LoadMedia *bool true Wait for images and video before capture.
EnableScroll *bool true Scroll top to bottom to trigger lazy loaders.
OutputFilename string auto Override the generated filename.
Auth *HTTPBasicAuth -- HTTP Basic credentials for pages behind a login.
Cookies, Headers []BrowserCookie, map[string]string -- Cookies injected before rendering (max 50) and extra request headers (max 20, hop-by-hop rejected).

Everything from ViewportWidth down lives on the embedded URLRenderOptions struct, shared by every URL-based method.

Do not combine Auth with an Authorization header. The API rejects the conflict rather than guessing which one you meant.

ConvertURLToScreenshot and ConvertURLToMarkdown#

Capture a PNG of any URL, or extract clean GitHub-Flavored Markdown with YAML frontmatter (title, description, url, links, images). The Markdown converter strips navigation, footers, ads, and scripts, and keeps the main article body.

shot, err := client.ConvertURLToScreenshot(ctx, "https://example.com", enconvert.URLToScreenshotOptions{
    URLRenderOptions: enconvert.URLRenderOptions{ViewportWidth: enconvert.Int(1440)},
    SaveTo:           "shot.png",
})

article, err := client.ConvertURLToMarkdown(ctx, "https://example.com/article",
    enconvert.URLToMarkdownOptions{SaveTo: "article.md"})

Both take the same URLRenderOptions as ConvertURLToPDF, plus SaveTo. Neither accepts SinglePage or PDFOptions.

ConvertImage#

Convert between jpeg, png, svg, heic, and webp in any direction, or rasterize a PDF to JPEG.

// From a path
result, err := client.ConvertImage(ctx, enconvert.FilePath("photo.heic"),
    enconvert.ConvertImageOptions{OutputFormat: "webp", SaveTo: "photo.webp"})

// From bytes already in memory
buf, _ := os.ReadFile("photo.heic")
result, err = client.ConvertImage(ctx, enconvert.FileInput{Data: buf, Filename: "photo.heic"},
    enconvert.ConvertImageOptions{OutputFormat: "webp", SaveTo: "photo.webp"})
Option Type Required Description
OutputFormat string Yes jpeg, png, svg, heic, or webp. jpg is accepted as an alias for jpeg.
SaveTo string -- Local path to stream the result to.
OutputFilename string -- Override the generated filename.

The input format comes from the filename extension (.jpg, .jpeg, .png, .svg, .heic, .webp, .pdf). Unsupported pairs fail locally, before any network call, with an error listing what is available:

enconvert.ValidOutputsFor("pdf")  // []string{"jpeg"}
enconvert.ValidOutputsFor("json") // []string{"csv", "toml", "xml", "yaml"}

ConvertDocument#

Convert documents and data formats. OutputFormat defaults to pdf when left empty.

// docx to pdf
_, err := client.ConvertDocument(ctx, enconvert.FilePath("report.docx"),
    enconvert.ConvertDocumentOptions{SaveTo: "report.pdf"})

// json to yaml
_, err = client.ConvertDocument(ctx, enconvert.FilePath("data.json"),
    enconvert.ConvertDocumentOptions{OutputFormat: "yaml", SaveTo: "data.yaml"})

Supported inputs: .doc, .docx, .xls, .xlsx, .ppt, .pptx, .html, .htm, .odt, .ods, .odp, .ots, .pages, .numbers, .md, .markdown, .csv, .json, .xml, .yaml, .yml, .toml.

Option Type Default Description
OutputFormat string "pdf" Target format. yml, htm, md, and jpg are normalized to their canonical names.
SaveTo string -- Local path to stream the result to.
OutputFilename string -- Override the generated filename.
PDFOptions *PDFOptions -- Page setup. Only meaningful when the output is PDF.

The 43 implemented pairs, exposed as the enconvert.ImplementedConversions map, are: json to csv, toml, xml, yaml; xml to csv, json; yaml to json; csv to json, xml; toml to json; markdown to html, pdf; html to pdf; doc, excel, ppt, odt, ods, odp, ots, pages, and numbers to pdf; all 20 ordered pairs among jpeg, png, svg, heic, webp; and pdf to jpeg.

EPUB has no dedicated document pair. Send .epub files through ConvertToPDF or ConvertToMarkdown instead.

ConvertToMarkdown#

Auto-detect the input server-side and return clean Markdown. This is the RAG-ingestion building block: PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT and MD, plus legacy and ODF office formats.

_, err := client.ConvertToMarkdown(ctx, enconvert.FilePath("handbook.pdf"),
    enconvert.ConvertToMarkdownOptions{SaveTo: "handbook.md"})

ConvertToMarkdownOptions has two fields, SaveTo and OutputFilename. Images are not supported by this endpoint, and there are no PDF options on it. The output keeps the document's heading hierarchy, so a semantic chunker can split on headings rather than arbitrary character counts.

ConvertToPDF#

Auto-detect the input server-side and return a PDF: office, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, plain text, raster images, SVG, EPUB, or an existing PDF passed through for normalization.

_, err := client.ConvertToPDF(ctx, enconvert.FilePath("slides.pptx"),
    enconvert.ConvertToPDFOptions{SaveTo: "slides.pdf"})

// PDF passthrough, converted to grayscale
_, err = client.ConvertToPDF(ctx, enconvert.FilePath("scan.pdf"), enconvert.ConvertToPDFOptions{
    PDFOptions: &enconvert.PDFOptions{Grayscale: enconvert.Bool(true)},
    SaveTo:     "scan-gray.pdf",
})
Only PDFOptions.Grayscale is honored here. Every other geometry field is ignored on anything-to-pdf. Use ConvertDocument or ConvertURLToPDF when you need page size, orientation, margins, scale, headers, or footers.

ConvertToPDFOptions has three fields: SaveTo, OutputFilename, and PDFOptions.

ConvertWebsiteToPDF and ConvertWebsiteToScreenshot#

Discover every page of a site, convert each in the background, and collect a single ZIP. Both are async only: they return a BatchSubmission, and you poll with GetBatchStatus or block with WaitForBatch. A private API key with crawl access is required.

batch, err := client.ConvertWebsiteToPDF(ctx, "https://example.com", enconvert.WebsiteToPDFOptions{
    WebsiteConversionOptions: enconvert.WebsiteConversionOptions{CrawlMode: enconvert.CrawlModeSitemap},
})

status, err := client.WaitForBatch(ctx, batch.BatchID, enconvert.WaitForBatchOptions{SaveTo: "site.zip"})
fmt.Println(status.Completed, "of", status.Total, "pages converted")
Option Type Default Description
CrawlMode CrawlMode auto CrawlModeAuto, CrawlModeSitemap (sitemap.xml only), or CrawlModeFull (sitemap plus BFS crawl).
IncludePatterns []string -- Only crawl URLs matching these patterns, full crawl mode.
ExcludePatterns []string -- Skip URLs matching these patterns, full crawl mode.
NotificationEmail string project owner Address notified when the batch finishes.
CallbackURL string -- Webhook POSTed when the batch finishes.
SinglePage *bool server default PDF only. Continuous page versus paginated.
PDFOptions *PDFOptions -- PDF only. Applied to every page.

Everything on URLRenderOptions is accepted here too, and is sent per page only when you set it. WaitForBatchOptions takes Interval (default 5 seconds), Timeout (default 30 minutes), and SaveTo. Blowing the timeout returns an *APIError with status 504; a finished batch with no ZIP returns one with status 500. ConvertWebsiteToScreenshot is identical minus SinglePage and PDFOptions, and produces a ZIP of PNGs.


Web intelligence (V2)#

Every V2 read carries a RenderQuality score from 0.0 to 1.0, exposed as a *float64 on PerceiveResult, DistillItem, WatcherSnapshot, and the direct-download result. A low score means the page did not render cleanly: a challenge page, a cookie wall, an empty SPA shell, an HTTP error. The content still comes back, flagged, alongside a Deductions map naming each penalty that fired and a Warnings slice, so a bad read never quietly enters your agent's context. All V2 methods require a private API key; public keys are rejected. Gate on the score before you trust anything:

if op.RenderQuality != nil && *op.RenderQuality < 0.6 {
    log.Printf("low quality read of %s: %v", op.URL, op.Deductions)
}

Perceive#

Render one URL into the artifacts you ask for. Synchronous, with artifact URLs signed for 15 minutes.

op, err := client.V2.Perceive(ctx, "https://example.com", enconvert.PerceiveOptions{
    Outputs:  []enconvert.PerceiveOutputName{enconvert.PerceiveOutputMarkdown, enconvert.PerceiveOutputStructured},
    Extract:  []enconvert.PerceiveExtractName{enconvert.PerceiveExtractTables, enconvert.PerceiveExtractMetadata},
    Viewport: &enconvert.PerceiveViewport{Width: enconvert.Int(1440)},
})
fmt.Println(op.OperationID, op.Outputs["markdown"].URL, op.Structured, op.ExtractionTier)

// Re-sign the artifact URLs later
again, err := client.V2.GetPerceiveOperation(ctx, op.OperationID)
Option Type Default Description
Outputs []PerceiveOutputName markdown, structured markdown, html_cleaned, html_raw, screenshot, screenshot_full_page, pdf, links, images, structured.
Extract []PerceiveExtractName -- tables, prices, contacts, metadata, main_content, headings, structured_data, technologies, all.
Schema map[string]any -- JSON schema for structured extraction through the LLM tier.
WaitFor, WaitTimeoutMs string, *int 30000 ms A CSS selector, optionally prefixed css:, or js:<expr>, and its budget (0 to 60000).
JSCode string -- JavaScript executed after navigation, max 20000 characters.
Viewport *PerceiveViewport 1920 x 1080 Width 320 to 3840, Height 240 to 2160.
Headers, Cookies, Auth map[string]string, []BrowserCookie, *HTTPBasicAuth -- Request headers, injected cookies, HTTP Basic credentials.
CacheMode PerceiveCacheMode enabled enabled (1 hour cache), bypass, refresh.
PDFOptions *PDFOptions -- Only meaningful when Outputs includes pdf.
BlockResources []PerceiveResourceType -- image, media, font, stylesheet, script, xhr, fetch, websocket, manifest, other.
RespectRobots, Mobile *bool server default Honor robots.txt; emulate a mobile device.
OnlyMainContent *bool true Strip nav, header, footer, and cookie banners from the markdown artifact and the main_content extract. Set false for the full page.
DirectDownload *bool false Stream raw bytes instead of a JSON envelope. Prefer PerceiveDirect.
Three options are declared but not live yet. ProxyURL, Geolocation, and ActionChain are accepted by the Go struct and serialized, but the server currently answers 422 for all three. Leave them unset.

Streaming a single artifact straight to disk skips the JSON envelope. PerceiveDirect validates locally that you asked for exactly one artifact-producing output:

direct, err := client.V2.PerceiveDirect(ctx, "https://example.com", enconvert.PerceiveOptions{
    Outputs: []enconvert.PerceiveOutputName{enconvert.PerceiveOutputPDF},
})
os.WriteFile(direct.Filename, direct.Content, 0o644)

// Re-download a stored artifact later. Pass "" when the operation made only one.
saved, err := client.V2.DownloadPerceiveArtifact(ctx, direct.OperationID, enconvert.PerceiveOutputPDF)

PerceiveDirectResult carries Content, ContentType, Filename, OperationID, ObjectKey, CacheHit, RenderQuality, SourceStatusCode, ContentHash, and WarningsCount, all read from response headers. A 410 *APIError from DownloadPerceiveArtifact means the artifact aged out of its retention window.

Batches take up to 1000 URLs with one shared options block. Small batches finish inline; larger ones come back queued, so poll GetPerceiveBatch until Status is PerceiveBatchStatusCompleted and read Zip or Items:

batch, err := client.V2.PerceiveBatch(ctx, []string{"https://a.example", "https://b.example"},
    enconvert.PerceiveBatchOptions{OutputMode: enconvert.PerceiveBatchOutputZip})
done, err := client.V2.GetPerceiveBatch(ctx, batch.JobID)

OutputMode is PerceiveBatchOutputManifest (default) or PerceiveBatchOutputZip. DirectDownload is rejected with 422 on batches.

Discover#

Enumerate a site's URLs from its sitemap, an HTTP crawl, or both. No browser is started, so it is fast and cheap.

found, err := client.V2.Discover(ctx, "https://example.com", enconvert.DiscoverOptions{
    Mode: enconvert.DiscoverModeHybrid, MaxURLs: enconvert.Int(200), ExcludePatterns: []string{"/tag/"},
})
fmt.Println(found.Total, found.Truncated, found.Sources)
Option Type Default Description
Mode DiscoverMode hybrid DiscoverModeSitemap, DiscoverModeCrawl, or DiscoverModeHybrid (sitemap plus HTTP crawl).
MaxURLs, MaxDepth *int 100, 2 1 to 1000 URLs, crawl depth 1 to 5.
IncludePatterns, ExcludePatterns []string -- Regex allowlist then denylist, re.search semantics, max 50 each.
SameDomainOnly *bool true Stay on the seed URL's domain.
RespectRobots *bool server default Honor robots.txt.

DiscoverResult gives you URL, Mode, Total, URLs, PagesCrawled, Truncated, RobotsRespected, Sources (raw counts per source before dedup), and Warnings.

Lookup#

Categorized web search, optionally rendering the top results in the same round trip.

search, err := client.V2.Lookup(ctx, "best static site generators", enconvert.LookupOptions{
    Category: enconvert.LookupCategoryWeb, NumResults: enconvert.Int(10), PerceiveTop: enconvert.Int(3),
})
for _, hit := range search.Results {
    fmt.Println(hit.Position, hit.Title, hit.URL)
    if hit.Perceive != nil {
        fmt.Println(hit.Perceive.Outputs["markdown"].URL)
    }
}
Option Type Default Description
Category LookupCategory web web, news, images, scholar, patents, maps.
Country, Locale string -- Google gl country code (us, in) and hl interface language (en).
TimeFilter LookupTimeFilter -- hour, day, week, month, year.
NumResults, Page *int 10, 1 1 to 100 results, page 1 to 10.
Location string -- Free-text location, for example Austin, Texas.
Autocorrect *bool true Let the provider fix typos in the query.
PerceiveTop *int 0 0 to 10. Runs a full browser render of the top N result URLs and attaches each PerceiveResult inline.

LookupResult also carries AnswerBox, KnowledgeGraph, PerceiveOperationIDs, and Warnings.

Distill#

Schema-driven structured extraction. Provide exactly one of URLs (max 50) or DiscoverFrom; Schema is always required. Both rules are checked locally before any request goes out.

extraction, err := client.V2.Distill(ctx, enconvert.DistillOptions{
    URLs:   []string{"https://example.com/pricing"},
    Schema: map[string]any{"plans": "list of plan names with monthly prices"},
    CSSSchema: &enconvert.CSSSchema{
        BaseSelector: ".plan-card",
        Fields: []enconvert.CSSField{
            {Name: "name", Type: enconvert.CSSFieldText, Selector: "h3"},
            {Name: "price", Type: enconvert.CSSFieldText, Selector: ".price"},
        },
    },
})
first := extraction.Results[0]
fmt.Println(first.Data, first.ExtractionTier, first.FieldsFromCSS, first.FieldsFromLLM)

The optional CSSSchema runs first and answers whatever it can with plain selectors; only the fields it misses escalate to the LLM tier, and ExtractionTier reports which tiers actually answered (css, llm, mixed, or none). CSSField.Type is one of text, attribute, html, regex, nested, list, or nested_list, nested up to 5 levels deep.

Swap URLs for DiscoverFrom to discover then distill in one call. DistillDiscoverFrom takes URL, Mode (default hybrid), and MaxPages (1 to 50, default 10, capping both discovery and distillation):

_, err = client.V2.Distill(ctx, enconvert.DistillOptions{
    DiscoverFrom: &enconvert.DistillDiscoverFrom{URL: "https://example.com", MaxPages: enconvert.Int(10)},
    Schema:       map[string]any{"title": "page title", "summary": "one-line summary"},
})

Ingest#

Turn a site, a URL list, or a stack of uploaded documents into chunked, RAG-ready JSONL. Always asynchronous.

job, err := client.V2.Ingest(ctx, enconvert.IngestOptions{
    Mode:       enconvert.IngestModeSitemap,
    URL:        "https://docs.example.com",
    MaxPages:   enconvert.Int(100),
    Chunk:      &enconvert.IngestChunkOptions{MaxWords: enconvert.Int(512), SentenceOverlap: enconvert.Int(1)},
    WebhookURL: "https://my.app/hooks/enconvert",
})

status, err := client.V2.GetIngestJob(ctx, job.JobID)
if status.Status == enconvert.IngestStatusCompleted {
    fmt.Println(status.TotalChunks, status.OutputURL)
}
Option Type Default Description
Mode IngestMode urls IngestModeURLs, IngestModeSitemap, IngestModeCrawl, IngestModeFiles.
URL string -- Seed URL. Required for sitemap and crawl, forbidden for urls.
URLs []string -- Explicit URLs, max 1000. Required for urls, forbidden otherwise.
MaxPages, MaxDepth *int 50, 2 Discovery cap 1 to 1000 for sitemap and crawl, depth 1 to 5.
SameDomainOnly *bool true Stay on the seed URL's domain.
IncludePatterns, ExcludePatterns []string -- Regex allowlist, then denylist.
RespectRobots *bool server default Honor robots.txt.
WaitFor, WaitTimeoutMs string, *int 30000 ms Selector or js: expression awaited per page, and its budget (0 to 60000).
Chunk *IngestChunkOptions -- MaxWords 32 to 4000, default 512. SentenceOverlap 0 to 10, default 1.
WebhookURL string -- Completion webhook, HMAC-signed.

The mode and URL rules above are enforced client-side: Ingest returns a plain Go error, not an API round trip, if you send URLs with Mode: IngestModeSitemap.

Uploaded files run through the same pipeline and the same job lifecycle:

fileJob, err := client.V2.IngestFiles(ctx,
    []enconvert.FileSource{enconvert.FilePath("handbook.pdf"), enconvert.FilePath("notes.docx")},
    enconvert.IngestFilesOptions{Chunk: &enconvert.IngestChunkOptions{MaxWords: enconvert.Int(512)}})

PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT and MD, and legacy or ODF office files are accepted; at least one file is required. Job management and webhook plumbing:

list, err := client.V2.ListIngestJobs(ctx, enconvert.V2ListOptions{Limit: enconvert.Int(20)})
canceled, err := client.V2.CancelIngestJob(ctx, job.JobID) // idempotent
secret, err := client.V2.GetWebhookSecret(ctx)             // Secret, SignatureHeader, SignatureScheme, ...
rotated, err := client.V2.RotateWebhookSecret(ctx)         // old signatures stop verifying at once
retry, err := client.V2.RetryIngestWebhook(ctx, job.JobID)

RetryIngestWebhook answers 409 when the job is not completed and 400 when it has no webhook configured. V2ListOptions takes Skip and Limit (1 to 100, default 20).

Watch#

Re-render a page on a fixed cadence and get notified when it changes.

watcher, err := client.V2.CreateWatcher(ctx, "https://example.com/pricing", enconvert.WatchCreateOptions{
    FrequencyMinutes: enconvert.Int(60),
    DiffMode:         enconvert.WatchDiffAuto,
    WebhookURL:       "https://my.app/hooks/changes",
})

history, err := client.V2.GetWatcherSnapshots(ctx, watcher.WatcherID, enconvert.SnapshotListOptions{Limit: enconvert.Int(10)})
for _, snap := range history.Snapshots {
    fmt.Println(snap.CheckedAt, snap.HasChanges, snap.ChangeCount, snap.Changes)
}
Option Type Default Description
FrequencyMinutes *int 60 60 to 43200. The hourly floor is hard.
DiffMode WatchDiffMode auto WatchDiffAuto, WatchDiffText, WatchDiffStructured, WatchDiffTables, WatchDiffMetadata.
TrackFields map[string]any -- Field or selector subset handed to the diff engine.
WebhookURL, NotifyEmail string, *bool --, true HMAC-signed change webhook, and whether to email the project owner.
// A pointer to the empty string clears the webhook; nil leaves it alone.
_, err = client.V2.UpdateWatcher(ctx, watcher.WatcherID, enconvert.WatcherUpdate{
    Status: enconvert.WatcherStatusPaused, WebhookURL: enconvert.String(""),
})
_, err = client.V2.DeleteWatcher(ctx, watcher.WatcherID) // soft delete, idempotent

UpdateWatcher requires at least one field and returns a plain Go error if you hand it an empty struct. Status accepts only WatcherStatusActive or WatcherStatusPaused; deleting goes through DeleteWatcher, which returns the tombstoned watcher with status deleted. ListWatchers and GetWatcher round out the group.

Snapshot diffs contain untrusted page content. WatcherSnapshot.Changes is a slice of raw maps lifted from the watched page. Escape the values before rendering them anywhere.

PDF options#

PDFOptions is shared by ConvertURLToPDF, ConvertDocument, ConvertWebsiteToPDF, PerceiveOptions, and (for Grayscale only) ConvertToPDF. Only the fields you set are sent.

opts := &enconvert.PDFOptions{
    PageSize:    "A4",
    Orientation: "landscape",
    Margins:     &enconvert.PDFMargins{Top: enconvert.Float64(10), Bottom: enconvert.Float64(10)},
    Scale:       enconvert.Float64(0.9),
    Footer:      &enconvert.PDFHeaderFooter{Content: "Confidential", Height: enconvert.Float64(20)},
}
Field Type Description
PageSize string "A4", "A3", "Letter", "Legal", and friends.
PageWidth, PageHeight *float64 Set both together to override PageSize.
Orientation string "portrait" or "landscape". Defaults to portrait.
Margins *PDFMargins Top, Bottom, Left, Right, each a *float64 in millimetres. All four optional.
Scale *float64 Render scale, for example 0.9 for 90%.
Grayscale *bool Post-process the PDF to grayscale.
Header, Footer *PDFHeaderFooter Each has Content (max 2000 characters) and Height.

Error handling#

Go has no exception classes, so every API failure is one concrete type, *enconvert.APIError, plus three predicates. Error() renders as "[<status>] <message>".

result, err := client.ConvertURLToPDF(ctx, "https://example.com", enconvert.URLToPDFOptions{})
switch {
case err == nil:
    fmt.Println(result.PresignedURL)
case enconvert.IsAuthenticationError(err):
    log.Println("invalid or missing API key")
case enconvert.IsRateLimitError(err):
    log.Println("too many requests, back off and retry")
case enconvert.IsQuotaError(err):
    log.Println("request rejected with 402")
default:
    var apiErr *enconvert.APIError
    if errors.As(err, &apiErr) {
        log.Printf("api error [%d]: %s", apiErr.StatusCode, apiErr.Message)
    } else {
        log.Println(err) // network failure, context cancellation, local validation
    }
}
Check Raised on Status code
IsAuthenticationError(err) Invalid, missing, or revoked key 401, 403 (both recorded as 401)
IsQuotaError(err) Any response the API answers with 402 402
IsRateLimitError(err) Rate limit exceeded 429
errors.As(err, &apiErr) Any other 4xx or 5xx the actual code

Errors that never reach the network, such as an unsupported conversion pair, a Distill call with both URLs and DiscoverFrom, or a PerceiveDirect call asking for two artifacts, come back as plain error values from errors.New or fmt.Errorf, not *APIError. Response codes are catalogued in the error codes reference.


Timeout recovery#

Long URL renders and large document conversions can outlive a reverse proxy's 60 to 120 second ceiling even when the job finishes fine on the server. The SDK polls its way out of that, with no code from you:

  1. Before each single-file and single-URL conversion, the client generates a UUIDv4 and sends it as job_id.
  2. If that request comes back >= 500, the client silently switches to GET /v1/convert/status/{job_id}, polling every 3 seconds.
  3. On success it returns the result. On failed it returns *APIError with status 500 and the server's message.
  4. The polling deadline is 5 minutes, after which you get an *APIError with status 504 and the message Conversion timed out.

ConversionResult.JobID is always populated, even when the sync path succeeded and the response omitted it, so you can hand it to GetJobStatus yourself:

status, err := client.GetJobStatus(ctx, result.JobID)
switch status.Status {
case enconvert.JobStatusSuccess:
    fmt.Println(status.PresignedURL)
case enconvert.JobStatusFailed:
    log.Println(status.Error)
}
Website batches opt out on purpose. ConvertWebsiteToPDF and ConvertWebsiteToScreenshot have no per-job row to poll, so a 5xx there surfaces immediately instead of being retried. V2 methods do not use job fallback either. Polling runs inside the context.Context you pass, so cancelling the context aborts the wait at once.

Configuration#

client, err := enconvert.New(os.Getenv("ENCONVERT_API_KEY"),
    enconvert.WithTimeout(300*time.Second),
    enconvert.WithBaseURL("https://api.enconvert.com"),
)
Constructor input Type Default Description
apiKey (first argument) string required Private API key. New returns an error when it is empty.
WithTimeout(d) time.Duration 300 * time.Second Sets Timeout on the internal *http.Client, covering downloads too.
WithBaseURL(u) string https://api.enconvert.com Override for a self-hosted gateway. Trailing slashes are stripped.

Per-call deadlines layer on top of the client timeout through the context: wrap it with context.WithTimeout(context.Background(), 30*time.Second) and pass that as the first argument.

Never hardcode the API key. Read it from an environment variable or your secret manager, and keep it on the server. Anyone holding your private key can run conversions against your project.

The client is safe to share across goroutines: it holds an *http.Client and no per-request mutable state. Build one at startup and reuse it. See authentication for key types and rotation.


Result shape#

Single-file and single-URL conversions return a ConversionResult:

type ConversionResult struct {
    PresignedURL          string   // signed download URL for the output
    ObjectKey             string   // storage object key
    Filename              string   // server-side filename
    FileSize              *int64   // bytes, nil when the API omits it
    ConversionTimeSeconds *float64 // nil when the API omits it
    JobID                 string   // always set by the client
}

Presigned URLs are short-lived. Pass SaveTo to have the SDK stream the bytes to disk for you, or fetch the URL yourself and store the file in your own bucket if you need long-term access. The download deliberately does not carry your API key, since a presigned URL is self-authenticating and forwarding the key to a storage host would leak it.

V2 artifacts arrive as V2OutputArtifact values keyed by output name, each carrying URL (pre-signed for 15 minutes and re-signed on every status GET), ObjectKey, SizeBytes, ContentType, and ExpiresIn (900 seconds by default). PerceiveResult wraps them with the honesty metadata: RenderQuality, StatusCode, Deductions, CacheHit, Warnings, ContentHash, URLFinal, Structured, ExtractionTier, Tokens, CostCents, DurationMs, and OptionsEcho, which echoes back the options the server actually honored with secrets reduced to booleans.


Source and issues#

  • Module and source: github.com/conversionapi/go-sdk
  • Version: exposed at runtime as the enconvert.Version constant
  • License: MIT, no third-party dependencies

Related reading: all SDKs, V2 overview, perceive, discover, lookup, distill, ingest, watch, endpoints overview, parameters and options, and your dashboard for keys.


Frequently asked questions#

How do I convert files in Go?#

Run go get github.com/conversionapi/go-sdk, build a client with enconvert.New(os.Getenv("ENCONVERT_API_KEY")), then call a typed method such as ConvertDocument, ConvertImage, or ConvertURLToPDF. Pass SaveTo in the options struct and the SDK streams the finished file straight to that path, creating parent directories as needed.

How do I convert a URL to PDF in Go?#

Call client.ConvertURLToPDF(ctx, url, enconvert.URLToPDFOptions{SaveTo: "page.pdf"}). Set SinglePage: enconvert.Bool(false) to paginate instead of producing one continuous page, and pass PDFOptions for page size, orientation, margins, scale, grayscale, headers, and footers.

How do I convert DOCX to PDF in Go?#

client.ConvertDocument(ctx, enconvert.FilePath("report.docx"), enconvert.ConvertDocumentOptions{SaveTo: "report.pdf"}). The output format defaults to pdf, so you can leave OutputFormat empty. The same method handles XLSX, PPTX, ODT, ODS, ODP, OTS, Pages, Numbers, HTML, Markdown, CSV, JSON, XML, YAML, and TOML input.

How do I convert HEIC to WebP in Go?#

client.ConvertImage(ctx, enconvert.FilePath("photo.heic"), enconvert.ConvertImageOptions{OutputFormat: "webp", SaveTo: "photo.webp"}). The input format is read from the filename extension, and all 20 ordered pairs among jpeg, png, svg, heic, and webp work the same way. Unsupported pairs fail locally before any request is sent.

Does the Go SDK pull in any third-party dependencies?#

No. go.mod declares the module and a Go 1.21 floor and nothing else. The client is built on net/http, encoding/json, mime/multipart, and crypto/rand from the standard library, so it adds no transitive supply chain to your build.

How do I scrape a web page into clean Markdown in Go?#

Two options. client.ConvertURLToMarkdown returns GitHub-Flavored Markdown with YAML frontmatter and is the simplest path. client.V2.Perceive with Outputs: []enconvert.PerceiveOutputName{enconvert.PerceiveOutputMarkdown} gives you the same Markdown plus a RenderQuality score, Deductions, Warnings, and the option to add screenshots, links, or structured extraction in the same render.

What does render quality mean and why should I check it?#

RenderQuality is a *float64 from 0.0 to 1.0 attached to every V2 read. It drops when the page did not render honestly: a bot challenge, a login wall, a cookie banner over an empty shell, or an HTTP error status. The content is still returned rather than swallowed, so check the score (and the Deductions map naming each penalty) before feeding the text to a model.

How do I set a per-request timeout or cancel a conversion in Go?#

Every method takes a context.Context first. Wrap it with context.WithTimeout or context.WithCancel for per-call control; WithTimeout on the constructor sets the floor-level HTTP client timeout for all calls, including the download of a SaveTo file.

What happens when a long conversion hits the proxy timeout?#

The SDK sends a client-generated job_id with each single-file and single-URL conversion. If the request returns >= 500, it polls GET /v1/convert/status/{job_id} every 3 seconds for up to 5 minutes, returning the result on success and an *APIError on failed. Exceeding the deadline yields an *APIError with status 504. Website batch submissions deliberately skip this fallback.

Can I ship the Go SDK inside a desktop or mobile client?#

No. It authenticates with a private API key, and V2 endpoints reject public keys outright. Keep the client on a server you control and let your app talk to that. See authentication for the key model.

Is the Go client safe to use from multiple goroutines?#

Yes. *enconvert.Client wraps a single *http.Client and keeps no mutable per-request state, so build one at startup and share it everywhere.