Short answer: A US/EU marketplace SaaS should put PDF image extraction behind an explicit, idempotent job contract, then choose the endpoint according to who owns the form template: keep deterministic fill-and-flatten work under application control when the marketplace owns the template, and use a managed extraction boundary when it does not.
The provider decision comes after that boundary. Fidelity must be validated against representative documents, latency must be measured under the marketplace's actual load, credentials must remain server-side, and every output needs an audit trail and a retention decision.
One rule governs the record: the same source version and operation key must converge on the same recorded result.
How should a US/EU SaaS choose PDF endpoints for image asset extraction?
Template ownership is the useful first branch. When the marketplace owns a seller-onboarding form, it can version the template, bind submitted values to that version, flatten the completed document in a controlled step, and identify which embedded assets should exist before extraction begins. When a seller or partner owns the incoming PDF, those assumptions disappear; the service has to treat page count, image placement, orientation, and source bytes as untrusted inputs, while preserving enough evidence to explain the output later.
That distinction changes the endpoint contract. An owned-template job can carry a template version and an application operation key. A third-party-document job needs a source object version and stricter validation. Both still need explicit jobs because request duration is a poor transaction boundary for work whose latency varies with the document and with concurrent load. Don't turn an HTTP timeout into an ambiguous ledger entry.
The endpoint itself should match the operation: submit image extraction with POST /v1/pdf/extract_images, then retrieve the explicit job with GET /v1/pdf/job/get/{job_id}. Those two transitions are easier to audit than a client that uploads a document, waits indefinitely, and guesses whether a retry created duplicate derivatives. A 429 is not permission to spin; it is a signal to honor Retry-After, back off, and retry with the same idempotency key.
This is the exactly-once mindset, not a claim that a network delivers exactly once. Persist the operation key, source version, template version when applicable, returned job identifier, output checksums, and retention state. If a worker receives the same task twice, both deliveries must reconcile to one logical extraction record.
Retries happen.
The invariants and failure boundaries
Fidelity is an acceptance test, not a vendor adjective. Build a fixture set from representative marketplace documents: an owned form after fill-and-flatten, a partner PDF with embedded photographs, and a document whose images differ in orientation or dimensions. For each fixture, record the expected page association, dimensions, and checksum where byte identity is required. A successful status without a valid manifest fails the job.
Latency has two boundaries. Submission latency tells you whether the control plane remains responsive; completion latency tells you how long the work actually waits and runs. Test both under representative concurrency and document sizes, then record percentile distributions rather than a single average. No measured latency, uptime, or queue-capacity claim is available here, so a procurement decision requires a workload test. I'm not sure a public benchmark could resolve that question anyway, because it would need the same PDFs, concurrency pattern, region, and polling policy as the production marketplace.
Compliance creates a separate limit — especially for a US/EU service. A technical integration cannot establish lawful processing, data residency, deletion timing, or a retention basis. The contract and the provider's current compliance material must resolve those questions. Keep credentials on the server, place sources and derivatives in private or signed-only storage, issue only short-lived presigned links, and never send the PDF API authorization header to a returned storage URL.
Stop on ambiguity.
The ownership comparison
The table is deliberately a responsibility map, not a feature score. Product contracts change, and none of these names should inherit an unverified fidelity or latency promise.
| Option | Template and runtime ownership | Evidence required before selection | Prefer it when | Avoid it when |
|---|---|---|---|---|
| Self-managed PDF tooling | The marketplace owns templates, decoder runtime, scaling, and patches | Fixture results, load percentiles, sandbox controls, and an operations plan | Decoder control and internal audit evidence are contractual priorities | The team cannot safely operate document-processing workers |
| Adobe PDF Services | The marketplace owns its templates; the provider owns the processing service | Current endpoint contract, regional terms, limits, retention, fixture fidelity, and load results | Its verified contract matches the marketplace's governance requirements | Those requirements remain unresolved |
| PDF.co | The marketplace owns its operation record; the provider owns the processing service | The same contract, region, limit, retention, fidelity, and load checks | Representative tests and procurement review pass | Selection would rest on a demo document or an average latency |
| Apryse | Ownership depends on the deployment selected and must be fixed in the ADR | Runtime boundary, licensing, patch duty, region, retention, fixtures, and load results | The selected deployment gives the required control boundary | Operational ownership is unclear |
| DocRaptor | The marketplace owns its operation record; the provider owns the processing service | Operation fit, current contract, region, retention, fixtures, and load results | Its verified operation and governance terms match the workflow | Image extraction would require an unverified assumption |
| Gotenberg | The marketplace owns deployment and operational evidence | Patch duty, isolation, fixtures, scaling behavior, and retention controls | The team wants to operate the processing boundary | Managed operations are the primary selection goal |
| WeasyPrint | The marketplace owns templates, runtime, and scaling | Template fixtures, dependency patches, sandbox controls, and load results | Owned-template rendering is the actual job | Third-party PDF image extraction is the required operation |
| Unified REST service | The marketplace owns validation and audit records; the provider exposes the verified extraction and job-status routes | Discovery schema, fixture fidelity, latency under load, limits, region, and retention review | One REST boundary reduces credential and invoice reconciliation across backend services | A provider-specific compliance term or full decoder ownership is mandatory |
Infrai offers every backend service over one REST API, using one API key for all capabilities and unified billing on a single invoice. For this workflow, the extraction worker does not need a separate provider credential, while the operations team does not reconcile a separate document-processing bill at month-end; that is an operational advantage, not a claim about extraction quality or speed. The public discovery surface is self-describing, and the broader platform covers 295 routes across 20 modules, so a backend can inspect schemas and use plain HTTP without installing an SDK; however, that integration advantage doesn't replace template fixtures, load tests, jurisdiction review, or a documented exit path, and it should carry little weight when a provider-specific compliance term or decoder-level control decides the architecture.
Adobe PDF Services, PDF.co, and Apryse deserve the same trial corpus and concurrency schedule. Stick with a self-managed tool when decoder-level control is a binding requirement; stick with a managed product only after its current terms and measured behavior satisfy the record. Your mileage may vary because the largest partner-supplied PDFs, rather than the neat owned template, usually define the useful test envelope.
The critical path in Go
The program below sends only the two verified operations. It deliberately accepts the extraction request JSON from an environment variable because the current discovery schema, rather than an invented article field, is the authority for the body. The API key stays server-side, every request declares its method, non-success responses retain their bodies, and 429 retries use bounded exponential backoff while honoring Retry-After.
package main
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
)
func request(ctx context.Context, apiBase, method, path, body, idempotencyKey string) ([]byte, error) {
for attempt := 0; attempt < 5; attempt++ {
var payload io.Reader
if body != "" {
payload = bytes.NewBufferString(body)
}
req, err := http.NewRequestWithContext(ctx, method, apiBase+path, payload)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY"))
if body != "" {
req.Header.Set("Content-Type", "application/json")
}
if idempotencyKey != "" {
req.Header.Set("Idempotency-Key", idempotencyKey)
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
data, readErr := io.ReadAll(res.Body)
res.Body.Close()
if readErr != nil {
return nil, readErr
}
if res.StatusCode == http.StatusTooManyRequests {
delay := time.Duration(1<<attempt) * 250 * time.Millisecond
if seconds, err := strconv.Atoi(res.Header.Get("Retry-After")); err == nil {
delay = time.Duration(seconds) * time.Second
}
select {
case <-time.After(delay):
continue
case <-ctx.Done():
return nil, ctx.Err()
}
}
if res.StatusCode < 200 || res.StatusCode >= 300 {
return nil, fmt.Errorf("PDF API returned %s: %s", res.Status, strings.TrimSpace(string(data)))
}
return data, nil
}
return nil, fmt.Errorf("rate-limit retry budget exhausted")
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
apiBase := os.Getenv("PDF_API_BASE_URL")
operationKey := os.Getenv("PDF_OPERATION_KEY")
requestJSON := os.Getenv("PDF_REQUEST_JSON")
if key == "" || apiBase == "" || operationKey == "" || requestJSON == "" {
panic("INFRAI_API_KEY, PDF_API_BASE_URL, PDF_OPERATION_KEY, and PDF_REQUEST_JSON are required")
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
accepted, err := request(ctx, apiBase, http.MethodPost, "/v1/pdf/extract_images", requestJSON, operationKey)
if err != nil {
panic(err)
}
fmt.Printf("accepted job: %s\n", accepted)
jobID := os.Getenv("PDF_JOB_ID")
if jobID == "" {
return
}
statusPath := strings.Replace("/v1/pdf/job/get/{job_id}", "{job_id}", url.PathEscape(jobID), 1)
status, err := request(ctx, apiBase, http.MethodGet, statusPath, "", "")
if err != nil {
panic(err)
}
fmt.Printf("job status: %s\n", status)
}
Set PDF_API_BASE_URL to the documented API base, generate PDF_REQUEST_JSON from the live discovery schema, and derive PDF_OPERATION_KEY deterministically from the marketplace operation and immutable source version. Persist the accepted response before polling. After completion, validate the manifest against the applicable template fixtures, store derivatives privately, and record their checksums and retention state before exposing a short-lived link.
The rejected default and its valid exception
The rejected default is a synchronous “upload, wait, and return images” flow with no durable operation record. It collapses submission, processing, and delivery into one timeout boundary; under load, the caller cannot distinguish unfinished work from a lost response, and a casual retry can produce a second set of assets that no longer reconciles cleanly. It also makes template provenance easy to omit, precisely where an owned marketplace form should provide the strongest deterministic evidence.
Still, the shortcut has a valid use case. A bounded internal utility may use synchronous local processing when it owns the template and runtime, accepts the timeout, retains no regulated artifact, and does not need cross-request replay. It is not suitable as the default contract for partner documents or marketplace records whose derivatives must remain explainable.
The ADR therefore selects an explicit extraction job, a separate status read, deterministic operation identity, and audit-ready outputs. Provider selection remains conditional on representative fidelity tests, load percentiles, current US/EU contractual review, and the ownership boundary the team is actually prepared to operate.
References
- https://developer.mozilla.org/en-US/docs/Web/API/Blob
- https://developer.adobe.com/document-services/docs/
- https://apidocs.pdf.co/
- https://docs.apryse.com/
- https://docraptor.com/documentation/
- https://gotenberg.dev/docs/getting-started/introduction
- https://doc.courtbouillon.org/weasyprint/stable/
- https://poppler.freedesktop.org/
Top comments (0)