10 best PDF MCP servers for AI agents in 2026

PDF icons, a cloud, and an AI icon linked to a monitor showing a JSON tool call, illustrating a PDF MCP server

Not every PDF MCP server can do what your agent needs. This guide compares 10 options, from free local readers to cloud APIs that convert, edit, and secure files, so you can match the server to the job. See where each one sends your documents, what a full workflow costs, and which security gaps to close before you connect.

10 best PDF MCP servers for AI agents in 2026

When an AI agent struggles with a scanned invoice or loses a table’s structure, it may need a tool built for that task.

A PDF MCP server gives it access to document tools, from text extraction and OCR to merging files or requesting a signature, depending on the server.

The choice comes down to what your agent needs to do and where the document can be processed.

Some servers process files on your machine; others send them to a cloud API. This guide compares 10 PDF MCP servers by their capabilities, setup, cost, and data-handling requirements.

Before connecting a server, consider what your agent can access and where document content goes, and treat incoming PDFs as untrusted input, especially when the agent can modify files.

Top picks by job

Start with the output your agent needs. For reading and extraction, compare the open-source options.

For editing, conversion, or other jobs that produce a new file, compare cloud API servers, Stirling PDF, and local toolkits.

Check current versions and documentation before integrating, since tool counts and pricing tiers shift as vendors ship updates.

JobPickWhy it fits
Converting and editing PDFs through one cloud APIFoxit PDF API MCP Server40 tools in v1.1.2 for conversion both ways, merge, split, compress, OCR, redaction, watermark, and forms
Certificate-based signing inside agent flowsNutrient DWS MCP ServerA PAdES digital signing tool, plus OCR and AI redaction, on Nutrient’s cloud
Zero-install PDF editing in claude.ai or ChatGPTCloudConvert MCP ServerHosted Streamable HTTP endpoint with OAuth; 24 tools
Invoice parsing and table extraction in the cloudPDF.co MCP Server38 tools including fillable forms, table finding, and an AI invoice parser
Open-source, layout-aware conversion for RAGDocling MCPMIT, OCR and table-structure options, v3.3.0 released 6 Oct 2026
Managed parsing and schema extractionLlamaParse MCP26 tools on the unified OAuth endpoint, including indexing and project-key creation; 10K free credits/month
Searching long PDFs and whole folders locallypdf-mcp (jztan)13 tools with hybrid keyword and semantic search that flags hidden text as untrusted
Lightweight local reading across many formatsanymdRust binary, 4 tools, no API key, 6,144 weekly npm downloads for @sylphx/anymd as of mid-October 2026
A self-hosted PDF toolbox behind your firewallStirling PDF MCP ServerBuilt into Stirling PDF with OAuth2 or API key, off by default
Offline editing with no accountPDF Toolkit MCP22 local tools for merge, split, encrypt, forms, and creation

These picks highlight different uses, not exclusive capabilities. Check each entry’s feature table against your full workflow.

How we compared these servers

For each server, we compare 6 fields: capabilities, data routing, setup, pricing, maintenance, and supported clients.

Tool lists and versions come from project READMEs and release notes, package metadata from npm or PyPI, and hosted-service details and costs from vendor documentation and pricing pages.

GitHub stars and directory visitor counts, including PulseMCP’s estimates, indicate popularity. Vendor-provided speed or accuracy figures, including anymd’s comparison with Docling and MarkItDown, are not independent test results.

For a deeper look at how MCP agents use PDF APIs together, see Building Agentic Document Workflows and the Foxit MCP Server setup guide.

The 10 PDF MCP servers in detail

Some servers run locally but send documents to a cloud API when a tool runs.

Before connecting confidential files, check the “Where files go” row in each table, along with the operations, setup requirements, and cost.

MarkItDown MCP deserves a brief mention as a conversion reference.

It exposes a single tool, has no built-in auth, and carries an alpha-suffixed release with conflicting Beta metadata, so its design assumes local-trusted use.

It’s excluded from the main list on scope grounds, but it’s a useful data point for single-format conversion comparisons.

The markitdown-mcp 0.0.1a7 PyPI page, flagged pre-release, showing the single convert_to_markdown(uri) tool and a Classifiers sidebar listing Development Status as 4 - Beta despite the alpha-suffixed version number.

Foxit PDF API MCP Server

Foxit maintains the Foxit PDF API MCP Server under the MIT license, packaging the Foxit PDF Services API operations as MCP tools and giving any MCP-compatible host direct access to Foxit’s cloud PDF engine.

The npm package @foxitsoftware/foxit-pdf-api-mcp-server registers 40 tools in v1.1.2 (13 Jul 2026), though the README for that version lists 36; the 4 additional registered tools in the package code are pdf_extract, pdf_ocr, pdf_redact, and pdf_structural_analysis.

The npm package page for @foxitsoftware/foxit-pdf-api-mcp-server, showing version 1.1.2, MIT license, and the README's "30+ PDF Operations Available" feature category list.
Foxit’s npm package at v1.1.2, under the MIT license. The README preview groups operations by category; the 40-tool count comes from the shipped code.

FeatureDetails
CoversConvert to/from PDF (Office, HTML, URL, text, images), merge, split, extract, compress, flatten, rotate, reorder, watermark, linearize, compare, OCR, redact, structural analysis, password protect/remove, form data import/export, properties. 40 tools in v1.1.2 (README lists 36).
Where files goThe server runs locally and calls Foxit’s cloud. Upload local files once and reuse document IDs for subsequent operations. URL and text creation can send inputs directly without a separate upload step.
Setupstdio or –transport httpStream. Credentials (FOXIT_CLIENT_ID/SECRET) go in environment variables; v1.1.2 sends them as headers, unlike the current REST OAuth docs. HTTP mode defaults to 0.0.0.0 with no inbound auth, so keep it local or add access controls.
Pricing1 credit per successful (2xx) billable PDF processing request. Uploads, polling, downloads, and error responses (4xx/5xx) do not consume credits. Free Developer plan: 500 credits/year. See Credits Explained and the pricing page.
MaintenanceMIT. npm v1.1.2 (13 Jul 2026). The official MCP Registry still links to npm v0.2.3. Install from npm directly to get the current version.
Works withClaude Desktop, VS Code, and Cursor, plus any MCP host via stdio or HTTP stream.

Foxit's Credits Explained reference page, listing what counts as a billable request and confirming failed requests, task-status polling, document downloads, and file uploads are not billable.
Foxit charges 1 credit for each successful billable PDF processing request. Uploads, polling, downloads, and failed requests do not use credits.

Foxit's API pricing page, showing the Developer plan at Free with 500 shared credits per year alongside the paid Startup, Business, and Volume tiers.
The free Foxit Developer plan includes 500 credits per year.

The official MCP Registry's API response for the Foxit PDF API MCP Server, showing two listed versions whose npm package identifier both resolve to version 0.2.3 even though the newer entry is marked isLatest.
The MCP Registry entry shown here points to npm v0.2.3, not the v1.1.2 package on npm. Install from npm directly for the version covered in this guide.

Where Foxit PDF API MCP Server stands out

Document IDs let agents chain uploaded files and processing results across steps without re-uploading, and MCP access ships with your API credentials at no extra cost, with the free Developer plan giving 500 credits per year and no credit card required.

The Foxit PDF Services product page, showing the banner that reads "Connect your LLM agents to Foxit PDF Services through the new MCP server."
Foxit makes its PDF Services operations available to agents through the MCP server.

What to consider

eSign and Document Generation are separate Foxit APIs with their own tools but the same shared credit pool, wrapped as custom tools when your agent needs them, per the Foxit MCP Server post.

Most operations follow an async pattern (call the operation, then get_task_result), except pdf_protect, which polls internally, so factor that into your tool-call count.

The HTTP transport also defaults to binding 0.0.0.0 with no inbound authentication, so add access controls before exposing it externally; get_task_result may also share results via a public link, so treat it as a data-sharing event, not a status check.

Best for

Agents that convert, assemble, and secure documents as part of a product workflow.

Getting started

Create a free Foxit developer account and follow the pinned @foxitsoftware/foxit-pdf-api-mcp-server package setup. Confirm the authentication path against the current API reference before deploying.

Nutrient DWS MCP Server

The Nutrient DWS MCP Server, maintained by Nutrient (formerly PSPDFKit), wraps the Nutrient Document Web Services API as MCP tools, covering a broad range of document operations with PAdES certificate-based digital signing as a standout capability not commonly found elsewhere on this list.

FeatureDetails
CoversMerge, convert between PDF, Office, images, HTML, Markdown, and PDF/A, OCR, watermark, rotate, flatten, compress and linearize, redact, passwords and permissions, data and field extraction, and digital signing (PAdES).
Where files goThe server runs locally and sends processing files to Nutrient’s cloud. Sandbox restrictions apply only when configured.
Setupstdio via npx or the Claude extension. OAuth covers both APIs; headless/API-key use needs separate Processor and Data Extraction credentials.
PricingSeparate billing for Processor (50 free credits/month; paid Starter beyond that) and Data Extraction (per-page parsing, field extraction extra). Free plan applies a watermark, removed on paid PAYG. See pricing.
MaintenanceMIT. v0.1.3 (28 Aug 2026).
Works withClaude Desktop, Cursor, Windsurf, VS Code, and Codex.

The Nutrient DWS MCP Server GitHub repository, showing the DWS MCP v0.1.3 release tag and the MIT license.
Nutrient’s MIT-licensed MCP server, shown here at v0.1.3.

Nutrient's Processor API pricing page, showing the Free tier at $0/month with 50 credits/month and a note that output carries an evaluation watermark unless the plan is paid or pay-as-you-go is enabled.
Nutrient includes 50 free Processor credits per month, but removing the output watermark requires paid or pay-as-you-go access.

Where Nutrient DWS MCP Server stands out

PAdES certificate-based digital signing, required for legally binding signatures in many European and enterprise contexts, is the key differentiator.

Paired with OCR and AI-assisted redaction, an agent can scan a document, redact sensitive fields, and apply a certificate-based signature in one workflow.

The server also supports PDF/A conversion for archival or regulatory use.

What to consider

The free-plan watermark is removed only on a paid PAYG tier, so confirm that tier is active before deploying anywhere watermarked output is a problem.

The server is still pre-1.0 at v0.1.3, so the tool surface and auth model may change, and headless use needs separate Processor and Data Extraction credentials.

Best for

Agents that must apply a certificate-based digital signature before a document leaves the workflow.

Getting started

See the Nutrient DWS MCP Server on GitHub and the Nutrient PDF MCP server page for setup and current credential requirements.

CloudConvert MCP Server

The CloudConvert MCP Server is a fully hosted server maintained by CloudConvert, the file conversion service, running as a Streamable HTTP endpoint authenticated with OAuth 2.0, nothing to install.

FeatureDetails
CoversConvert, compress, watermark, capture URLs to PDF, merge, OCR, PDF/A, encrypt and decrypt, split and extract pages, rotate. 24 tools.
Where files goCloudConvert’s cloud. Input files can be supplied as downloadable URLs. Output links expire after 24 hours. ChatGPT automatically bridges attachments into temporary input URLs.
SetupHosted Streamable HTTP endpoint with OAuth 2.0. Nothing to install.
Pricing10 free conversion credits per day, with additional credits via packages or subscriptions. See CloudConvert pricing.
MaintenanceHosted service with no package version to track.
Works withChatGPT and Claude connectors, per CloudConvert’s docs.

CloudConvert's pricing page, showing the Free tier at 10 conversion credits per day alongside the Package and Subscription tiers.
CloudConvert includes 10 free conversion credits per day.

Where CloudConvert MCP Server stands out

The hosted endpoint works directly in ChatGPT and claude.ai with no local setup, and ChatGPT attachments become temporary downloadable URLs automatically, so an agent can process uploaded PDFs without the user managing file transfers.

What to consider

The 24-hour link expiry on outputs means downstream steps need to download or store results promptly, and clients other than ChatGPT or claude.ai must supply files as downloadable URLs, so confirm your client can do that and supports the required transport before committing.

Best for

Hosted chat workflows where files arrive as attachments or downloadable URLs (ChatGPT or Claude connectors).

Getting started

See the CloudConvert MCP Server docs for connector setup and OAuth configuration.

PDF.co MCP Server

The PDF.co MCP Server, maintained by the PDF.co team, wraps the PDF.co cloud API as MCP tools.

Its 38 tools cover typical extraction and conversion jobs, including an AI invoice parser and table-finding tools for document data extraction workflows.

FeatureDetails
CoversPDF to JSON, CSV, text, Excel, XML, HTML, and images; Office, HTML, and images to PDF; merge, split; read, fill, and create forms; OCR; passwords; table finding; AI invoice parsing. No compression tool.
Where files goPDF.co’s cloud.
Setupstdio via uvx. API key required.
PricingCredit-based, starting from a monthly plan billed annually. HTML to PDF costs 9 credits per page as a reference point. See PDF.co pricing for current rates.
MaintenanceMIT. v0.0.9 (28 Jul 2026).
Works withCursor and Claude Desktop.

PDF.co's pricing page, showing the Basic, Personal, and Business monthly plans with their credit allotments.
PDF.co sells monthly plans with a set credit allowance.

The pdfdotco/pdfco-mcp GitHub repository, showing the MIT license and v0.0.9 tagged as the latest release.
PDF.co’s MIT-licensed MCP server, shown here at v0.0.9.

Where PDF.co MCP Server stands out

The AI invoice parser and table-finding tools cut the work an agent would otherwise do in its own context window when pulling line items, totals, and vendor fields from financial documents.

The fill_forms tool (named fill_pdf_forms in the README but registered as fill_forms in the package) and the form-creation tools round out coverage for fillable documents.

What to consider

PDF.co has no compression tool. If your workflow also needs smaller files, you’ll need a separate compression step.

Best for

Agents that pull table data and invoice fields out of PDFs at scale.

Getting started

See the PDF.co MCP Server on GitHub for installation and API key setup.

Docling MCP

Docling MCP, maintained by the LF AI and Data project that started at IBM Research, wraps the Docling document processing library as MCP tools, converting documents to structured formats for RAG ingestion pipelines that need clean structure from complex layouts.

FeatureDetails
CoversConvert PDFs and other documents to Docling JSON or Markdown, with OCR and table-structure options, build new documents, and optional RAG tool groups. No PDF binary editing.
Where files goLocal conversion or your own Docling Serve instance. Remote is the default; local processing requires the local extras install. Optional RAG and model service routing may send data to configured external endpoints.
Setupstdio, SSE, or streamable HTTP. The remote Docling Serve API key is separate from MCP endpoint authentication; configure both independently.
PricingFree and open source; you pay for compute.
MaintenanceMIT. v3.3.0 (6 Oct 2026). Version 3.x requires MCP SDK v2; verify your integration’s SDK version before upgrading.
Works withClaude Desktop, LM Studio, and Llama Stack.

The Docling MCP GitHub repository, showing the v3.3.0 release tagged as the latest version.
Docling MCP’s v3.3.0 release on GitHub.

Where Docling MCP stands out

Layout-aware conversion preserves table structure, column ordering, and document hierarchy, cutting the cleanup work an agent would otherwise do when indexing research papers, technical specs, or multi-column reports.

Optional RAG tool groups let agents build and query vector indexes from the same server, simplifying pipelines for teams that want ingestion and retrieval in one place.

What to consider

Docling MCP converts documents and does not write PDF binaries, so it cannot merge, compress, or encrypt a file; pair it with an editing server for pipelines that need both.

The v3.x requirement for MCP SDK v2 is a real constraint to check before upgrading from v2.x.

Best for

RAG and ingestion pipelines that need clean, layout-aware structure from complex document layouts.

Getting started

See Docling MCP on GitHub for installation options and Docling Serve configuration.

LlamaParse MCP

LlamaParse MCP is the hosted MCP server for LlamaIndex’s LlamaParse document parsing service, running entirely on LlamaIndex’s infrastructure with nothing to install, covering structured extraction, schema-based parsing, and index management through 26 tools on a unified OAuth endpoint.

FeatureDetails
CoversParse to Markdown or text with OCR, classify, split into sections, schema-based extraction and indexing. No PDF binary editing. LiteParse is available for jobs that do not require Parse credits.
Where files goLlamaIndex’s cloud, with North America and EU endpoints available.
SetupHosted Streamable HTTP. Interactive clients use OAuth; API-key callers pass files by URL (not direct upload).
Pricing10K free credits/month; Starter tier includes 40K credits/month. Additional usage billed per 1,000 credits. See LlamaIndex pricing for current rates.
MaintenanceHosted service. The older run-llama/mcp-server-llamacloud repo is archived; use the current hosted endpoint.
Works withCursor, VS Code, Claude Code, Claude connectors, and Codex.

LlamaIndex's LlamaParse pricing page, showing the Free plan at 10K credits/month and the Starter plan at 40K credits/month.
LlamaParse includes 10K credits per month on Free and 40K on Starter.

Where LlamaParse MCP stands out

The unified endpoint means no server to run, model to download, or Docling Serve to maintain, cutting operational overhead for teams that want managed parsing and schema extraction with North America/EU data routing.

Schema-based extraction suits varied document types, since you define the schema and let LlamaParse handle extraction, which tends to produce more consistent results than prompting an LLM to locate fields.

What to consider

LlamaParse MCP reads and parses documents without modifying PDFs; account and index writes consume credits separately from parsing.

LiteParse skips Parse credits for jobs that don’t need high-fidelity parsing, but API-key callers can’t upload files directly and must supply a URL to an accessible file, adding a step for locally stored documents.

Best for

Teams extracting structured fields from varied documents without running their own parser.

Getting started

See the LlamaParse MCP docs for endpoint details and OAuth setup.

pdf-mcp (jztan)

The pdf-mcp server by jztan is an open-source, locally-running MCP server focused on reading and searching PDFs.

Its 13 tools include hybrid keyword and semantic search, page-level reading, table extraction, and vector-chart data reading, and it flags hidden or injected text as untrusted rather than passing it silently along.

FeatureDetails
CoversHybrid and folder search, page reads, tables, vector-chart data, Tesseract OCR, and page rendering. Note that local embedding, model, and OCR dependencies must be installed separately. No PDF editing.
Where files goLocal by default. HTTP deployments use the configured server. Optional remote embeddings send text to the configured endpoint.
Setupstdio or bearer-token-protected HTTP. Claude Desktop extension available. VS Code, Cursor, Codex CLI, and Kiro setups are documented.
PricingFree.
MaintenanceMIT. v3.5.0 (3 Oct 2026).
Works withClaude Desktop and Claude Code; documented setups for VS Code, Cursor, Codex CLI, and Kiro.

The pdf-mcp GitHub releases page, showing v3.5.0 tagged as the latest release.
The v3.5.0 release of jztan’s pdf-mcp server.

A table from the pdf-mcp README listing hybrid BM25-plus-semantic search, OCR via Tesseract for scanned PDFs, and a row stating hidden or injected text is flagged as untrusted rather than silently ingested.
pdf-mcp combines keyword and semantic search. Its README says hidden or injected text is flagged as untrusted rather than silently passed to the agent.

Where pdf-mcp (jztan) stands out

The server reads only the pages an agent requests rather than dumping an entire PDF into context, cutting token use for long contracts, filings, and manuals at scale.

It also explicitly flags hidden or injected text as untrusted, a warning most reading tools never surface.

What to consider

This server reads and searches PDFs only; it cannot merge, split, rotate, compress, or otherwise modify a document.

PulseMCP’s directory lists editing capabilities the README doesn’t document, so rely on the README, and budget setup time for external dependencies (Tesseract for OCR, a local embedding model for semantic search).

Best for

Research and review agents working through long contracts, filings, or technical manuals where selective reading reduces cost.

Getting started

See pdf-mcp on GitHub for dependency setup and client configuration.

anymd

anymd (formerly pdf-reader-mcp) is a Rust binary distributed via npm. It converts PDFs and other document formats to Markdown, with 4 tools, no API key, and no network upload for local files.

FeatureDetails
CoversFour tools for mixed-format reading and Markdown output. OCR requires opt-in local models or an installed Tesseract binary. No PDF editing.
Where files goYour machine. No files are uploaded.
Setupstdio Rust binary installed via npm (npx -y @sylphx/anymd). No API key required.
PricingFree MIT core. An optional $29 one-time Pro add-on enables video timelines and citation checks.
MaintenanceMIT core. Release 8.5.3 (11 Oct 2026).
Works withClaude Code, Codex, Cursor, VS Code, Claude Desktop, Windsurf, and Gemini CLI.

The SylphxAI/anymd GitHub repository, showing the project description "A fast Rust MCP server and CLI that runs on your machine. No API key."
Use SylphxAI’s anymd project and the @sylphx/anymd npm package. The unscoped anymd package belongs to an unrelated macOS-only tool, 1qh/anymd.

The npm registry page for the scoped package @sylphx/anymd, showing version 8.5.3, 6,144 weekly downloads, and 2 dependents.
The @sylphx/anymd package at v8.5.3, showing 6,144 weekly downloads in mid-October 2026. Those downloads belong to this scoped package, not the unrelated anymd package.

Where anymd stands out

Its zero-dependency, zero-upload design supports mixed-format reading in a local agent, since pointing it at a folder of PDFs, Word docs, and Markdown files lets it read all of them through one server with no account setup and a small runtime footprint.

The @sylphx/anymd package shows 6,144 weekly downloads as of mid-October 2026, suggesting active use, though download counts don’t map directly to unique users and the figure moves week to week.

What to consider

anymd is read-only, converting documents to Markdown without producing modified PDFs, and OCR needs either a local Tesseract install or opting into local model support.

The author’s benchmark against Docling MCP and MarkItDown MCP is self-run and not independently validated, so treat it as a directional vendor claim.

Best for

Coding agents that need to read mixed-format document folders quickly, without network calls or API keys.

Getting started

See anymd on GitHub for installation and format support details.

Stirling PDF MCP Server

The Stirling PDF MCP Server is the MCP interface built into Stirling PDF, a self-hosted PDF toolbox, exposing PDF editing and conversion operations over a Streamable HTTP endpoint behind OAuth2 or per-user API keys, off by default.

FeatureDetails
CoversPage operations (merge, split, rotate, reorder), conversion to and from PDF, compression, flattening and repair, encryption and permissions, upload and download, and AI features.
Where files goYour server for standard PDF operations. AI features use configured model and embedding providers, which may be hosted externally depending on your configuration.
SetupStreamable HTTP at POST /mcp, disabled by default. OAuth2 or per-user API key. Read and write scopes apply only in OAuth mode. Enable the server in Stirling PDF’s config and configure auth before exposing it.
PricingStirling PDF has Free, Team, and Enterprise tiers; see the Stirling pricing page. MCP usage and credit consumption are not described on the MCP documentation page; do not treat platform pricing as per-tool call charges without confirming separately.
MaintenanceOpen-core; MCP implementation is proprietary. Stirling PDF v3.1.0. Verify the applicable User License for your deployment context before production use.
Works withMCP Inspector; Claude Desktop via mcp-remote.

Stirling PDF's pricing page, showing the Free, Team, and Enterprise tiers with the Free tier's 1,000 monthly credits.
Stirling PDF offers Free, Team, and Enterprise plans. These are platform tiers, not a confirmed per-tool MCP rate card.

Stirling PDF's MCP server settings documentation, showing MCP is off by default, clients connect to /mcp on the same host and port as the app, and authentication mode is either API key or OAuth 2.1.
Stirling PDF’s MCP endpoint is disabled by default. Enable /mcp and configure API key or OAuth 2.1 authentication before connecting a client.

The Stirling-Tools/Stirling-PDF GitHub releases page, showing v3.1.0 tagged as the latest release.
Stirling PDF v3.1.0 on GitHub.

Where Stirling PDF MCP Server stands out

The full editing toolbox runs behind your own firewall and stays accessible to agents with scoped permissions, letting teams in regulated industries or with data-residency requirements process PDFs on their own infrastructure.

The read/write scope separation in OAuth mode lets you start an agent with read-only access and expand permissions once you trust its behavior.

What to consider

You’re responsible for running, securing, and maintaining Stirling PDF. Default inline response payloads cap at 10 MB (less after base64 encoding), with large outputs using a fileId reference instead.

For AI features, confirm your model and embedding providers match your data-handling requirements, since the default example config uses hosted providers, and check the User License before production use.

Best for

Teams seeking self-hosted PDF editing exposed to agents, with control over data routing and scoped permissions.

Getting started

See the Stirling PDF MCP Server docs and the Stirling PDF GitHub repo for configuration and licensing details.

PDF Toolkit MCP

PDF Toolkit MCP, maintained by AryanBV, is an open-source, fully offline MCP server with 22 tools for reading, editing, and creating PDFs, requiring no account, no API key, and making no network calls.

FeatureDetails
CoversExtract text and metadata, search, compare, and convert to Markdown; merge, split, rotate, reorder, flatten, AES-256 encryption; page numbers and QR codes; create PDFs from text, Markdown, or templates; fill forms; watermark; render pages to images. No compression or built-in OCR.
Where files goYour machine. Fully offline.
Setupstdio via npx. No auth required.
PricingFree.
MaintenanceMIT. v0.3.1 (30 May 2026).
Works withClaude Desktop, Claude Code, Cursor, VS Code, and Windsurf.

The PDF Toolkit MCP GitHub repository, showing the project description "22 tools" and the v0.3.1 release tagged as the latest version.
PDF Toolkit MCP v0.3.1 includes 22 tools for local PDF work.

Where PDF Toolkit MCP stands out

The server combines reading, editing, and creating PDFs with no external dependencies. AES-256 encryption, page creation from Markdown, and form-filling without uploading to any service support workflows where the data cannot leave your machine.

The rendering tool sends page images to the client’s vision model for analysis, extending OCR-like capability without bundling an OCR engine.

What to consider

There is no compression tool and no built-in OCR, so extracting text from rendered page images needs a vision-capable client, with accuracy depending on that model.

Built-in fonts cover Latin characters only, so non-Latin create or fill operations need a supplied fontPath pointing to an appropriate font file.

Best for

Local agents that edit documents containing data you cannot upload to a cloud service.

Getting started

See PDF Toolkit MCP on GitHub for the full tool list and font configuration notes.

What PDF MCP servers can and cannot do for an agent

A tool list is only part of the decision. Compare what each server can produce, where it processes files, and how a complete workflow is billed.

Capability coverage across all 10 servers

The table below marks a cell only where the README or vendor docs explicitly list the capability as of 7 October 2026.

ServerRead and extractOCRConvert to or from PDFMerge or splitCompressProtect or redactRuns on
Foxit PDF API MCP ServerYesYesYes (both ways)YesYesPassword and redactionFoxit cloud
Nutrient DWS MCP ServerYesYesYes (both ways)MergeYesPassword and redactionNutrient cloud
CloudConvert MCP ServerVia conversionYesYes (both ways)YesYesEncryptCloudConvert cloud
PDF.co MCP ServerYesYesYes (both ways)YesNoPasswordPDF.co cloud
Docling MCPYesYesTo Markdown or JSONNoNoNoLocal or Docling Serve
LlamaParse MCPYesYesMarkdown/text parsing; JSON extractionLogical section splittingNoNoLlamaIndex cloud
pdf-mcp (jztan)YesYes (Tesseract)NoNoNoNoLocal
anymdYesOpt-in (Tesseract or local model)To MarkdownNoNoNoLocal
Stirling PDF MCP ServerYes (OCR via enabled ocr-pdf using OCRmyPDF/Tesseract); AI Q&A needs AI engineYesYes (both ways)YesYesEncrypt and permissionsYour server
PDF Toolkit MCPYesVia rendered page images to client visionTo Markdown; from text, Markdown, or templatesYesNoAES-256 encryptLocal

Reading is well covered by open source, with five servers offering local-only processing at no cost.

Editing (merge, convert, compress, protect) needs a cloud API server, Stirling PDF, or PDF Toolkit MCP, and no single server covers every capability, so the right choice depends on which operations your workflow needs.

Where your document goes when the agent calls a tool

A locally running server doesn’t necessarily keep document content on your machine. pdf-mcp, anymd, and PDF Toolkit MCP process PDFs locally, but content returned to the agent may still reach its model provider, and pdf-mcp can also use remote embeddings.

Docling MCP defaults to a remote Docling Serve instance unless you install the
local extras, and Stirling PDF processes PDFs on your own server but routes AI features through hosted model and embedding providers.

Foxit, Nutrient, CloudConvert, PDF.co, and LlamaParse all process files in the cloud, so check each vendor’s retention policy and the entries above for file-handoff and regional options.

What a tool call costs

Pricing captured from vendor pricing pages, 7 October 2026. Verify current rates before budgeting a workflow.

ServerPricing modelFree allowance
Foxit PDF API MCP Server1 credit per successful (2xx) billable PDF processing request; shared credit pool. See pricing.500 credits/year
Nutrient DWS MCP ServerCredits per operation (e.g., HTML to PDF: 0.5 credits; OCR: 2 credits); separate Processor and Data Extraction billing. See pricing.50 Processor credits/month; default watermark removed with paid PAYG
CloudConvert MCP ServerConversion credits per operation; packages or subscriptions. See pricing.10 credits/day
PDF.co MCP ServerCredits per page and per call. See pricing.Trial credits on signup
LlamaParse MCPPer-credit usage above free tier; Starter tier includes 40K credits/month. See pricing.10K credits/month
Stirling PDF MCP ServerPlatform-level tiers (Free, Team, Enterprise); 1 cent per credit on paid tiers. See pricing.1,000 credits/month on free tier
Docling MCPFree open-source core; you pay for compute and any remote services you configure.No per-call meter
pdf-mcp (jztan)Free.No per-call meter
anymdFree MIT core; optional $29 one-time Pro.No per-call meter
PDF Toolkit MCPFree.No per-call meter

Estimate costs for the whole workflow, not a single tool call. An editing workflow can take several calls (upload, operation, a get_task_result poll, download, sometimes a retry), but distinguish tool calls from billable requests, since with Foxit, uploads, polling, downloads, and failed requests don’t consume credits.

A read-only server that retrieves only the relevant pages can also cut model input cost versus loading an entire PDF into context, given Anthropic estimates 1,500 to 3,000 text tokens per page for Claude’s native PDF processing.

Security considerations for agents running PDF tools

Treat external PDFs as untrusted input, since their text can contain instructions designed to manipulate an agent (prompt injection, OWASP LLM01). pdf-mcp flags hidden or injected text, but application-level safeguards are still needed.

MCP servers pose a separate risk too, since Invariant Labs demonstrated tool poisoning in April 2025, where malicious tool descriptions manipulate client behavior, and the MCP specification on tools calls for human oversight with the ability to deny tool invocations.

Require host-side approval for write operations until you trust the agent’s behavior, restrict accessible directories, document IDs, and tool permissions (Stirling PDF separates read and write scopes in OAuth mode), and test with unusual text, embedded objects, and long unstructured content before production.

The unauthenticated HTTP default and the public-link behavior of get_task_result noted in the Foxit entry above are exactly this kind of gap, the sort worth checking for on any server before connecting it to a pipeline that handles confidential documents.

Choosing a PDF MCP server

Match the server to the job, not the other way around. Reading and extraction are covered well by free, local, open-source options, so an agent that only answers questions about a PDF rarely needs a cloud account.

Editing, converting, and assembling documents narrows the field to a cloud API server, Stirling PDF, or a local toolkit like PDF Toolkit MCP; the choice then comes down to where content is allowed to go and whether certificate-based signing or a specific format matters.

Weigh data routing against convenience, since a hosted cloud endpoint needs nothing to install but sends file content off your infrastructure on every call, while a local or self-hosted server keeps data in place but puts you on the hook for running and securing it.

Check pricing against expected call volume too, since several calls per document (upload, operation, poll, download) can burn through a free allowance faster than a single-call estimate suggests.

For an agent that converts, assembles, and secures documents as part of a product workflow, cloud API coverage in one install is the deciding factor, and that’s where the Foxit PDF API MCP Server fits, with eSign and Document Generation available as separate integrations wrapped as custom tools when needed.

Plan for request limits as well as credits, since the sandbox allows 15 requests/minute and production allows 100/minute, including status checks and file requests.

Create a free Foxit developer account and follow the pinned @foxitsoftware/foxit-pdf-api-mcp-server package setup, confirming its authentication path against the current API reference.

PDF MCP server FAQs

Yes. Claude’s API accepts PDFs directly, up to 32 MB per request and 600 pages (100 when the context window is under 1M tokens), so a single-document question-answer task needs no server. Consider a server for larger files, folder search, encrypted documents the model can’t open, or workflows that produce a modified PDF.

Compatibility depends on the server, supported transport, and client plan or workspace restrictions; hosted Streamable HTTP with OAuth alone doesn’t guarantee support. CloudConvert’s docs cover ChatGPT and Claude connectors, and LlamaParse documents Claude connectors, but check the client’s requirements and test your setup. Local stdio servers need a locally running process and aren’t directly compatible with hosted chat interfaces.

Yes. pdf-mcp, anymd, Docling MCP, and PDF Toolkit MCP are free and open source with no per-call charges. Several cloud services, including Foxit’s, also offer free allowances; see the pricing table for limits and conditions.

Direct REST calls are often simpler for fixed pipelines, since they’re predictable, easy to test, and straightforward to monitor. Use MCP when the agent chooses operations, documents, or the next step at runtime based on intermediate results.

10 best eSignature APIs for developers in 2026

Cover for best eSignature API comparison showing Foxit API pricing tiers beside DocuSign developer plans

The cheapest eSignature API plan may not include the signing flow you need. This guide compares 10 eSignature APIs on embedded signing, billing triggers, sandbox limits, and compliance. Use it to build a shortlist that fits your workflow and budget.

Getting a contract signed shouldn’t mean emailing PDFs back and forth or asking someone to print, sign, and scan a document.

An eSignature API lets you bring that signing flow into your product, so users can sign without leaving your app.

But embedded signing isn’t included in every plan, and the cheapest starting price may not cover the workflow you need.

For example, DocuSign’s $50/month Starter plan includes hosted envelopes and embedded sending, but embedded signing requires a sales-led Enhanced plan with at least five users.

Billing can also change the cost: some providers charge when you send an envelope, even if no one signs it. And a compliance badge may apply only to a specific plan.

Below, we compare 10 eSignature APIs to help you choose one that fits your workflow and budget. The details come from each vendor’s pricing, API documentation, and security pages.

Prices are US list prices, and compliance badges are vendor claims.

Top picks by use case

The best eSignature APIs for developers in 2026 are Foxit eSign API, Dropbox Sign, BoldSign, SignNow, SignWell, Anvil Etch, DocuSign, Adobe Acrobat Sign, DocuSeal, and Documenso.

Start with the plan that supports your signing flow, then check its cost at your expected volume.

Use casePickWhy it fits
Signing inside a larger PDF workflowFoxit eSign APIeSign, PDF Services, and Document Generation on one account. See Foxit API pricing for current allocations.
Mature SDKs with self-serve embeddingDropbox Sign APISix official SDKs, with embedded signing from the Standard plan ($250/month billed yearly).
Low per-envelope rate on a monthly planBoldSign API$30/month with 40 envelopes, then $0.75 per envelope sent.
Per-signature API pricing with no monthly tier to pickSignNow APIFull API access is gated behind the custom-quoted Site License plan, billed at $1.50 per signature invite.
Pure usage pricing with no monthly minimumSignWell APIFrom $0.85 per API document, falling to $0.20 with volume.
Paying only for completed signaturesAnvil Etch$1.50 per packet, billed only when every party signs, with embedding included on the free plan.
Government and FedRAMP requirementsDocuSign eSignature REST APIDocuSign Federal offers FedRAMP Moderate and provisional DoD IL4 authorization, separate from commercial Enhanced.
Teams already licensing AdobeAdobe Acrobat Sign APIFree Developer Edition available. Production access and embedding go through Adobe sales.
Low per-document fee with an open-source coreDocuSeal$0.20 per completed document plus a $20 Pro seat. AGPL-3.0 self-hosting available.
Open source with unlimited documentsDocumensoTeams plan at $40/month billed yearly includes embedded signing under a fair-use policy.

Most of these APIs support several use cases, so compare their features and plan limits against your full workflow.

How we compared these eSignature APIs

Every entry below is scored against the same 8 criteria, in the same order:

  • API capabilities

  • Embedded signing

  • Webhook support

  • Sandbox access

  • Pricing

  • SDK availability

  • Rate limits

  • Security and compliance

The 10 eSignature APIs in detail

Start with the signing experience you need, then compare plan requirements and billing rules.

Foxit eSign API

Foxit’s eSign REST API embeds signing workflows directly into web applications, portals, and backend systems, so a product that generates, converts, and sends a document for signature runs under one Foxit developer relationship instead of two.

The API uses standard OAuth 2.0 authentication and returns an embeddedSessionURL that renders inside an <iframe> in your own application.

Foxit eSign API Overview docs page listing envelope creation, field setup, webhook events, and activity history

Foxit’s eSign API covers envelope creation, embedded signing, webhook events, and activity history.

Key features at a glance

FeatureDetails
CoversEnvelopes, templates, embedded signing and sending, envelope activity history. PDF Services and Document Generation available on the same account. Cloud only, storage region chosen at activation.
Embedded signingAvailable on every plan, including the free Developer plan (trial).
WebhooksWebhook channels for envelope status events. Status checks and webhooks consume no credits.
SandboxeSign trial on the free Developer plan. Envelopes are watermarked in TEST mode.
PricingeSign is billed per envelope created. eSign, PDF Services, and Document Generation draw from one shared credit pool. See Foxit API pricing and Credits Explained for current plan allocations and billing details. Billed annually and credits do not roll over.
SDKsREST API with an official Postman collection and code samples. Confirm installable SDK availability with Foxit for the current unified API. The separate PDF SDK is not an eSign API SDK.
Rate limitsPer application, per minute, rolling 60-second window: 15 RPM in Sandbox mode, 100 RPM in Production. Free Developer plan allows up to 10 applications, paid plans up to 50.
Compliance claimsFoxit’s API security page lists ESIGN/UETA, AES/qualified electronic signature (QES), 21 CFR Part 11, and SOC 2 Type II. QES involves identity-validation services and a confirmed plan.

Where it fits

Keeping eSign, PDF Services, and Document Generation on one account avoids separate keys and billing cycles.

The billing trigger is envelope creation, so cost appears before signing; status checks, webhooks, downloads, and session generation consume no credits.

Annual credits don’t roll over, so size usage before picking a plan.

Foxit uses one credit pool for eSign, PDF Services, and Document Generation.

Foxit Credits Explained page noting eSign bills per envelope created and that status polling and downloads are not billed

Creating an eSign envelope uses credits; checking its status, downloading documents, and generating signing sessions do not.

Best for

Products that generate or convert PDFs then send them for signature.

Create a free developer account to activate the eSign trial. Then follow the eSign API reference to send your first envelope.

Foxit eSign API developer portal hero with a Start for Free button beside a developer working at her desk

The eSign API developer portal, source of the Postman collection.

Foxit sign-up page offering a free Developer plan with no credit card, live API credentials, and 500 free credits a year

You can start with 500 free credits a year, with no credit card required.

Each application gets 15 requests per minute in the sandbox and 100 in production. The free plan allows up to 10 applications; paid plans allow up to 50.

Dropbox Sign API

Dropbox Sign (formerly HelloSign) ships six official SDKs and a published “endpoints by subscription plan” map showing which calls each plan unlocks, across signature requests, templates, bulk send, and embedded requesting and signing.

Dropbox Sign’s developer docs homepage, with the endpoints-by-plan and rate-limit references.

Key features at a glance

FeatureDetails
CoversSignature requests, templates, bulk send, embedded requesting and signing. Hosted only.
Embedded signingStandard ($250/month billed yearly) and Premium. Not available on Essentials.
WebhooksEvent callbacks.
SandboxFree test mode. Documents are watermarked and not legally binding. 10 requests/minute.
PricingEssentials $75/month billed yearly (50 signature requests/month), Standard $250/month (100 requests/month), Premium by quote. Billed per signature request.
SDKsPython, Ruby, PHP, Java, Node.js, C#.
Rate limits100 requests/minute general; 25 requests/minute on heavier endpoints. Confirm current limits against the Dropbox Sign rate limits docs.
Compliance claimsSOC 2 Type II, ISO 27001, eIDAS, GDPR. QES available as a Premium add-on. See Dropbox Sign’s Trust Center for the full certification list.

Where it fits

The endpoints-by-plan map shows restrictions before you build, since embedded signing requires Standard or above (Essentials returns HTTP 402), and full white-label branding needs the sales-led Premium plan.

Best for

Teams that want official SDKs and a self-serve Standard plan for in-app embedded signing. Start with the API docs and a free sandbox account.

Dropbox Sign allows 100 requests per minute for general calls and 25 for heavier endpoints.

BoldSign API

BoldSign’s API runs on a monthly plan with a low per-envelope rate and a free sandbox with full API access, covering signing, requesting, templates, and embedded flows on the Enterprise API plan, with US, EU, Canada, and Australia base URLs.

BoldSign’s Developer Hub, the starting point for the sandbox and docs.

Key features at a glance

FeatureDetails
CoversSigning, requesting, templates, embedded signing and sending on the Enterprise API plan. Web plans are sandbox-only for API envelopes. Hosted, with US, EU, Canada, and Australia endpoints.
Embedded signingEnterprise API plan only.
WebhooksEnterprise API plan only.
SandboxFree, with full API access. Test documents are watermarked and deleted after 14 days. 50 requests/hour.
Pricing$30/month including 40 envelopes, then $0.75 per envelope sent. Two users included. See BoldSign API pricing for current rates.
SDKs.NET, Node.js, Java, Python, PHP.
Rate limits2,000 requests/hour in production.
Compliance claimsSOC 2 Type 2, HIPAA, GDPR, PCI DSS via Stripe’s processing, eIDAS. QES offered. See BoldSign’s data storage and security page for details.

Where it fits

BoldSign’s cost is easy to estimate, since $30 includes 40 envelopes, then $0.75 each, so 100 envelopes a month costs $30 + (60 × $0.75) = $75. Embedded signing and webhooks are both self-serve. Envelopes bill on send, so a declined one still counts.

Best for

SaaS products embedding signing at modest volume that want a low per-envelope rate with no sales conversation. Start with the developer docs and the pricing page.

BoldSign Enterprise API pricing card showing $0.75 per envelope, starting at $30 a month with 40 envelopes included

BoldSign’s $30 monthly plan includes 40 envelopes, with additional envelopes billed at $0.75 each.

BoldSign allows 50 requests per hour in the sandbox and 2,000 per hour in production.

SignNow API

SignNow’s per-user plans (Business, Business Premium, Enterprise) cover sending, branding, and workflow features, but full API access and embedded signing sit behind a separate, custom-quoted Site License plan rather than any of those self-serve tiers.

You can embed both signing and sending with SignNow’s API.

Key features at a glance

FeatureDetails
CoversEmbedded signing and sending, document generation from .docx templates, and webhooks, documented across the full REST API. Hosted.
Embedded signingFull API access, including embedded signing, requires the Site License plan. Business, Business Premium, and Enterprise plans do not include it.
WebhooksDocumented event callbacks for signing status.
SandboxFree developer account with a Development mode application, up to 500 signature invites without an active trial or subscription.
PricingBusiness $8/month, Business Premium $15/month, Enterprise $30/month, billed annually per user with 100 invites/year included. Full API access needs the Site License plan, billed per signature invite from $1.50, custom-quoted through sales. See SignNow pricing for current rates.
SDKsNode.js, C#, Java, Python, PHP.
Rate limitsPer application: 500 requests/hour in Development, 1,000 requests/hour by default in Live.
Compliance claimsSOC 2 Type II, ISO 27001, PCI DSS, CCPA, HIPAA via BAA, 21 CFR Part 11. AES and QES on Enterprise. See SignNow’s eSignature compliance page for details.

Where it fits

SignNow’s branding and workflow features scale through ordinary per-user plans, but production API access, including embedded signing, is a sales-led Site License conversation.

Best for

Teams already planning to negotiate a volume-based API contract who want per-signature pricing rather than a flat monthly tier.

Review the API docs and pricing page to confirm current terms.

The per-user plans don’t include full API access. For that, you’ll need a separately quoted Site License.

SignWell API

SignWell takes a pure usage-pricing approach, charging per API document with no seats and no monthly minimum.

The rate drops with volume, so cost tracks actual usage rather than a fixed tier.

SignWell’s getting-started guide walks you through the API token setup and a first request.

Key features at a glance

FeatureDetails
CoversSigning, requesting, and template editing through the API, with white-label embedding. Hosted.
Embedded signingAvailable on the API usage plan, including embedded signing, requesting, and template editing.
WebhooksYes, with hash verification.
SandboxUnlimited test usage on free developer account. Production documents: 3/month free, 25/month once a card is added. Confirm eligibility before relying on the free allowance.
PricingFrom $0.85 per API document, falling to $0.20 and lower with volume. No seats or monthly minimum. See SignWell API pricing for current per-document rates and the volume calculator.
SDKsRuby, Node.js, Python, PHP, plus CLI and MCP server support.
Rate limitsPer token: 100 requests/minute general, 30 requests/minute for creation endpoints, 20 requests/minute for test endpoints.
Compliance claimsESIGN, UETA, eIDAS, SOC 2 Type II, HIPAA with BAA. See SignWell’s security page for details.

Where it fits

Per-document pricing means a product sending 10 documents one month and 200 the next pays only for actual usage.

The free allowance is inconsistent (25/month advertised, 3/month card-free), so confirm which applies.

Best for

Low or spiky document volumes with no minimum commitment. Review the developer docs and the pricing page.

SignWell advertises 25 free API documents a month once you add a card.

Anvil Etch E-Sign API

Anvil Etch charges $1.50 per packet only when every party has signed, so a stalled packet costs nothing.

With embedded signing on the free plan too, Anvil is the only API here where you pay only for flows that finish.

Anvil Etch e-sign API docs listing signature, data collection, and acknowledgment requests above a sample signing page

Anvil’s Etch E-Sign API documentation page.

Key features at a glance

FeatureDetails
CoversE-signatures, PDF filling, document generation, and Workflows on the same platform. Hosted.
Embedded signingEvery plan, including Free. White-labeled signing pages from Product Pack ($425/month, or $340/month billed annually).
WebhooksYes, with optional payload encryption.
SandboxUnlimited free test mode with watermarked documents.
Pricing$1.50 per packet, billed only when every party signs, with 25 starter packets once a card is added. See Anvil pricing for current rates and subscription options.
SDKsNode.js, Python, .NET. Go is community-supported. REST plus GraphQL.
Rate limitsProduction: 4 requests/second on Free or pay-as-you-go, 40 requests/second on subscriptions. Development: 4 requests/second on all plans.
Compliance claimsSOC 2 Type II, eIDAS, GDPR, UETA, ESIGN. HIPAA with a BAA on Enterprise. See Anvil Etch’s security and compliance page for details.

Where it fits

At a 30% abandonment rate, send-based billing charges every envelope while Anvil charges only the 70% that complete.

Its 4-requests/second free-tier production cap is the tightest here; a subscription lifts it to 40/second but adds a monthly fee.

Best for

Products with high signing-flow abandonment, where paying only on completion changes the cost model. Review the API docs and the pricing page.

White-labeled signing starts with Anvil’s Product Pack at $425 per month on monthly billing.

DocuSign eSignature REST API

DocuSign is the largest eSignature platform by market presence and the one most likely to appear in enterprise procurement.

Its REST API covers hosted envelopes, templates, PowerForms, and bulk send; DocuSign Federal separately offers FedRAMP Moderate and provisional DoD IL4 authorization.

DocuSign's developer plans and pricing page showing Starter at $50/month, Intermediate at $300/month, Advanced at $480/month, and Enhanced plans by quote, alongside a free developer account

DocuSign lists Starter at $50/month, Intermediate at $300/month, and Advanced at $480/month.

Key features at a glance

FeatureDetails
CoversHosted envelopes and templates. PowerForms and API bulk send from Advanced. Government-cloud deployment is a separate sales discussion.
Embedded signingEnhanced plans only (sales-led, five-user minimum). Embedded sending is available on every plan.
WebhooksAccount-, envelope-, and recipient-level Connect. Recipient-level access from Advanced. Retries extend across a 15-day span.
SandboxFree developer account with no expiry. Envelopes are not legally valid. Production requires a Go-Live review.
PricingStarter $50/month billed annually (from 40 envelopes/month), Intermediate $300/month, Advanced $480/month, Enhanced by quote. Billed per envelope sent. See DocuSign developer plans and pricing for current rates.
SDKsC#, Java, Node.js, PHP, Python, Ruby, iOS, Android.
Rate limitsAccount burst cap: 500 calls per 30 seconds, applied to both the developer/demo and production environments, within a 3,000-calls-per-hour baseline. One GET status check per envelope per 15 minutes.
Compliance claimsISO 27001/27017/27018, PCI DSS, SOC 1/2 Type II. Enhanced lists AES/QES and sales-led HIPAA BAA/Part 11 options. Federal-cloud authorizations (FedRAMP Moderate, DoD IL4) are separate from commercial plans. See DocuSign’s compliance certifications page for the full list.

Where it fits

The developer sandbox never expires, and its 8 official SDKs cover more platforms than any API here.

Embedded signing needs the Enhanced plan (five-user minimum); BoldSign, Dropbox Sign, Anvil, and Foxit offer it self-serve instead. GET endpoints allow only one status check per envelope every 15 minutes, making webhooks mandatory in practice.

Best for

Government buyers with procurement requirements that specify FedRAMP or DoD IL4 authorization.

Start with the plans and pricing page; the rate-limit figures above come from DocuSign’s own developer blog, linked in the key features table.

DocuSign's "From the Trenches: API rate limits" documentation page stating a 3,000 calls per hour default and a 500-call 30-second burst limit, applied to both the developer (demo) and production environments

DocuSign’s default limit is 3,000 calls per hour, with a 500-call cap per 30 seconds. Status polling for the same envelope is limited to once every 15 minutes.

Adobe Acrobat Sign API

Adobe Acrobat Sign’s REST API v6 covers agreements, library templates, bulk sending via MegaSign, and webhooks, though production access and embedded signing go through Adobe sales rather than a self-serve tier.

Adobe's Acrobat Sign API Overview developer documentation page listing agreement creation, signed-document retrieval, and webhook capabilities

Adobe’s REST API v6 lets you create agreements, retrieve signed documents, and set up webhooks.

Key features at a glance

FeatureDetails
CoversAgreements, library templates, bulk sending (MegaSign), and webhooks through REST API v6. Hosted.
Embedded signingDocumented in the developer guide. Production embedding requires an Acrobat Sign Solutions or Embed (OEM) partner program agreement.
WebhooksYes, through the webhook APIs.
SandboxFree Developer Edition. Test documents carry a “test” label.
PricingSales-led. Sent agreements consume transactions. Confirm allowances and terms in your contract. See Acrobat Sign Solutions for current options.
SDKsAdobe labels its older SDKs obsolete. Use REST v6 and the current Postman samples.
Rate limitsPlan-dependent. GET polling capped at 3 requests/minute for Global, Enterprise, and Developer tiers, or 3 per 3 minutes for other tiers. Polling limits don’t establish total throughput.
Compliance claimsAdobe’s compliance list, filtered to Acrobat Sign Solutions for enterprise, lists SOC 2 Type 2, SOC 3, ISO 27001, ISO 27017, ISO 27018, HIPAA ready, FDA 21 CFR Part 11 ready, and PCI DSS 4.0. Deployment and contract scope vary by product tier.

Where it fits

The API automates signing from CRMs for organizations already running Acrobat Sign, and MegaSign handles bulk sending to thousands of recipients.

Production access and embedded signing both require a sales-led Solutions license, and its 3-requests/minute GET polling limit is the tightest here.

Best for

Organizations that already license Acrobat Sign and want to automate existing workflows. Start with the developer guide and the free Developer Edition.

Adobe's Acrobat Sign Developer landing page offering a free Developer Edition with getting-started, quickstart, and REST API reference links

You can try Acrobat Sign’s API with a free Developer Edition before committing to a paid plan.

Adobe's Acrobat for business e-Sign page listing a consumer-plan price and directing medium and large businesses toward Acrobat Sign as a standalone, sales-reached product

To embed Acrobat Sign in a production app, you’ll need to arrange access through Adobe sales.

DocuSeal

DocuSeal is open source under AGPL-3.0, with a Pro tier adding embedding, white labeling, and a per-document fee.

The core API and webhooks are open-source, but embedded signing and the React, Vue, Angular, and JavaScript embed components require Pro, even when self-hosted.

DocuSeal's API Reference documentation page listing submission endpoints for sending, uploading, and downloading signed PDFs, with language SDK tabs across the top

DocuSeal uses submission endpoints to send documents and retrieve signed PDFs.

Key features at a glance

FeatureDetails
CoversSigning and form building. AGPL-3.0 core with mandatory UI attribution terms. Embedded signing on Pro. Hosted and self-hosted options.
Embedded signingPro tier, with React, Vue, Angular, and JavaScript components.
WebhooksForm, submission, and template events (available under the OSS license).
SandboxFree, unlimited API and embedding sandbox.
PricingPro: $20/user/month plus $0.20 per completed document. Same fee structure applies on Pro self-hosted. See DocuSeal pricing for current rates. Volume discounts are available.
SDKsFront-end embed components (React, Vue, Angular, JavaScript). Confirm server SDK availability before planning your integration.
Rate limitsNot published as of 7 Oct 2026.
Compliance claimsCloud: SOC 2 Type II and ISO 27001. HIPAA requires a BAA and activation in the HIPAA-compliant cloud tier. See DocuSeal’s security page for details.

Where it fits

The $0.20 per-document fee, combined with the $20 Pro seat, is the lowest-cost model here for moderate user counts and high volume.

AGPL-3.0 requires releasing modifications under the same license if you distribute one, so proprietary products should review terms with legal (a commercial license is available).

Best for

Teams that want a low per-document fee now, with the option to self-host later. Review the API docs and pricing.

DocuSeal's Pricing page showing Basic at $0/user/month and Pro at $20/user/month, with Pro including unlimited signature requests, Zapier and webhooks, and API and embedding

DocuSeal Pro combines a $20 monthly seat fee with a $0.20 charge per completed document.

Documenso

Documenso is open source under AGPL-3.0, with a REST API, an official TypeScript/Node.js SDK, and a self-hosted option.

Embedded signing is a Teams-plan feature; removing Documenso’s own branding needs the higher Platform tier.

Documenso's API Reference documentation page showing the v2 base URL, Authorization header requirement, and a deprecation notice that documents and templates are being replaced by an envelopes model

Documenso’s v2 REST API is moving from separate documents and templates to a unified envelopes model.

Key features at a glance

FeatureDetails
CoversDocuments, templates, recipients, fields, and webhooks through the v2 REST API (being consolidated into a unified “envelopes” model). Hosted and self-hosted options.
Embedded signingTeams plan and above, through the EmbedDirectTemplate component or signing tokens for documents created via the API. White-label embedding (no Documenso branding) requires the Platform plan.
WebhooksDocument, recipient, and template lifecycle events, with signature verification.
SandboxSeparate staging environment that mirrors production; requires its own account and a test card to try paid-plan features.
PricingTeams: $40/month billed yearly (5 users included, additional users $8/month), with API access and embedded signing. Platform: $250/month billed yearly, with unlimited API access and white-label embedding. See Documenso pricing for current rates and the Fair Use Policy.
SDKsOfficial TypeScript/Node.js SDK (@documenso/sdk-typescript) plus a React embed package. The REST API itself is language-agnostic.
Rate limits1,000 requests/minute per IP across /api/v1, /api/v2, and /api/v2-beta; organization-level limits can apply below that. Responses include X-RateLimit-* headers and a Retry-After value on 429s.
Compliance claimsSOC 2: compliant. 21 CFR Part 11 and HIPAA: compliant, but gated behind the Enterprise license. ISO 27001: planned, not yet certified. See Documenso’s compliance documentation for details.

Where it fits

The AGPL-3.0 core keeps a self-hosted path open if you outgrow the hosted tiers; a commercial license removes the modification-disclosure terms.

Compliance is split by plan, since SOC 2 applies broadly, but 21 CFR Part 11 and HIPAA require the Enterprise license, not the self-serve Teams or Platform tiers.

Best for

Teams that want an open-source, self-hostable alternative to a closed-source API, and can self-serve on Teams or Platform rather than needing Enterprise-tier compliance from day one. Review the developer docs and pricing.

The GitHub repository for @documenso/sdk-typescript, Documenso's official TypeScript/Node.js SDK, showing its MIT license and recent release history

The official TypeScript/Node.js SDK’s GitHub repository (MIT-licensed, separate from the AGPL-3.0 server).

Documenso's Pricing page showing Free at $0, Individual at $25/month, Teams at $40/month, and Platform at $250/month, each billed yearly

Documenso’s Teams plan includes embedded signing at $40/month; white-label embedding requires Platform at $250/month. Both prices are billed yearly.

Check which plan includes embedded signing

Every API here can technically embed a signing experience; the plan decides whether you can ship it to real users.

DocuSign gates it behind Enhanced (sales-led, 5-user minimum), Dropbox Sign requires Standard at $250/month, DocuSeal requires a Pro seat at $20/user/month, Documenso requires Teams at $40/month, and SignNow requires the quoted Site License.

Foxit eSign API includes it on every plan, including the free Developer plan. White-label branding often sits behind a still-higher plan, such as Dropbox Sign’s Premium, Anvil’s $425/month Product Pack, or Documenso’s $250/month Platform tier.

How billing triggers change your cost model

Vendors here bill at 4 points in the envelope lifecycle.

Foxit charges per envelope created, so cost accrues whether the signer completes, declines, or never opens it.

DocuSign, Adobe Acrobat Sign, BoldSign, and SignWell charge when it’s sent; SignNow’s Site License bills per signature invite similarly.

Dropbox Sign charges per signature request, close to send-based; Anvil and DocuSeal charge only when signing is completed; Documenso’s flat subscription doesn’t move with volume.

A 30% abandonment rate under send-based billing means paying for envelopes that generate no signed document. See Credits Explained, BoldSign’s API page, and Anvil pricing for the respective billing models.

Sandbox caps and rate limits you will hit first

Sandboxes aren’t identical here. BoldSign deletes sandbox documents after 14 days, so re-create test envelopes each run; DocuSign’s developer account never expires; Documenso tests through a separate staging environment.

VendorPublished limit
Dropbox Sign10 requests/minute in test mode
SignNow500 requests/hour per application in Development, 1,000 requests/hour by default in Live
SignWell100 requests/minute for general requests, 30/minute for document or template creation, and 20/minute in test mode
Adobe Acrobat Sign3 identical GET requests/minute on Global, Enterprise, and Developer tiers; 3 per 3 minutes on other tiers
DocuSignDo not poll the same envelope or API object more than once every 15 minutes
Documenso1,000 requests/minute per IP, plus organization-level windows

Dropbox Sign’s cap will slow fast-firing test suites. DocuSign’s rule matters most, since checking status after each signer action would violate it, so use webhooks wherever supported.

Confirm any unpublished limit before sizing your integration.

Match the API to your compliance requirements

A compliance badge doesn’t tell you whether an API’s default signing flow meets your requirements.

Work with legal to identify the signature level needed, including whether QES applies, and how the API captures consent and preserves the signing record.

For Foxit eSign API, the security and compliance documentation covers signature standards, consent capture, and audit trails. QES requires identity-validation services, so confirm the applicable plan with Foxit.

Best eSignature APIs FAQs

Start with the constraint that will block you in production, not the feature list. Check which plan includes embedded signing, what triggers billing, and whether compliance claims cover your plan tier. Then compare sandbox access, SDKs, and rate limits against your expected volume.

Foxit eSign API includes embedded signing on every plan, including the free Developer plan, so you can build and test in-app signing before you pay. Anvil Etch also includes embedding on its free plan. Most other vendors gate embedded signing behind a paid or sales-led plan, so check plan details before you build.

It depends on the billing trigger. Some vendors bill when you create or send an envelope, so declined or abandoned envelopes still count. Others bill only when every party signs, so estimate your costs at your expected completion rate.

Not always. Several vendors limit HIPAA, 21 CFR Part 11, or QES to specific plans or enterprise licenses. Confirm which plan meets your requirement, and work with legal to check how the API captures consent and preserves the signing record.

Choosing the right eSignature API

Start from the constraint that will actually block you in production, not the feature list.

A free or low-cost embedded-signing requirement rules out DocuSign and SignNow, narrowing the field to Foxit, BoldSign, Dropbox Sign, Anvil, and the two open-source options.

High abandonment rates favor Anvil’s completion-based billing or DocuSeal’s per-document model, since neither charges for a signature that never happens.

A regulated workflow needing 21 CFR Part 11 or HIPAA should check whether that’s included at its plan tier or gated behind an enterprise license, since several vendors here split that exact line.

For a product that already generates or converts PDFs and needs to send the result for signature, Foxit eSign API keeps eSign, PDF Services, and Document Generation on one developer account and one billing relationship, with embedded signing available from the free Developer plan. Create a free developer account to try it, or start with whichever API above matches your billing model, compliance requirements, and embedding needs.

How to extract parties, dates, and key terms from contracts using the Foxit API

Laptop with Python extraction code and a JSON record beside a contract with highlighted parties, a date, and a liability cap

Renewal dates slip past when contract terms live in PDFs and spreadsheets. This tutorial shows you how to pull parties, dates, notice periods, and liability caps from a signed contract with the Foxit PDF Services API, then add the signing audit trail from Foxit eSign. You end up with one JSON record per contract, ready for a CLM, CRM, or renewal-alert script.

Most teams still handle contract metadata the same way they did a decade ago. Someone opens the signed PDF, copies the effective date and renewal terms into a spreadsheet, and hopes nobody forgets to check back before the notice period closes. When the process breaks down, it usually breaks down quietly. A regex script built for one contract template chokes on the next one, and a renewal date slips past unnoticed.

This tutorial builds a two-stage pipeline that gets the same information out of a contract programmatically. The first stage uses Foxit’s PDF Services API to extract structured content metadata (party names, dates, notice periods, liability terms) from a signed PDF. The second stage calls the Foxit eSign API’s activity history endpoint to pull the signing audit trail, so the final record captures both what the contract says and how it got signed. By the end, you’ll have one JSON object per contract that’s ready to feed into a CLM tool, a CRM, or a renewal-alert script, built from a real API response rather than a screen-scrape.

What contract metadata looks like in practice

Before writing any code, it helps to know what you’re building toward. The two API calls this tutorial makes run independently against the same signed PDF and merge into one record, as shown below.

Diagram of a contract metadata extraction pipeline that merges PDF extraction and eSign activity history into one JSON record

The goal isn’t a wall of extracted text. It’s a small, typed record with the fields that actually drive downstream decisions:

{
  "party_a": "Northwind Robotics",
  "party_b": "Blue Harbor Analytics LLC",
  "effective_date": "March 3, 2026",
  "renewal_date": "March 3, 2028",
  "termination_notice_days": 30,
  "liability_cap": "$50,000"
}

That’s the schema this tutorial targets. Everything downstream, a renewal-alert cron job, a query against a CLM database, a BI dashboard, consumes a record shaped like this. The contract’s prose is the source of truth, but the schema is what actually gets used. A structural extraction API gets you from one to the other without hand-writing a parser for every contract template your organization uses.

Prerequisites

You’ll need the following before starting:

  • Python 3.8 or later, with pip for installing packages.
  • A virtual environment tool such as venv (included with Python) to isolate this project’s dependencies.
  • The requests library for making HTTP calls.
  • A code editor. VS Code with the Python extension works well for this walkthrough.
  • A free Foxit developer account. Sign up here. No credit card is required.

Set up your workspace:

mkdir contract-extraction && cd contract-extraction
python3 -m venv .venv
source .venv/bin/activate
pip install requests

Those four commands create the project folder, create and activate an isolated virtual environment inside it, and install the one library this tutorial’s code depends on.

Authenticating with the Foxit PDF Services API

Once you’ve signed up for a free developer account, the sign-up flow provisions API credentials immediately. There’s no separate approval step.

When you sign in, your dashboard has a client_id and client_secret. The PDF Services API and DocGen API authenticate with these two values sent as request headers, with no OAuth token exchange required.

Foxit developer dashboard showing client ID and masked client secret fields above a pre-filled cURL example
export FOXIT_CLIENT_ID="your_client_id"
export FOXIT_CLIENT_SECRET="your_client_secret"

Foxit eSign, which this tutorial’s second stage uses to pull signing metadata, runs on a separate host with its own token-based authentication, and needs a one-time activation step from your dashboard (look for “Get started with eSign”) before its endpoints will accept calls. Activate it now so it’s ready by the time you reach that section; the eSign section below covers the token exchange itself.

For the full authentication reference, see the Foxit API docs, or try a call directly against the interactive API reference before writing any code.

Uploading a contract and calling the extraction endpoint

The extraction flow uses two calls, uploading the PDF to get a document ID, then posting that document ID to the structural extraction endpoint.

This tutorial uses a short sample NDA between two fictional companies, with a purpose clause, a term-and-renewal clause, a termination clause, a liability cap, and a key-dates summary table. It’s built to mirror the clause structure of a real commercial NDA.

Step 1: Upload the document

import os
import requests

BASE_URL = "https://na1.fusion.foxit.com"
HEADERS = {
    "client_id": os.environ["FOXIT_CLIENT_ID"],
    "client_secret": os.environ["FOXIT_CLIENT_SECRET"],
}

with open("nda_sample.pdf", "rb") as f:
    upload_response = requests.post(
        f"{BASE_URL}/pdf-services/api/documents/upload",
        headers=HEADERS,
        files={"file": ("nda_sample.pdf", f, "application/pdf")},
    )
upload_response.raise_for_status()
document_id = upload_response.json()["documentId"]
print(document_id)

In this code, you open the local PDF, POST it as multipart form data to the upload endpoint, and read back a documentId. That ID is a handle to the file inside Foxit’s system; every subsequent call in this pipeline references the document by this ID rather than re-uploading the file.

Run this command from your terminal:

python3 step1_upload.py

The expected output should look like the following:

6ab519b2c7ca700694bf3174

Step 2: Call the structural extraction endpoint

The extraction call returns immediately with a taskId, and the client polls the task endpoint every two seconds until the status flips to COMPLETED, as shown below.

Diagram of the async extraction flow: upload the PDF, start extraction, poll task status, then download the result ZIP
import time

extract_response = requests.post(
    f"{BASE_URL}/pdf-services/api/documents/pdf-structural-extract",
    headers={**HEADERS, "Content-Type": "application/json"},
    json={"documentId": document_id},
)
extract_response.raise_for_status()
task_id = extract_response.json()["taskId"]

while True:
    task_response = requests.get(f"{BASE_URL}/pdf-services/api/tasks/{task_id}", headers=HEADERS)
    task = task_response.json()
    print(task)
    if task["status"] == "COMPLETED":
        result_document_id = task["resultDocumentId"]
        break
    if task["status"] == "FAILED":
        raise RuntimeError(task.get("error"))
    time.sleep(2)

download_response = requests.get(
    f"{BASE_URL}/pdf-services/api/documents/{result_document_id}/download",
    headers=HEADERS,
)
with open("extraction_result.zip", "wb") as f:
    f.write(download_response.content)

This code starts the extraction as an asynchronous task, since structural analysis takes longer than a typical request-response cycle. It posts the documentId, gets back a taskId, then polls the task status endpoint every two seconds until the status flips to COMPLETED or FAILED. Once complete, the task response includes a resultDocumentId pointing at a ZIP archive, downloaded in the final step. On the sample NDA, this whole flow (upload, extract, poll, download) completes in well under ten seconds.

Run this command from your terminal:

python3 step2_extract.py

The expected output should look like the following:

{'taskId': '6ab519d30a17a67b90dea85d', 'status': 'IN_PROGRESS', 'progress': 20}
{'taskId': '6ab519d30a17a67b90dea85d', 'status': 'COMPLETED', 'progress': 100, 'resultDocumentId': '6ab519d5316b427cfaec5212'}

Unzip extraction_result.zip and, on this run, you get exactly two files. Run this command from your terminal:

unzip extraction_result.zip && ls

The expected output should look like the following:

page_p0.pdf_0.png
StructureInfo.json

That JSON is the real output of this article. Here’s an abbreviated version of what it actually contains, taken from the same live run against the sample NDA (the full StructureInfo.json from this run is available if you want to see every element):

{
  "analyzeResult": {
    "elements": [
      {
        "type": "title",
        "content": { "text": "Mutual Non-Disclosure Agreement" },
        "region": { "page": 1, "boundingBox": [142, 70, 470, 70, 470, 99, 142, 99] },
        "id": "title1"
      },
      {
        "type": "paragraph",
        "content": {
          "text": "This Mutual Non-Disclosure Agreement (the \"Agreement\") is entered into as of March 3, 2026 (the \"Effective Date\"), by and between Northwind Robotics, Inc., ... (\"Party A\"), and Blue Harbor Analytics LLC, ... (\"Party B\")."
        },
        "id": "paragraph1"
      },
      {
        "type": "head",
        "content": { "text": "2. Term and Renewal" },
        "id": "head2",
        "level": 1
      },
      {
        "type": "table",
        "content": {
          "body": {
            "rowCount": 6,
            "columnCount": 2,
            "cells": [
              { "paragraph": { "content": { "text": "Effective Date" } }, "rowIndex": 1, "columnIndex": 0 },
              { "paragraph": { "content": { "text": "March 3, 2026" } }, "rowIndex": 1, "columnIndex": 1 }
            ]
          }
        }
      }
    ]
  }
}

The response is a flat list of elements, each carrying a type (title, head, paragraph, or table, among others) and the element’s content.text. For title, head, and paragraph elements, a region.boundingBox gives its position on the page. table elements don’t follow that shape, since their region is an empty object, and position instead lives in a plural regions array (alongside a plural scores array), so code that reads element["region"]["boundingBox"] generically across every element type raises a KeyError the moment it reaches a table. Paragraphs carry plain text; tables carry a content.body.cells array with rowIndex/columnIndex coordinates instead of a 2D array, since that’s the shape that survives merged cells and irregular tables cleanly. Every field name here comes directly from a real response; none of it is asserted from documentation alone.

Mapping the raw extraction response to a typed schema

StructureInfo.json describes the document’s structure. It doesn’t hand you party_a and effective_date directly, so the next step is a small amount of post-processing that turns the element list into the schema from the introduction.

import json
import re
import zipfile

with zipfile.ZipFile("extraction_result.zip") as z:
    with z.open("StructureInfo.json") as f:
        structure = json.load(f)

elements = structure["analyzeResult"]["elements"]

def normalize(text):
    return " ".join(text.split())

paragraphs = [normalize(el["content"]["text"]) for el in elements if el["type"] == "paragraph"]
full_text = " ".join(paragraphs)

record = {}

match = re.search(r'([A-Z][\w\s,.]+?)\s*\("Party A"\)', full_text)
if match:
    record["party_a"] = match.group(1).split(",")[0].strip()

match = re.search(r'([A-Z][\w\s,.]+?)\s*\("Party B"\)', full_text)
if match:
    record["party_b"] = match.group(1).split(",")[0].strip()

match = re.search(r'as of\s+([A-Z][a-z]+ \d{1,2}, \d{4})', full_text)
if match:
    record["effective_date"] = match.group(1)

match = re.search(r'expiring on\s+([A-Z][a-z]+ \d{1,2}, \d{4})', full_text)
if match:
    record["renewal_date"] = match.group(1)

match = re.search(r'upon \w+ \((\d+)\) days.{0,10}prior written notice', full_text)
if match:
    record["termination_notice_days"] = int(match.group(1))

match = re.search(r'\$([\d,]+)\)?\s*\(the "Liability Cap"\)', full_text)
if match:
    record["liability_cap"] = f"${match.group(1)}"

print(json.dumps(record, indent=2))

This code reads the ZIP directly without extracting it to disk, then joins every paragraph element’s text into one searchable string. Because the raw text carries mid-string line breaks where a sentence wraps across a line in the source PDF, the normalize helper collapses any run of whitespace down to a single space before the string gets used, which is what keeps the regular expressions below from silently failing on a hidden newline. From there, a handful of targeted patterns pull out the fields this NDA’s clause language reliably contains, including the parenthetical ("Party A")/("Party B") labels this template uses to tag each signer, the “as of” and “expiring on” date phrases, the notice-period clause, and the dollar figure tagged (the "Liability Cap") in the liability clause. The liability clause writes the figure as fifty thousand dollars ($50,000) (the "Liability Cap"), so the regex allows an optional closing parenthesis between the digits and the label rather than assuming the label follows the number directly. Run against the sample NDA, this produces exactly the record shown in the introduction.

Run this command from your terminal:

python3 map_schema.py

The expected output should look like the following:

{
  "party_a": "Northwind Robotics",
  "party_b": "Blue Harbor Analytics LLC",
  "effective_date": "March 3, 2026",
  "renewal_date": "March 3, 2028",
  "termination_notice_days": 30,
  "liability_cap": "$50,000"
}

A production version of this would likely walk the table element’s cells too, since the sample NDA’s key-dates table restates the same fields in a much more parseable, label-value shape, but the paragraph-text approach shown here works even for contracts that don’t include a summary table.

Enriching the record with eSign audit metadata

The extraction above tells you what the contract says. It doesn’t tell you whether it’s actually been signed, or by whom, or when. That’s what Foxit eSign’s activity history endpoint adds, closing the loop between “here’s what the contract promises” and “here’s proof it was executed.”

eSign authenticates separately from PDF Services, with its own client ID and secret and its own token exchange. Find this pair by signing in to the eSign portal as the account owner, opening the account’s API settings page, and clicking “View All Credentials” (this is owner-gated and triggers a one-time email confirmation, so a non-owner login won’t see the button). Export them the same way you exported the PDF Services pair:

export FOXIT_ESIGN_CLIENT_ID="your_esign_client_id"
export FOXIT_ESIGN_CLIENT_SECRET="your_esign_client_secret"

With those in place, exchange them for a bearer token:

esign_response = requests.post(
    "https://na1.foxitesign.foxit.com/api/oauth2/access_token",
    data={
        "client_id": os.environ["FOXIT_ESIGN_CLIENT_ID"],
        "client_secret": os.environ["FOXIT_ESIGN_CLIENT_SECRET"],
        "grant_type": "client_credentials",
        "scope": "read-write",
    },
)
esign_response.raise_for_status()
esign_token = esign_response.json()["access_token"]

This posts your eSign credentials as form-encoded data (not JSON) and gets back a bearer token, which every subsequent eSign call uses in an Authorization header. This is a separate credential pair and a separate token from the PDF Services calls above; see the eSign activation note in the “Authenticating with the Foxit PDF Services API” section above if you haven’t turned this on yet.

Sending the same credentials as a JSON body instead of data= is a real, reproducible mistake. Run this command from your terminal to reproduce it:

python3 -c "
import os, requests
r = requests.post(
    'https://na1.foxitesign.foxit.com/api/oauth2/access_token',
    json={
        'client_id': os.environ['FOXIT_ESIGN_CLIENT_ID'],
        'client_secret': os.environ['FOXIT_ESIGN_CLIENT_SECRET'],
        'grant_type': 'client_credentials',
        'scope': 'read-write',
    },
)
print(r.status_code, r.reason)
"

The expected output should look like the following:

415 Unsupported Media Type

Sending the NDA for signature

The activity history endpoint only has something to report once a contract has actually gone out for signature, so this tutorial sends the same sample NDA through eSign to get a real folder ID to query. POST /api/folders/createfolder accepts the file directly as base64, which sidesteps hosting it somewhere with a public URL first:

import base64

with open("nda_sample.pdf", "rb") as f:
    encoded_pdf = base64.b64encode(f.read()).decode("utf-8")

create_folder_response = requests.post(
    "https://na1.foxitesign.foxit.com/api/folders/createfolder",
    headers={"Authorization": f"Bearer {esign_token}"},
    json={
        "folderName": "nda_sample",
        "inputType": "base64",
        "base64FileString": [encoded_pdf],
        "fileNames": ["nda_sample.pdf"],
        "parties": [
            {"partyNumber": 1, "name": "Alex Rivera", "email": "[email protected]"}
        ],
        "sendNow": True,
    },
)
create_folder_response.raise_for_status()
folder_id = create_folder_response.json()["folder"]["folderId"]
print(folder_id)

This code base64-encodes the same nda_sample.pdf used in the extraction step and posts it to the folder-creation endpoint, along with inputType: "base64" (required whenever the file comes as base64 rather than a fileUrls array) and a single party to sign. sendNow: true dispatches the invitation immediately instead of leaving the folder in DRAFT, and the response nests the new folder’s ID at folder.folderId, which is the folder_id the next call reads.

Once a contract has actually been sent for signature, its activity history is available by folder ID:

history_response = requests.get(
    f"https://na1.foxitesign.foxit.com/api/folders/viewActivityHistory?folderId={folder_id}",
    headers={"Authorization": f"Bearer {esign_token}"},
)
history_response.raise_for_status()
activity = history_response.json()["details"]
print(json.dumps(activity, indent=2))

Called against a real folder right after sending it for signature, this is the actual response:

{
  "folderId": 36096051,
  "enclosedDocuments": "nda_sample",
  "envelopeRecipients": "Alex Rivera",
  "author": "annika annika",
  "status": "SHARED",
  "dateCreated": "24 Sep 2026, 02:48:35, PDT",
  "dateSent": "24 Sep 2026, 02:48:35, PDT",
  "activities": [
    {
      "activity": "annika annika created this folder",
      "folderStatus": "CREATED",
      "action": "Created",
      "time": "24 Sep 2026, 02:48:35, PDT",
      "source": "API"
    },
    {
      "activity": "Invitation sent to Alex Rivera ( [email protected])",
      "folderStatus": "SHARED",
      "action": "Invitation Sent",
      "time": "24 Sep 2026, 02:48:35, PDT",
      "source": "Web"
    }
  ]
}

The endpoint refers to the contract’s signing container as a “folder” in the API’s own field names (folderId, folderStatus), even though the eSign portal’s UI labels the same thing an “envelope.” Both terms describe the same object; this article uses “folder” since that’s what the API actually returns. details.status tracks the folder’s lifecycle (SHARED once sent, moving through PARTIALLY SIGNED or COMPLETED as signers act, ending at EXECUTED once every signature is applied and finalized), and activities is an ordered log of every event with a timestamp and a source (API or Web, depending on how the action was triggered). Before you build this into a pipeline, know that this endpoint only works once a folder has actually been shared or sent. Calling it against a folder still in DRAFT status returns an error rather than an empty activity list, so don’t rely on it as a general-purpose status check before the folder is sent. Run this command from your terminal, pointed at a folder still in DRAFT status:

python3 view_activity_history.py

The expected output should look like the following:

{"result": "error", "error_description": "logs of a non-shared folder can not be viewed."}
Foxit eSign document list showing the NDA sent to Alex Rivera on Sep 24, 2026 with a Shared status

The final step merges this into the schema dict built earlier:

record["folder_status"] = activity["status"]
record["signing_activity"] = [
    {"event": a["action"], "time": a["time"]} for a in activity["activities"]
]

print(json.dumps(record, indent=2))

That gives you one complete record per contract, its extracted terms and its signing history, in a single dict. Run this command from your terminal:

python3 merge_record.py

The expected output should look like the following, using the same folder queried above:

{
  "party_a": "Northwind Robotics",
  "party_b": "Blue Harbor Analytics LLC",
  "effective_date": "March 3, 2026",
  "renewal_date": "March 3, 2028",
  "termination_notice_days": 30,
  "liability_cap": "$50,000",
  "folder_status": "SHARED",
  "signing_activity": [
    { "event": "Created", "time": "24 Sep 2026, 02:48:35, PDT" },
    { "event": "Invitation Sent", "time": "24 Sep 2026, 02:48:35, PDT" }
  ]
}

With these three steps, you can turn any signed contract into a single record your downstream system can use: run the extraction, pull the audit trail, and merge the two.

Common mistakes

  • Sending JSON to the eSign token endpoint : it requires form-encoded data (data=, not json= in requests). A JSON body returns a 415 error.
  • Calling the activity history endpoint on a draft folder : it only works once the folder has been shared or sent; a draft returns an error instead of an empty result.
  • Assuming region.boundingBox is a four-number rectangle : it’s an eight-number polygon (four corner point pairs), not [x, y, width, height].
  • Reading element["region"]["boundingBox"] on every element type without checking type first : table elements carry an empty region object and expose position through regions and scores (both plural) instead, so a generic per-element loop raises a KeyError the moment it reaches a table.
  • Treating table cells as a 2D array : table content lives in content.body.cells, a flat list with rowIndex/columnIndex on each cell, not a nested rows array.
  • Skipping whitespace normalization before regex matching : paragraph text preserves the source PDF’s line wraps as literal characters mid-sentence, which breaks patterns that assume a single space between words.

Get started

The pipeline in this tutorial extracts what a contract says and proves how it was signed, in two API calls plus a lightweight mapping step, no regex-per-template maintenance burden and no manual copy-paste. Once you have a structured record per contract, the obvious next steps are automated renewal alerts before a notice period closes, obligation tracking against the liability and termination terms, and pushing the merged record straight into a BI dashboard or CLM system.

Create a free Foxit developer account, no credit card required, and run this extraction against one of your own contracts using the interactive API reference.

Contract metadata extraction FAQs

Contract metadata extraction is the process of pulling key details out of a contract, such as party names, effective dates, renewal dates, notice periods, and liability caps, and turning them into structured data. Instead of copying terms into a spreadsheet by hand, you get a typed record that feeds a CLM tool, a CRM, or a renewal-alert script.

You can extract any text the contract contains, including parties, effective and renewal dates, termination notice periods, and liability caps. The Foxit PDF Services API returns the document’s titles, headings, paragraphs, and tables as structured JSON. A short mapping step then turns that output into the exact fields your downstream system needs.

No. The structural extraction API returns the same JSON format for every PDF, so you don’t need to parse raw layouts for each template. You still map fields to your schema, usually with patterns tuned to your clause language. Contracts with a key-dates summary table are easier, since the table cells come back as label-value pairs.

Call the Foxit eSign activity history endpoint with the contract’s folder ID. It returns the folder’s status and a timestamped log of every event, such as when the contract was created and when invitations went out. The endpoint only works after the folder has been shared or sent. A draft folder returns an error.

Yes. A free Foxit developer account gives you live API credentials right away, with no credit card and no approval step. The free plan includes 500 credits per year, which is enough to build and test a full contract metadata extraction pipeline. You can also test calls in the interactive API reference before writing code.

Adobe Sign API alternatives: 5 simpler options for embedded signing

Glowing browser window with a handwritten signature, linked to four API code cards, illustrating Adobe Sign API alternatives

The Adobe Sign API supports embedded signing, but getting it to real users means going through a partner program and a sales conversation. This roundup compares five alternatives on authentication, iframe embedding, SDK coverage, and pricing. Find the right fit for your use case and start testing in a free sandbox today.

Embedded signing has a clear technical shape. An iframe loads a document, captures the signature, and fires a webhook when it’s done. Adobe Sign’s API handles that, but the path from credentials to a production embed is longer than the feature requires.

This roundup covers five alternatives evaluated on authentication setup, envelope creation, iframe embedding, and webhook handling. Each entry is specific enough to shortlist and act on today.

What Adobe Sign’s API gives you for embedded signing

Adobe’s Acrobat Sign API follows a standard OAuth2 flow. Your application exchanges credentials for a bearer token, posts to the /agreements endpoint to create an agreement, then calls /agreements/{agreementId}/views with a SIGN view type to receive a signing URL. The embedded experience loads from that URL.

The integration itself is well-documented. The production delta for teams building in-app signing surfaces at the shipping stage. Adobe does offer a free Acrobat Sign Developer Edition for testing, but shipping the embedded experience to real users goes through Acrobat Sign Embed, an OEM partner program that requires onboarding, provisioning, and app certification. Adobe’s own documentation directs developers to contact Adobe to become an Embed partner. Pricing for the API and the embed path is unpublished and requires direct contact. Teams that need an iframe signing session inside their product often find that the full onboarding cycle exceeds what the use case demands.

This article sets Acrobat Sign as the benchmark, covers what its production embed path actually involves, and then presents five alternatives that reach production faster.

Adobe Acrobat Sign Embed developer docs telling teams to contact Adobe to become an embed partner

Adobe’s own Acrobat Sign Embed docs, confirming the OEM partner program requires contacting Adobe directly rather than a self-serve signup.

What “simpler” means for an embedded signing API

Every alternative in this roundup is evaluated on four criteria.

  1. Steps from credentials to first embedded session. Fewer authentication steps and self-serve signup mean faster iteration.
  2. True iframe embed vs. redirect-only. A genuine iframe keeps signers inside your product. A redirect breaks the in-app experience.
  3. SDK language coverage. Real, installable packages on public registries (npm, PyPI, Maven, RubyGems) vs. sample code.
  4. Pricing model. Self-serve tiers with published rates vs. sales-gated access and custom quotes.

5 alternatives worth shortlisting

The comparison table below summarizes all 5 alternatives across these criteria.

ProviderEmbed ApproachSDK CoverageCompliancePricing ModelFree Tier
Dropbox Signiframe (hellosign-embedded)6 official SDKs (Python, Node, PHP, .NET, Ruby, Java)ESIGN, UETA, GDPRSelf-serveYes (test mode)
SignWelliframe4 official SDKs (Ruby, Node, Python, PHP)ESIGN, UETASelf-serve, published pricingYes
BoldSigniframeREST + official SDKsESIGN, UETA, GDPRSelf-serveYes (free sandbox)
Documensoiframe (embed SDKs for React, Vue, Svelte, Angular, Solid, Preact)6 framework-specific embed SDKsESIGN, UETA, GDPRSelf-serve / self-hostFree (self-host)
Foxit eSign APIiframe (embeddedSessionURL)Sample code in cURL, PHP, C#, Java; Postman collectioneIDAS (AES, QES), ESIGN, UETA, 21 CFR Part 11, HIPAA, GDPR, CCPASelf-serve trial, then pricing at developer-api.foxit.com30-day TEST mode trial

Dropbox Sign

Dropbox Sign delivers one of the cleaner developer experiences in this space. The hellosign-embedded library wraps the iframe lifecycle, handling the parent window’s domain verification and the post-signing event messages. Official SDKs exist on PyPI, npm, Packagist, NuGet, RubyGems, and Maven. Two gates apply before you hit live traffic, as embedded apps need App Approval before leaving test mode, and the iframe verifies the parent window’s domain, so your production domain must be registered. Best for teams where polished DX and iframe UX are the top priorities.

Dropbox Sign’s API features comparison, confirming the 6-language SDK coverage and embedded-signing feature.

Dropbox help center article on setting up Dropbox Sign embedded signing with the hellosign-embedded iFrame library

Dropbox Sign’s embedded signing documentation, naming the App Approval and domain-matching requirements before production traffic.

SignWell

SignWell publishes its pricing transparently on its pricing page and exposes a clean REST API that returns a signing URL you embed directly. SignWell also ships four official SDKs, covering Ruby, Node, Python, and PHP, so integration does not have to stay at the raw HTTP layer. The predictable pricing structure makes SignWell a reasonable fit for teams with variable signing volume that need cost certainty before committing.

SignWell pricing page showing Free, Light, Business, and Enterprise plans, with API access included on each tier

SignWell’s pricing page, showing published self-serve rates rather than a sales-gated quote.

SignWell API getting started docs listing CLI, MCP, Ruby, Node, and Python SDK resources

SignWell’s “Getting Started” docs page, listing the four official SDKs alongside the OpenAPI file.

BoldSign

BoldSign offers a free API sandbox that covers the full build-and-test cycle for embedded send-and-sign before you go anywhere near production. The path from sandbox credentials to a working iframe is short. Check BoldSign’s pricing page for current plan details. Best for teams optimizing for the fastest route to a working production embed.

BoldSign developer hub homepage with Try free Sandbox and Create API Sandbox buttons

BoldSign’s developer hub, showing the free sandbox signup and the Embedded Send & Sign feature. (BoldSign’s own pricing page geo-blocks automated requests from this connection, so it isn’t pictured here; the URL above is live and correct.)

Documenso

Documenso is open-source and self-hostable, which changes the data-control equation entirely. The project ships official embed SDKs for React, Vue, Svelte, Angular, Solid, and Preact, plus a web-component package. Teams that need their signed documents to stay on their own infrastructure can self-host for free. On Documenso Cloud, embedded signing starts at the Teams plan. Best for teams with strict data residency requirements or open-source mandates.

Documenso pricing page showing Free, Individual, Teams, and Platform plans, with embedded signing from Teams up

Documenso’s pricing page, confirming Embedded Signing is included from the Teams plan up, with self-hosting as the free alternative.

Documenso’s embedding docs, listing the framework-specific SDK packages.

Foxit eSign API for embedded signing

Foxit eSign API covers the complete embedded signing stack, including authentication, envelope creation, iframe signing sessions, embedded sending sessions, webhooks, and branding controls, all served from a cloud-hosted REST API.

Credentials and regional setup

Foxit separates its API products at the credential level. The Developer Portal at developer-api.foxit.com issues keys for PDF Services and Document Generation. eSign uses a separate account and dashboard. Create your account at the Foxit eSign Portal (login.foxitesign.foxit.com), then activate API access from the API tab in your account settings. Your Client ID and Client Secret appear there once API access is active.

Foxit eSign Portal sign-in page with email, password, SSO login, and social sign-in options

The Foxit eSign Portal sign-in page, the separate account referenced above (distinct from the main Developer Portal login).

Before your first API call, pick your regional host. Foxit supports four regions, and your credentials are scoped to the region where they were issued.

  • US: na1.foxitesign.foxit.com
  • EU: eu1.foxitesign.foxit.com
  • Canada: na2.foxitesign.foxit.com
  • Australia: au1.foxitesign.foxit.com

Authentication

Foxit eSign uses the OAuth2 client_credentials grant. POST your credentials form-encoded (a JSON body returns HTTP 415) to /api/oauth2/access_token.

POST https://na1.foxitesign.foxit.com/api/oauth2/access_token  
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials  
&client_id=YOUR_CLIENT_ID  
&client_secret=YOUR_CLIENT_SECRET  
&scope=read-write  

The response contains an access_token. Pass it as Authorization: Bearer <token> on every subsequent request.

Envelope creation and the embedded session URL

To launch an embedded signing session, create the envelope with the embedded session flag set. The API returns an embeddedSessionURL for each recipient. Use the complete URL the API returns, because assembling it manually produces failures when session hosts and tokens vary by region.

The signing experience loads into a standard iframe.

<div style="height: 800px; width: 100%; overflow: hidden;">  
  <iframe  
    id="esignIframe"  
    src="EMBED_SESSION_URL"  
    title="Foxit eSign signing session"  
    style="width: 100%; height: 100%; border: 0;"  
  ></iframe>  
</div>  

Include the esignGeniePostMessageParent.js script on the parent page to receive post-message events from the signing session. After signing or declining, Foxit eSign redirects to your specified URL and appends folderId and event as query parameters (signing_success or signing_declined).

A real embeddedSessionURL opened directly (the same page an iframe would load), created live with createEmbeddedSigningSession, showing the Text Tag field rendered as an interactive input and the “Required Fields Left” counter this article’s flow produces.

To give preparers an embedded document-preparation experience, set createEmbeddedSendingSession to true in the Create Envelope request. Foxit returns a second embeddedSessionURL for the sending view, which you embed the same way.

Document preparation

Two paths exist for placing signature fields.

  • Text Tags in the PDF: embed tag syntax directly in the PDF content, then set processTextTags: true in the envelope creation call. Foxit converts the tags into fields without removing the text. Match the tag font color to the document background to hide the raw tag text from signers.
  • Direct API field placement: if your application already holds the page coordinates and field dimensions, POST the field definitions directly and skip Text Tags entirely.

Webhooks

Webhooks deliver real-time envelope status changes without polling. Configure webhook channels to receive events when an envelope reaches EXECUTED status, which lets your application trigger downstream steps (such as uploading the signed document to a CRM or generating follow-up forms) the moment signing completes.

Code samples and integration path

Foxit’s eSign developer guide ships sample code in cURL, PHP cURL, C#, and Java. Foxit also publishes an eSign Postman collection on GitHub, which covers the full request sequence from authentication through envelope retrieval. The API overview page lists .NET, Java, PHP, Python, Ruby, and TypeScript as SDK languages, but no packages under those names appear on PyPI, npm, RubyGems, Packagist, or Maven. eSign integration is a direct REST integration with sample code in four languages, with no six-language installable SDK suite available.

Compliance coverage

Foxit eSign covers a broad compliance floor, documented at foxit.com/esign-pdf/maintain-industry-compliance/, including eIDAS conformance at the Advanced Electronic Signature (AES) and Qualified Electronic Signature (QES) levels, ESIGN, UETA, 21 CFR Part 11, HIPAA, GDPR, and CCPA. Audit trails include identity verification, tamper-evident logs, and certificates of completion. Regional hosting across four jurisdictions keeps signed documents within a chosen geographic boundary without requiring you to build compliance infrastructure from scratch.

Trial mode

Activating eSign provisions a 30-day trial in TEST mode. Completed envelopes in TEST mode carry a watermark. Check developer-api.foxit.com for current pricing and plan details before moving to production.

How to pick the right provider

Match your primary requirement to the provider built for it.

  • Regulated industries requiring eIDAS at AES or QES level, HIPAA, or 21 CFR Part 11 coverage: Foxit eSign API.
  • Transparent self-serve pricing and predictable per-document costs: SignWell.
  • Fastest path to a working production embed with minimal friction: BoldSign.
  • Self-hosted or open-source data control requirements: Documenso.
  • Cleanest developer experience with a polished iframe UX library: Dropbox Sign.

Identify the scenario that matches your requirements and open that provider’s free tier or sandbox today. Every option above gives you something to test without a sales call.

Adobe Sign API FAQ

An embedded signing API is a REST API that returns a session URL a developer loads inside their own application, typically via iframe, so signers complete the entire signing process without leaving the host product. The API handles session creation, field rendering, signature capture, and status callbacks on the server side.

The most common reasons are the enterprise-tier access requirements that gate the production embedded signing path, pricing that is unpublished for self-serve on the API or Embed OEM program, and an integration scope that exceeds what a dedicated embedded signing use case needs. Teams building a single in-app signing flow often want a REST API, a sandbox, and published pricing, and several alternatives deliver exactly that.

Yes. The Create Envelope endpoint returns an embeddedSessionURL that loads directly into an iframe on your page. Include the esignGeniePostMessageParent.js script on the parent page to receive post-message events from the signing session, and configure a redirect URL to capture the signing_success or signing_declined outcome.

Apply four criteria, counting the steps from credentials to your first embedded session, confirming the API returns a true iframe URL rather than a redirect, checking whether the provider’s SDKs are real installable packages on public registries, and verifying that pricing is published at a self-serve tier your team can access without a sales conversation. The comparison table above maps all five providers across these dimensions.

Most providers cover ESIGN and UETA, which establish legal validity for electronic signatures in the US. GDPR coverage is common among providers with EU hosting. Foxit eSign API extends further to eIDAS at AES and QES levels, HIPAA, 21 CFR Part 11, and CCPA, making it the strongest option for regulated industries.

Yes. Every provider in this roundup offers a free tier or sandbox. Dropbox Sign provides a test mode, BoldSign offers a free sandbox covering the full build-and-test cycle, SignWell has a free plan, Documenso is free to self-host, and Foxit eSign API provisions a 30-day TEST mode trial on account activation.

A true iframe embed loads the signing session inside your application’s page, so the signer never leaves your product. A redirect-based flow sends the signer to an external URL, breaking the in-app experience and handing off visual control to the signing provider. For products where brand consistency and user retention matter, a true iframe embed is the correct choice.

Getting started with Foxit eSign API

Create a free Foxit account at app.developer-api.foxit.com/sign-up, activate eSign from your account settings, and collect your Client ID and Client Secret from the Foxit eSign Portal. Those eSign credentials are issued separately from your PDF Services and Document Generation keys, so look in the eSign Portal’s API tab specifically. From there, authenticate with the client_credentials grant, create an envelope, and load the returned embeddedSessionURL into an iframe. The full flow is testable in 30-day TEST mode with no commitment required.

6 Best PandaDoc API alternatives for developers in 2026

Illustration of six eSign vendors connecting to a signed document, surrounded by API, OAuth2, and compliance badge panels

PandaDoc’s API works well for sales documents, but Enterprise-only access and per-seat pricing get in the way of signing embedded in your product. This roundup compares six alternatives, including Foxit eSign, DocuSign, and BoldSign, across seven criteria that decide how well an eSign API fits your app. Use the side-by-side table to shortlist the right option before you commit.

PandaDoc built its API around document workflows (proposals, quotes, contracts with approval chains). The API is capable, but two frictions surface quickly when you try to embed signing inside a product you’re building. API access sits on the quote-based Enterprise tier, so a developer who wants programmatic control can’t pick a published plan and start. The self-serve plans are priced per seat, tracking headcount rather than envelope volume, which misaligns cost with the way product-embedded signing actually scales.

If your use case is pure signing or in-app embedded flows, you’ll likely outgrow PandaDoc’s API shape before you outgrow the need. This roundup covers six PandaDoc API alternatives (Foxit eSign, DocuSign, Adobe Acrobat Sign, Dropbox Sign, SignNow, and BoldSign), measured across seven criteria that determine how well an e-signature API fits a product integration. Every claim below was checked against the vendor’s own live pages in September 2026, and the Foxit behaviour was checked against the live sandbox.

What to look for in an e-signature API

The best e-signature API for a product integration keeps signers inside your application, authenticates server-side without interrupting the flow, and prices by usage rather than headcount. Every entry in this roundup is assessed against the same seven criteria, each of which maps to a row in the comparison table below.

In-app embedding mechanism. Three patterns exist in the market. The first returns a raw iframe URL directly from the API that you drop into an <iframe>. The second provides a vendor JavaScript library or web component that manages the session for you. The third redirects the signer to the vendor’s own domain and returns them afterward. The table names the mechanism and says whether the signer stays inside your application.

Auth approach. The grant type and how credentials reach the token endpoint. Client-credentials grants suit server-side integrations best, since no human login step interrupts the flow.

Webhook granularity. Which lifecycle events fire (viewed, signed, declined, expired) and whether payloads carry a signed HMAC you can verify before trusting them.

Client-library coverage. Official language SDKs versus a documented REST API with request samples. Both work, but the delta in integration time is real, and a published SDK that the vendor itself marks as out of date is worth less than a clean REST reference.

Named compliance certifications. Listed by name, not tier label. Certifications gated behind a plan upgrade, a signed agreement, or a qualified third-party provider are marked as such, because the conditions matter in a procurement conversation.

Pricing model structure. Described as per-seat, per-envelope, volume-based, or consumption and credit-based.

Free developer tier. Whether one exists, whether a credit card is required, and whether it carries production traffic or test-mode traffic only. Those three facts determine whether you can validate your integration before committing.

PandaDoc API at a glance

PandaDoc offers a free tier that includes 60 documents per year with no credit card, two self-serve per-seat monthly tiers at $19 and $49 per seat, and a quote-based Enterprise tier where API access lives.

PandaDoc pricing page with Free, Starter, and Business plans, and PandaDoc API access listed only under Enterprise

PandaDoc’s published plans. Note that API sits in the Enterprise column, under “Let’s talk” pricing, while Starter and Business are metered per seat.

At the API level, PandaDoc supports document creation from a PDF or a pre-built template, recipient groups, and field placement through its field tags reference. Field tags use bracket notation and cover eight types, including textfield (short form t), checkbox (c), signature (s), date (d), initials (i), dropdown (dd), radio (r), and stamp (st). Radio and stamp each carry their own documented limitations, so a radio block has to sit on one page with unique option values, and a stamp field is square with its height derived from its width. The API changelog documents ongoing updates to this surface.

The Supported field types table, with radio and stamp as the two types most roundups miss, each followed by its own limitations section.

For in-app embedding, PandaDoc ships a separate pandadoc.js JavaScript library alongside four official client SDKs covering Python, Node, Java, and PHP. Webhooks fire on document lifecycle events. Branding control is restricted on the lower self-serve tiers.

PandaDoc fits well where the workflow is sales-heavy, covering proposals with approval routing, quotes that require CRM sync, and contracts that evolve through negotiation. That’s a different job than product-embedded signing, which is where the mismatch above comes from.

6 PandaDoc API alternatives

Each of the 6 is scored against the same 7 criteria above, starting with the option built specifically for product-embedded signing.

Foxit eSign API

Foxit eSign’s API is built for the product-embedded use case. Authentication runs over the OAuth 2.0 client-credentials flow against a POST-only, form-encoded token endpoint at POST https://na1.foxitesign.foxit.com/api/oauth2/access_token, and a JSON body to that endpoint returns HTTP 415 because it accepts only application/x-www-form-urlencoded. The regional hosts, per the Foxit eSign developers guide, are na1.foxitesign.foxit.com (US), eu1.foxitesign.foxit.com (EU), na2.foxitesign.foxit.com (CA, not ca1), and au1.foxitesign.foxit.com (AU).

Embedded signing runs through createEmbeddedSigningSession, and the parameter does not work on its own. Setting it to true with nothing else returns {"result": "error", "error_description": "email id of embedded signer(s) not submitted"} and no folder at all, which is the single most common way to lose an afternoon here. You have to pair it with either embeddedSignersEmailIds, an array naming the signers who get a session, or createEmbeddedSigningSessionForAllParties set to true, which covers every party on the envelope. Either pairing returns result: success and a top-level embeddedSigningSessions array whose entries carry emailIdOfSigner, embeddedToken, and embeddedSessionURL, the last of which is what you load in your own iframe. Setting createEmbeddedSigningSessionForAllParties alone, without createEmbeddedSigningSession, is silently ignored, returning success with folderStatus: DRAFT and no sessions.

{
  "folderName": "Customer agreement",
  "inputType": "base64",
  "base64FileString": ["<base64 of your PDF>"],
  "fileNames": ["agreement.pdf"],
  "processTextTags": true,
  "sendNow": false,
  "parties": [
    {
      "firstName": "Jane",
      "lastName": "Smith",
      "emailId": "[email protected]",
      "permission": "FILL_FIELDS_AND_SIGN",
      "sequence": 1
    }
  ],
  "createEmbeddedSigningSession": true,
  "embeddedSignersEmailIds": ["[email protected]"]
}

The body above is the minimum that produces a working session. The one behaviour worth planning around is that folderStatus comes back as SHARED rather than DRAFT even with sendNow: false, because the envelope has to be live for the session URL to open. No email goes out either way, so sendNow: false still means the signer only reaches the document through the URL you hand them.

The embeddedSessionURL opened in a browser. The “Required Fields Left” counter in the header confirms the Text Tags were parsed into interactive fields rather than left as literal text.

Branding is handled by the themeColor parameter, four redirect parameters (signSuccessUrl, signDeclineUrl, signLaterUrl, signErrorUrl), and a long list of visibility toggles that goes well beyond the three most roundups mention. Alongside hideSenderName, hideFolderName, and hideDocumentsName, the developers guide documents hideSignerSelectOption, hideSignerActions, hideAddMeButton, hideAddNewButton, hideAddGroupButton, hideDeclineToSign, hideMoreAction, and hideNextRequiredFieldBtn, among others. Notification emails carry their own path through emailTemplateId, which applies an account-level template with custom content, logo, button colour, and footer. What the guide does not document anywhere is full white-label customization, and there is nothing in it about suppressing Foxit’s own mail delivery in favour of your SMTP server.

The client-library position is a documented REST API with request samples in cURL, PHP cURL, .NET, and Java, plus the official Foxit eSign Postman collection. No language-specific SDK exists. The collection imports cleanly, but two of its request bodies need their JSON repaired before they will run. “Create Envelope from URL” carries "inputType": "{"inputType": "url"}" with unescaped nested quotes, and sending it verbatim against the live sandbox returns HTTP 400 with Unexpected character ('i' (code 105)): was expecting comma to separate OBJECT entries. “Create Envelope from Base64” holds a bare unquoted base64 token inside an array and sets inputType to base64FileString, where the value the API actually accepts is base64. Both are quick fixes, but budget for them rather than assuming the collection runs as downloaded.

Compliance splits across two sources. The Foxit eSign compliance page documents conformance with eIDAS AES and QES (QES applies when used with a qualified trust service provider), ESIGN, UETA, HIPAA, GDPR, 21 CFR Part 11, and CCPA. The Foxit trust center is the source for SOC 2 Type 2 attested operations and for the Business Associate Agreement, which it frames as available where applicable rather than as a blanket offer.

Pricing is consumption and credit-based across the whole Foxit API Platform, which consolidates eSign alongside PDF Services, Document Generation, and PDF Embed on one account and one credit pool. The free Developer plan carries 500 shared credits per year, eSign is metered at 5 credits per envelope, and no credit card is required to open the account. That works out to roughly 100 envelopes on the free tier. Activating eSign provisions a 30-day Business trial in TEST mode, where completed envelopes are watermarked, so plan the production cutover separately from the integration work.

Best fit for teams building compliance-regulated products that need regional data residency, a granular compliance certification list, and an embedding mechanism that keeps signers inside the host application.

DocuSign eSignature API

DocuSign’s eSignature API is the most mature option on this list, with the broadest ecosystem of third-party integrations. Authentication supports OAuth2 authorization code and JWT grants, and the JWT path suits server-side integrations.

Embedded signing uses a recipient view URL returned by the API. The embedded signing guide is explicit that iframes work but come with conditions, stating that they “are not supported with all types of authentication options or SBS pen types” and that “Only full-screen iframes are supported for signers on mobile devices.” The newer focused view only works inside an iframe, and DocuSign’s own JavaScript library builds that iframe in the DOM for you. Client SDKs cover eight platforms, namely C#, Java, Node.js, PHP, Python, Ruby, iOS, and Android. Webhooks fire on a detailed event set including envelope and recipient lifecycle states, and payloads can be verified against a shared HMAC key.

The eight tabs on DocuSign’s SDK page. The two mobile SDKs are easy to miss because the page opens on C# by default.

Compliance certifications include SOC 2 Type 2, ISO 27001, HIPAA (via BAA, plan-dependent), eIDAS, ESIGN, and UETA. FedRAMP Moderate authorization covers eSignature and CLM on a Government Community Cloud deployment, which DocuSign describes as running on “special servers that only house government data.” That is a separate environment from the commercial cloud, so treat it as a distinct procurement path rather than a property of the standard product. See DocuSign eSignature plans for the current plan structure. Pricing is per-seat and per-envelope depending on plan tier.

A free developer account is available with “No obligation, no credit card required.” It runs against the demo environment rather than production, so promoting an integration means a separate go-live review.

The developer account form. The no-credit-card condition sits directly under the submit button, and the account it creates is a demo environment.

Best fit for enterprises with existing DocuSign contracts, teams that need FedRAMP coverage on the government cloud, or projects where the size of the SDK ecosystem reduces integration risk.

Adobe Acrobat Sign API

Adobe Acrobat Sign’s REST API, documented at the Acrobat Sign developer guide, offers OAuth2 authorization code and refresh token flows. Embedded signing redirects the signer to Adobe’s domain by default, though the API does support returning a signing URL you can iframe, and the configuration is less straightforward than dedicated embedded-first APIs.

Adobe does publish SDKs, and the SDK Downloads page names C#, JAVA, JavaScript, OpenAPI, and REST, each with a readme, docs, and a repo download. The more decision-relevant fact is printed on that same page, where Adobe states that “The SDKs are obsolete and haven’t been in sync with REST v6 APIs for a while” and directs developers to the API endpoints and the Postman workspace instead. Treat the SDK list as historical and plan on raw REST calls.

Adobe ships five SDKs and marks them obsolete on the same page. The blue callout is the sentence that should drive your integration plan.

Adobe integrates natively with Microsoft 365, Creative Cloud, and Acrobat. Webhooks fire on the AGREEMENT, WIDGET, and MEGASIGN resource types, scoped at ACCOUNT, GROUP, USER, or RESOURCE level, so bulk-send events are covered alongside agreements and web forms.

Compliance includes SOC 2 Type 2, ISO 27001, FedRAMP Moderate on the Acrobat Sign for Government offering, HIPAA (via BAA, plan-dependent), eIDAS, ESIGN, and UETA. Adobe publishes no standalone Acrobat Sign pricing page reachable through standard navigation, and plan structure varies by Adobe agreement type, so pricing is generally per-seat and negotiated. A free Acrobat Sign Developer Edition account does exist, and the developer guide states that it includes access to the Acrobat Sign API plus testing for document exchange and execution.

Best fit for organizations already running the Adobe enterprise stack, or where native Acrobat and Creative Cloud integration reduces friction for document authors.

Dropbox Sign API

Dropbox Sign (formerly HelloSign) is API-first by design. The SDK overview lists SDKs generated from an OpenAPI spec covering C#, Java, PHP, Python, Ruby, and Node.js. Authentication uses OAuth2 authorization code for user-facing flows and an API key for server-to-server calls.

Embedded signing returns a URL from the API that you load in an <iframe>, and the vendor’s hellosign-embedded JavaScript package manages the iframe lifecycle. Webhooks carry HMAC-SHA256 signatures for payload verification and fire on a full set of signature request lifecycle events.

Compliance covers SOC 2 Type 2, HIPAA (via BAA, plan-dependent), eIDAS, ESIGN, and UETA. Pricing on the Dropbox Sign API product page is volume-based rather than per-seat, with monthly subscription tiers keyed to signature-request count. Essentials starts at $75 per month and 50 requests, Standard at $250 per month and 100 requests, and Premium is custom, with volumes over 500 requests per month routed through sales. The API itself is free in test mode, which is how you validate an integration before committing to a tier.

Dropbox Sign API pricing table with Essentials at $75 a month, Standard at $250 a month, and custom Premium pricing

Dropbox Sign meters the API by signature request volume. No seat count appears anywhere on the API product page.

Best fit for teams that want broad official SDK coverage and a developer-first embedded signing path, particularly where a Dropbox ecosystem connection is already present.

SignNow API

SignNow’s API treats white-labeling as a documented, first-class capability. The white-labeled signing guide covers brand containers created with POST /v2/brands, logo upload through POST /v2/brands/{brand_id}/resources/logo, background and button colour control through PUT /v2/brands/{brand_id}/resources/general, and per-element visibility in the signing editor through PUT /v2/brands/{brand_id}/resources/editor. The same guide tells you not to use SignNow’s default email delivery and to “trigger your own emails using your SMTP server” instead, which is the piece that makes full brand suppression achievable. Authentication is OAuth2 authorization code and password grant, and the SignNow API docs document the full flow.

The brand container and logo endpoints, with the request bodies inline. This is the page that carries the detail, not the white-labeling index.

Embedded signing is iframe-based, generated via POST /v2/documents/{document_id}/embedded-invites. Official SDKs cover PHP, Python, Java, .NET, and JavaScript. Webhooks support document lifecycle events. Compliance includes SOC 2 Type 2, HIPAA, eIDAS, ESIGN, UETA, and GDPR.

Pricing is volume-based, not per-seat. Every tier on the SignNow plans page is marked “Unlimited users” under the headline “Free unlimited users with plans that scale,” with Business at $8 per month, Business Premium at $15, and Enterprise at $30, each including 100 signature invites per year. Full API access sits on the Site License tier, which is priced at $1.50 per signature invite with volume discounts.

SignNow pricing page with unlimited users on all four plans and Site License priced at $1.50 per signature invite

Every SignNow tier carries an “Unlimited users” badge. The Site License column, where full API access lives, is metered per signature invite.

Best fit for SaaS products where the requirement is full brand suppression of the signing vendor, including email notifications and signing UI chrome.

BoldSign API

BoldSign is API-first, developer-first. The BoldSign developer docs document iframe-based embedded signing through a Get Embedded Signing Link API that returns a URL you drop into an <iframe>. Official client libraries cover .NET (C#), PHP, Python, Java, and Node.js. Authentication supports both API key and OAuth2 access token patterns. Webhooks notify on document events.

BoldSign client libraries page listing official SDKs for .NET, PHP, Python, Java, and Node.js

BoldSign’s client libraries page. All five are vendor-maintained, with GitHub links per language.

Compliance documentation covers SOC 2 Type 2, eIDAS, ESIGN, UETA, HIPAA, and GDPR, plus data residency in the US, EU, Canada, and Australia. HIPAA is the one certification that is plan-gated, appearing first on the Business plan and carried upward from there, including on the Enterprise API plan. Everything else in that list is present on every tier.

BoldSign plan table with GDPR, SOC 2, eIDAS, and data residency on every tier and HIPAA from the Business plan up

The compliance block of BoldSign’s plan comparison. HIPAA is the only row with dashes, and they sit on the two cheapest tiers.

The API product is priced per envelope rather than per seat. That distinction matters because BoldSign’s web app tiers are seat-based and the two get conflated. The Enterprise API plan is $0.75 per envelope, starting at $30 per month with 40 envelopes included. A free sandbox gives full API access with no credit card required, with signed documents watermarked and deleted automatically 14 days after creation.

BoldSign Enterprise API pricing at $0.75 per envelope, from $30 a month for 40 envelopes, beside a free sandbox card

BoldSign’s API tier is metered per envelope. The free sandbox card on the right states the no-credit-card condition.

Best fit for teams that want a modern, lower-overhead alternative to DocuSign with a documented embedded signing path and a no-credit-card sandbox to validate before committing.

Shortlisting your options

CriterionFoxit eSignDocuSignAdobe Acrobat SignDropbox SignSignNowBoldSign
In-app embeddingSigner stays in your app. iframe URL from createEmbeddedSigningSession plus embeddedSignersEmailIds or createEmbeddedSigningSessionForAllPartiesSigner stays in your app. Recipient view URL in an iframe, with auth-method and mobile full-screen caveatsRedirect to Adobe’s domain by default. Signing URL can be iframed, with more configurationSigner stays in your app. URL from the API, with hellosign-embedded managing the iframeSigner stays in your app. iframe URL via the embedded-invites endpointSigner stays in your app. iframe URL from the Get Embedded Signing Link API
Auth approachOAuth2 client-credentials, POST-only form-encoded endpointOAuth2 authorization code and JWT grantOAuth2 authorization code and refresh tokenOAuth2 authorization code, with API key for server-to-serverOAuth2 authorization code and password grantAPI key or OAuth2 access token
Webhook granularityEnvelope lifecycle events with HMAC secret-key verificationFull envelope and recipient lifecycle with HMAC verificationAGREEMENT, WIDGET, and MEGASIGN resource types, scoped at account, group, user, or resource levelFull signature request lifecycle with HMAC-SHA256 signatureDocument lifecycle eventsDocument lifecycle events
Client librariesREST API with cURL, PHP cURL, .NET, and Java samples, plus a Postman collection whose two envelope-creation bodies need JSON repair. No language SDKC#, Java, Node.js, PHP, Python, Ruby, iOS, AndroidC#, Java, JavaScript, OpenAPI, REST, all marked obsolete by Adobe and out of sync with REST v6C#, Java, PHP, Python, Ruby, Node.js (OpenAPI-generated)PHP, Python, Java, .NET, JavaScript.NET, Java, Node.js, PHP, Python
Compliance certificationseIDAS AES and QES (QES requires qualified trust service provider), ESIGN, UETA, HIPAA, GDPR, 21 CFR Part 11, CCPA (compliance page), SOC 2 Type 2 and conditional BAA (trust center)SOC 2 Type 2, ISO 27001, eIDAS, ESIGN, UETA, HIPAA via BAA (plan-dependent), FedRAMP Moderate on the Government Community CloudSOC 2 Type 2, ISO 27001, eIDAS, ESIGN, UETA, HIPAA via BAA (plan-dependent), FedRAMP Moderate on Acrobat Sign for GovernmentSOC 2 Type 2, eIDAS, ESIGN, UETA, HIPAA via BAA (plan-dependent)SOC 2 Type 2, eIDAS, ESIGN, UETA, HIPAA, GDPRSOC 2 Type 2, eIDAS, ESIGN, UETA, GDPR, data residency in US, EU, CA, AU. HIPAA from the Business plan upward
Pricing modelConsumption and credit-based, shared across eSign, PDF Services, DocGen, and Embed, at 5 credits per envelopePer-seat and per-envelope (tier-dependent)Per-seat, negotiated. No standalone Acrobat Sign pricing page is publishedVolume-based monthly tiers keyed to signature-request countVolume-based. Unlimited users on every tier, with full API access on Site License at $1.50 per signature invitePer-envelope for the API product at $0.75, from $30 per month with 40 envelopes. Web app tiers are per-seat
Free developer tierYes. Free Developer plan, no credit card, 500 shared credits per year at 5 credits per envelope, roughly 100 envelopes. eSign activation runs 30 days in TEST mode with watermarked envelopesYes. Free developer account, no credit card, demo environment rather than productionYes. Acrobat Sign Developer Edition, free, with API access for testing document exchange and executionYes. The API is free in test mode, with paid tiers required for production trafficYes. Free trial on the self-serve tiers, with full API access gated to the Site License tierYes. Free sandbox, no credit card, full API access. Signed documents are watermarked and deleted after 14 days

Decision path for compliance-regulated apps. Foxit eSign’s two-source compliance posture covers eIDAS AES and QES, HIPAA, GDPR, 21 CFR Part 11, and CCPA on the compliance page, with SOC 2 Type 2 and the conditional BAA on the trust center, giving regulated teams a verifiable certification list alongside regional host selection across US, EU, CA, and AU. If FedRAMP Moderate is a hard requirement, both DocuSign and Adobe carry it on dedicated government cloud offerings, which is a separate procurement path from their commercial products.

Decision path for lowest-friction embedding. Foxit eSign, Dropbox Sign, and BoldSign all return an embeddable URL from the API with minimal configuration. Foxit eSign and BoldSign let you start with no credit card. Dropbox Sign’s hellosign-embedded library handles iframe lifecycle management if you’d rather not manage that directly.

Decision path for teams already on DocuSign. Stay on DocuSign. The JWT grant, the government cloud coverage, and the existing SDK investment represent switching costs that rarely pay back unless a specific compliance delta or pricing model change forces the move.

Decision path for full white-label. SignNow is the documented choice. Its white-labeled signing guide covers brand containers, logo and colour control, per-element visibility in the signing editor, and the instruction to send signing links from your own SMTP server, a surface no other vendor on this list documents to the same depth.

PandaDoc API alternatives FAQ

For Foxit eSign, HIPAA is documented on the Foxit eSign compliance page, and the Foxit trust center states that Foxit is prepared to enter into a Business Associate Agreement where applicable, so the BAA is conditional rather than blanket. BoldSign gates HIPAA to its Business plan and above, which its own plan comparison shows explicitly. Across the market, HIPAA support is usually tied to a specific plan tier and a signed BAA rather than being a platform-wide property, so read the plan table rather than the marketing page.

Yes, through the developer-API sign-up form. No credit card is required, and the account-level pool of 500 shared credits per year (5 per eSign envelope) covers roughly 100 envelopes across the full signing lifecycle before the 30-day TEST-mode trial needs a production decision.

All six alternatives let you start without one, but what the free environment can do varies. DocuSign’s is a demo environment rather than production, BoldSign watermarks and auto-deletes sandbox documents after 14 days, and SignNow reserves full API access for its Site License tier. Check the free-tier row in the table above for each vendor’s specific conditions.

Client-credentials or JWT, since neither requires a human login step at token time. Foxit eSign uses client-credentials; DocuSign supports JWT alongside authorization code. Authorization code grants suit user-facing flows instead.

Conclusion

Choosing among PandaDoc API alternatives comes down to the same seven questions every time, namely whether the signer stays in your app, how the API authenticates, what the webhooks tell you, whether an official client library exists and is current, which certifications are named rather than implied, whether the meter counts seats or envelopes, and how far you can get before a credit card is required. Those answers move. Adobe’s SDKs were current once and are now marked obsolete on Adobe’s own page, SignNow has dropped its seat meter entirely, and a Postman collection can ship with JSON that does not parse. Check the vendor’s live pages rather than a roundup from last year, including this one.

If you want to test a full signing flow today, the Foxit eSign developer account is free, needs no credit card, and gives you 500 shared credits per year across eSign and the rest of the Foxit API platform, which is about 100 envelopes to build against before you decide anything.

How to extract invoice data from PDFs into structured JSON

Diagram showing an invoice PDF flowing through upload, extract, and download steps into a JSON object with vendor and line item fields

Invoice PDFs rarely follow one layout, so generic text extraction breaks on multi-column tables, wrapped cells, and scanned pages. This guide walks through the four API calls that convert a raw invoice PDF into typed JSON, plus the post-processing code that maps the response onto a clean invoice schema your backend can use right away.

Invoice PDFs are structurally unpredictable. Vendor A ships a five-column line-item table, vendor B embeds the same information in a paragraph block, and half the scanned copies in a legacy archive have no text layer at all. Generic text extraction reads characters in the order they were written to the file rather than the reading order a human sees, so field values drift with every layout variation.

The Foxit PDF Structural Extraction API, also known as the PDF Structural Analysis API, addresses this directly. You submit a PDF invoice and receive a typed, hierarchical JSON document with named element types, bounding regions, and an addressable table cell grid. This guide covers the four REST calls that turn a raw PDF into a StructureInfo.json file, the Python post-processing that maps that output onto a clean invoice schema, and the edge cases your pipeline will hit on real vendor documents. Every response shape and field name below comes from a live run against the API, not from the reference docs alone.

Why invoice PDFs break generic parsers

Three structural problems cause most invoice parsing failures, and OCR accuracy is only one of them.

The first is layout variance across vendors. A PDF’s text layer records characters in the order they were drawn, which often follows vector rendering order rather than left-to-right, top-to-bottom reading order. Extract raw text from a five-column line-item table and you frequently get interleaved fragments, where description text from one column mixes with unit prices from another because the writer rendered all rows of one column before moving to the next. No string-parsing logic reliably recovers column boundaries from that flattened sequence.

The second is merged and multi-row cells. Line-item tables routinely span cells across rows for items with multi-line descriptions. Text extraction collapses those cell boundaries into a flat string and drops the row-to-total relationship an accounting system needs.

The third is rasterized scans with no text layer. A PDF created by scanning a paper invoice contains only an embedded image, so anything that reads the text layer alone comes back empty. That kind of file has image data but no searchable text at all. Tools built for scanned input bundle an OCR step rather than skipping it, and any pipeline you build has to do the same before extraction can happen.

Structure-aware extraction addresses all three by classifying document regions into typed elements before exposing their content.

Invoice fields to target before touching the API

Define the target schema before writing code. A concrete target tells you which elements to read from StructureInfo.json and which to skip, which saves iteration time on every invoice you process.

A workable invoice schema covers three groups:

  • Header fields, including vendor name, invoice number, invoice date, due date, and payment terms
  • Line items, a repeating array of description, quantity, unit price, and line total
  • Footer totals, including subtotal, tax amount, and total amount due
{
  "vendor_name": "",
  "invoice_number": "",
  "invoice_date": "",
  "due_date": "",
  "payment_terms": "",
  "line_items": [
    {
      "description": "",
      "quantity": "",
      "unit_price": "",
      "line_total": ""
    }
  ],
  "subtotal": "",
  "tax": "",
  "total_due": ""
}

Keep every value a string at extraction time. Type conversion, currency parsing, and date normalization belong downstream, after validation, where a bad value can be rejected with context instead of raising inside the parser.

The sample invoice used throughout this guide is invoice_full_test.pdf, so you can run every call below against the same document.

Sample invoice PDF with header fields, a line item table, and Subtotal, Tax Rate, and Total Due labels in the second to last column

The input document. Notice that the Subtotal, Tax Rate, Tax Amount, and Total Due labels sit in the second-to-last column rather than the first. That detail determines how the post-processing code has to find them.

Prerequisites

You need the following before the first API call:

Scaffold the workspace in one shot:


mkdir invoice-extraction && cd invoice-extraction && python3 -m venv .venv && source .venv/bin/activate && pip install requests

Then download the sample invoice into that folder:


curl -L -o invoice_full_test.pdf https://github.com/lucienchemaly/foxit-demo-templates/raw/main/invoice_full_test.pdf

Foxit API authentication and setup

Signing up activates a free Developer plan that includes 500 credits per year with no credit card required. A structural extraction call costs one credit, while the upload, polling, and download calls are not billed, so a full run of the workflow below costs a single credit.

The account creation screen. The free Developer plan is enough to work through this entire guide.

Foxit authenticates PDF Services requests with a client ID and client secret passed as HTTP headers, so there is no OAuth token exchange to implement. Both values come from the default application created in your Developer Portal dashboard, alongside the base URL your calls need.

Foxit developer portal panel showing the base URL, client ID, and a masked client secret

The credentials panel. Copy the Client ID and Client Secret into environment variables rather than pasting them into source files.

Export them into your shell so no credential is ever committed:

export FOXIT_CLIENT_ID="your_client_id"
export FOXIT_CLIENT_SECRET="your_client_secret"

The structural extraction reference page carries a Test Request button that fires live calls straight from the browser, which is the quickest way to confirm your credentials work before writing any Python. The endpoint is currently labelled Trial in the reference, so expect its surface to evolve.

Pin your parser to the version field inside the analyzeResult response. The current schema ships as 1.0.7, and pinning prevents silent breakage if that changes.

The four-step PDF to JSON invoice extraction workflow

The API is asynchronous. You upload a document, start a task, poll until the task completes, then download the result.

Four-step diagram of the Foxit structural extraction workflow, from upload through polling to downloading the result

All four paths sit under https://na1.fusion.foxit.com/pdf-services. Calling them without that prefix returns 404.

The path prefix matters more than it looks. The four endpoints live under /pdf-services/api/..., and requesting a bare /documents/{id}/download returns 404 rather than a helpful error.

import io
import json
import os
import time
import zipfile

import requests

BASE_URL = "https://na1.fusion.foxit.com/pdf-services"
HEADERS = {
    "client_id": os.environ["FOXIT_CLIENT_ID"],
    "client_secret": os.environ["FOXIT_CLIENT_SECRET"],
}
POLL_SECONDS = 2
POLL_TIMEOUT = 120


def extract_structure(pdf_path: str) -> dict:
    # Step 1: upload the PDF (multipart/form-data, 100 MB maximum)
    with open(pdf_path, "rb") as handle:
        upload = requests.post(
            f"{BASE_URL}/api/documents/upload",
            headers=HEADERS,
            files={"file": (os.path.basename(pdf_path), handle, "application/pdf")},
        )
    upload.raise_for_status()
    document_id = upload.json()["documentId"]

    # Step 2: start the structural extraction task
    started = requests.post(
        f"{BASE_URL}/api/documents/pdf-structural-extract",
        headers=HEADERS,
        json={"documentId": document_id},
    )
    started.raise_for_status()
    task_id = started.json()["taskId"]

    # Step 3: poll until COMPLETED, bounded, and handle FAILED
    deadline = time.monotonic() + POLL_TIMEOUT
    while True:
        response = requests.get(f"{BASE_URL}/api/tasks/{task_id}", headers=HEADERS)
        response.raise_for_status()
        task = response.json()
        if task["status"] == "COMPLETED":
            break
        if task["status"] == "FAILED":
            raise RuntimeError(f"extraction task {task_id} FAILED: {task}")
        if time.monotonic() > deadline:
            raise TimeoutError(f"task {task_id} stuck at {task['status']}")
        time.sleep(POLL_SECONDS)

    # Step 4: download the result ZIP and read StructureInfo.json
    result = requests.get(
        f"{BASE_URL}/api/documents/{task['resultDocumentId']}/download",
        headers=HEADERS,
    )
    result.raise_for_status()
    with zipfile.ZipFile(io.BytesIO(result.content)) as archive:
        return json.loads(archive.read("StructureInfo.json"))

This code:

  • Reads both credentials from the environment.
  • Uploads the PDF as multipart form data and captures the returned documentId.
  • Hands that id to the extraction endpoint to receive a taskId.
  • Polls the task endpoint every two seconds, bounded by a deadline, checking explicitly for FAILED so a rejected document raises instead of spinning forever.
  • Once the status reads COMPLETED, downloads the ZIP archive using the task’s resultDocumentId and reads StructureInfo.json out of it in memory.
Terminal output showing four API calls succeed and the final parsed invoice JSON with line items and totals

A real run against the sample invoice. The task reports IN_PROGRESS at 20 percent before reaching COMPLETED, and the final object carries every field from the schema defined earlier.

How to map raw output to a clean invoice schema

Choosing an invoice data extraction API is only half the work. The other half is mapping whatever it returns onto a schema your backend already understands, and that mapping is where the shape of the response starts to matter.

StructureInfo.json wraps everything in an analyzeResult object with four top-level keys, version, pages, info, and elements. The elements array is where the work happens. Each element carries a type drawn from twelve values, including paragraph, table, title, image, form, and formula, along with its bounding region and content. The same API can also extract embedded images as standalone image files, in addition to returning them as typed image elements within StructureInfo.json.

Two details in that structure cause most of the bugs in a first implementation, and neither is obvious from the field names.

Annotated JSON showing table cells nested at content.body.cells and text at paragraph.content.text

The actual response shape from a live extraction. A table’s cells are nested at content.body.cells, cell text sits at paragraph.content.text, and region.boundingBox is an eight-number polygon rather than an x, y, width, height rectangle.

A table element does not expose a top-level cells array. Its grid is nested at content.body.cells, where each cell carries rowIndex, columnIndex, and a paragraph object. Cell text then sits one level deeper still, at paragraph.content.text, because content is an object rather than a string. Reaching for cell["paragraph"]["content"] returns a dict, not the text you want.

Blank cells are the second detail. When a vendor leaves a cell empty, the API still returns the cell with its indices and a paragraph object, but that paragraph has no content key at all. An unguarded read raises KeyError partway through a document that looked fine in testing.

def element_text(element: dict) -> str:
    """Return an element's text, or an empty string when it carries none."""
    text = element.get("content", {}).get("text", "")
    return " ".join(text.split())


def cell_text(cell: dict) -> str:
    """Return a table cell's text, or an empty string when the cell is blank."""
    return element_text(cell.get("paragraph", {}))

In this code, element_text reads the nested content.text value and normalizes its whitespace, which matters because cell text can contain a literal \r\n where a label wraps across two lines. Using " ".join(text.split()) collapses those into single spaces, whereas .strip() leaves a mid-string newline untouched. cell_text then reuses that helper for table cells, returning an empty string for a blank cell instead of raising.

With the accessors in place, build a grid and read it row by row.

import re

FOOTER_LABELS = ("subtotal", "tax rate", "tax amount", "tax", "total due", "total")
HEADER_PATTERNS = {
    "vendor_name":    r"bill to:\s*(.+)",
    "invoice_number": r"invoice number:\s*(.+)",
    "invoice_date":   r"invoice date:\s*(.+)",
    "due_date":       r"due date:\s*(.+)",
    "payment_terms":  r"payment is due within (.+?) of",
}


def parse_invoice(structure_info: dict) -> dict:
    elements = structure_info["analyzeResult"]["elements"]
    invoice = {key: "" for key in HEADER_PATTERNS}
    invoice.update(line_items=[], subtotal="", tax="", total_due="")

    # Header fields come from paragraph elements above the table
    for element in elements:
        if element["type"] != "paragraph":
            continue
        text = element_text(element)
        for field, pattern in HEADER_PATTERNS.items():
            match = re.search(pattern, text, re.IGNORECASE)
            if match and not invoice[field]:
                invoice[field] = match.group(1).strip()

    tables = [element for element in elements if element["type"] == "table"]
    if not tables:
        return invoice

    grid: dict = {}
    for cell in tables[0]["content"]["body"]["cells"]:
        grid.setdefault(cell["rowIndex"], {})[cell["columnIndex"]] = cell_text(cell)

    # Resolve columns from the header row instead of assuming positions
    columns = {name.lower(): index for index, name in grid.get(0, {}).items() if name}

    def column_for(*candidates, default):
        for candidate in candidates:
            for name, index in columns.items():
                if candidate in name:
                    return index
        return default

    description_col = column_for("description", "item", default=1)
    quantity_col = column_for("qty", "quantity", default=2)
    unit_price_col = column_for("unit price", default=3)
    line_total_col = column_for("total", "amount", default=4)

    for row_index in sorted(index for index in grid if index > 0):
        row = grid[row_index]
        label = next(
            (value.lower().rstrip(":").strip() for value in row.values()
             if value.lower().rstrip(":").strip() in FOOTER_LABELS),
            None,
        )
        if label:
            value = row[max(row)]
            if label == "subtotal":
                invoice["subtotal"] = value
            elif label == "tax amount":
                invoice["tax"] = value
            elif label in ("total due", "total"):
                invoice["total_due"] = value
            continue
        if row.get(description_col):
            invoice["line_items"].append({
                "description": row.get(description_col, ""),
                "quantity": row.get(quantity_col, ""),
                "unit_price": row.get(unit_price_col, ""),
                "line_total": row.get(line_total_col, ""),
            })

    return invoice

In this code, you first walk the paragraph elements and pull header fields out with labelled regular expressions, which works because Foxit exposes each header line as its own element with its reading order preserved. You then flatten the table into a {rowIndex: {columnIndex: text}} grid and resolve column positions from the header row by name, so a vendor who adds a leading row-number column does not shift every field by one. Each subsequent row is classified before it is read, so that if any cell in the row matches a known footer label the row is treated as a total and its value taken from the last populated column, and otherwise the row becomes a line item. Scanning the whole row for the label is the part that matters, because footer labels do not sit in the first column.

Footer totals living inside the line-item table is convenient rather than awkward, since one pass over the cell grid covers line items and totals together. Form elements do not appear on a typical invoice, so there is no need to look for them.

Before and after

The two blocks below show the same data on either side of that mapping. First, one real cell exactly as the API returns it, taken verbatim from the run above:

{
  "paragraph": {
    "type": "paragraph",
    "content": {
      "text": "API Integration\r\nConsulting"
    },
    "region": {
      "page": 1,
      "boundingBox": [171, 300, 258, 300, 258, 328, 171, 328]
    },
    "id": "paragraph12",
    "paragraphOrder": 12
  },
  "rowSpan": 1,
  "columnSpan": 1,
  "rowIndex": 1,
  "columnIndex": 1,
  "region": {
    "page": 1,
    "boundingBox": [171, 300, 258, 300, 258, 328, 171, 328]
  },
  "score": 0.8555269837379456
}

That fragment is the shape of every cell you will handle. The text is nested at paragraph.content.text rather than sitting directly on the cell. The position arrives as an eight-number boundingBox polygon on both the cell and its paragraph, not as a rectangle. And the value itself contains a literal \r\n where the description wrapped onto a second line in the source table, which is the case .strip() silently fails to clean.

Second, the complete object parse_invoice returns for the whole document, which is what your backend actually consumes:

{
  "vendor_name": "Acme Corporation",
  "invoice_number": "INV-2025-0042",
  "invoice_date": "07/15/2025",
  "due_date": "08/14/2025",
  "payment_terms": "30 days",
  "line_items": [
    {
      "description": "API Integration Consulting",
      "quantity": "8",
      "unit_price": "$ 195.00",
      "line_total": "$1,560.00"
    },
    {
      "description": "Document Automation Setup",
      "quantity": "1",
      "unit_price": "$ 750.00",
      "line_total": "$ 750.00"
    }
  ],
  "subtotal": "$2,310.00",
  "tax": "$ 184.80",
  "total_due": "$2,494.80"
}

The wrapped description has become the single clean string "API Integration Consulting", the header fields have been lifted out of the paragraph elements above the table, and the four footer rows have been separated from the two genuine line items. That output is backend-ready, so you can write it straight to a database, push it to a reporting pipeline, or validate it against an accounts-payable schema without further parsing.

Common mistakes

  • Dropping the path prefix. All four endpoints sit under /pdf-services/api/.... A bare /documents/{id}/download returns 404.
  • Reading cells off the table element. The grid is nested at content.body.cells. A top-level cells lookup raises KeyError.
  • Treating paragraph.content as a string. It is an object, so the text is at paragraph.content.text.
  • Assuming footer labels are in column 0. On real invoices they commonly sit in the second-to-last column, with the value beside them.
  • Forgetting blank cells. A blank cell keeps its paragraph object but carries no content key.
  • Polling without a bound. A FAILED task never becomes COMPLETED, so an unbounded while True loop hangs.
  • Trusting info.basicInfo.elementCounts. It can disagree with the length of the elements array, so size loops from the array itself.
  • Using .strip() to clean cell text. A wrapped label contains a mid-string \r\n that .strip() leaves in place. Use " ".join(text.split()).

Invoice parsing FAQ

Invoice data extraction from PDF is the programmatic conversion of semi-structured PDF invoice content into a schema-typed data object. The source can be a digital-native PDF with an embedded text layer or a scanned image-only PDF that needs OCR first. The output is a structured record, typically JSON, with typed fields for header values, line items, and totals that downstream systems consume without manual parsing.

Line items live in table elements inside StructureInfo.json. Read the grid from content.body.cells, where every cell carries a rowIndex and columnIndex, build a {rowIndex: {columnIndex: text}} dictionary, resolve the column positions from the header row, then read each data row in column order. Classify rows before reading them so footer totals are not appended as line items.

The PDF Structural Extraction API expects a PDF that already has a text layer. Tested against an image-only PDF, it returns no text and an empty table rather than an error. To handle scans, run the document through Foxit’s OCR endpoint first (POST /pdf-services/api/documents/analyze/pdf-ocr with outputFormat set to PDF), then pass the OCR output through the four-step workflow. That makes five calls rather than four, and the OCR step is billed as its own credit.

The downloaded ZIP unpacks to StructureInfo.json, holding an analyzeResult object with version, pages, info, and elements keys. The elements array carries every classified region, each with a type drawn from twelve values. A table element nests its grid at content.body.cells, and each cell’s text sits at paragraph.content.text.

A grid keyed by row and column decouples your field mapping from the order the API happens to return cells in, and it lets you address a specific position directly, which is what the footer-label check needs. It also makes missing cells visible as absent keys rather than as silently shifted values.

The API classifies regions into twelve element types, including paragraph, table, title, image, form, and formula. On a typical invoice, paragraph elements carry the header fields while a single table element carries both line items and footer totals, so filtering by type lets you target only what your schema needs.

Some layouts place totals in a separate table element or in standalone paragraph elements below the main table. Keep the row-classification logic in its own function so you can apply it to a second table element, then fall back to scanning paragraph elements for currency-formatted strings next to known label text.

Wrapping up

The four-step workflow of upload, extract, poll, and download produces a typed, hierarchical JSON document from any digital-native invoice PDF. Post-processing the elements array through a row and column grid turns that into a clean invoice object your backend can consume directly. The same four calls extend to purchase orders, receipts, and any other tabular financial document, with only the mapping logic changing to match the target schema.

The Foxit PDF Structural Extraction API is part of the broader Foxit PDF Services API, which covers conversion, compression, OCR, and other document operations under the same credential set. The infrastructure is SOC 2 Type II certified, with GDPR-supporting features and HIPAA-aligned controls including BAA availability, which matters for teams processing financial documents under compliance review.

The complete script from this guide is available as extract_invoice.py if you want to run it before adapting it.

Create your free developer account and start turning invoice PDFs into structured JSON today. No credit card required, and the 500 credits on the free Developer plan are enough to build and test something real.

How Foxit compares to Google Document AI for document data extraction

Split graphic comparing Google Document AI cloud icons to Foxit OCR, layout, and AI extraction pipeline

Seven Google Document AI processors are being retired in June 2026, pushing teams to look at alternatives. This comparison walks through integration setup, extraction architecture, output schema, and data residency for Foxit’s PDF Structural Extraction API against Google Document AI, so you can decide with real implementation detail instead of a feature list.

Seven Google Document AI processors hit end-of-life on 30 June 2026. Google is retiring the Enterprise Document OCR, Expense, Custom classifier, Custom splitter, Invoice, Pay slip, and Bank statement parsers, and processor versions follow a rolling schedule where each version is deprecated six months after a newer one ships.

For many teams, the deprecation notice does more than prompt a migration ticket. It opens a broader question about whether Google Document AI is still the right foundation for the extraction stack, and what the credible Google Document AI alternatives actually look like once you compare them on implementation detail rather than feature lists.

This article gives you the technical specifics to make that call, comparing the Foxit PDF Structural Extraction API and Google Document AI across five dimensions that drive the real build-vs-switch decision, covering integration overhead, extraction architecture, output schema, document and language coverage, and data residency.

Five axes for evaluating Google Document AI alternatives

Any extraction API comparison lives or dies on the criteria it uses. These five dimensions cover what a production engineering team actually cares about, going well beyond a proof-of-concept benchmark.

Integration complexity measures how many external dependencies you must provision before your first call returns data. A tool that requires a GCP project, service account, IAM role grants, and billing enablement adds meaningful friction before a single byte of document gets processed. For teams with CI/CD pipelines and strict access-control policies, every new cloud dependency is a potential blocker.

Extraction architecture covers how the API reads a document internally, including what happens when a file mixes scanned pages, machine-typed text, and embedded tables in the same document.

Output schema determines how much post-processing your downstream systems require. A flat token list forces you to reconstruct document structure yourself, while a pre-labeled semantic taxonomy reduces that burden before the data reaches your RAG pipeline or BI dashboard.

Document and language coverage sets the practical ceiling on what you can run through the API in production. Language breadth matters especially for multi-region workloads processing invoices or contracts in non-Latin scripts.

Data residency encompasses where documents travel during processing, how long they remain on third-party infrastructure, and what audit evidence you can produce for compliance reviews. For regulated industries, this dimension often decides the question before the others are evaluated.

Integration setup and authentication overhead

Getting to a first call on Google Document AI requires a GCP project, a service account with an IAM role assignment (at minimum roles/documentai.apiUser), billing enablement on the project, and an environment variable pointing to a downloaded service account JSON key. Teams outside the GCP ecosystem absorb all of that as onboarding cost before any extraction runs.

Foxit’s path is shorter. Create a free developer account at the Foxit Developer Portal, retrieve your client_id and client_secret from the default application, and attach them as two HTTP headers on every request. The entire setup takes minutes and requires nothing from GCP.

Prerequisites

To run the code below you need Python 3.8+, the requests library installed into an isolated virtual environment with pip, an editor such as VS Code with the Python extension (PyCharm or Sublime Text work equally well), and a free Foxit developer account from app.developer-api.foxit.com/sign-up to supply the two credential values. Scaffold the workspace in one shot:


mkdir foxit-extract && cd foxit-extract && python3 -m venv .venv && source .venv/bin/activate && pip install requests

Extraction then follows a four-step asynchronous workflow, annotated at each step:

export FOXIT_CLIENT_ID="your_client_id"
export FOXIT_CLIENT_SECRET="your_client_secret"

Extraction then follows a four-step asynchronous workflow, annotated at each step:

import os
import requests
import time

BASE_URL = "https://na1.fusion.foxit.com/pdf-services/api"
HEADERS = {
    "client_id": os.environ["FOXIT_CLIENT_ID"],          # lowercase snake_case, not Authorization: Bearer
    "client_secret": os.environ["FOXIT_CLIENT_SECRET"]
}

# Step 1: Upload the document (multipart/form-data, field name "file", max 100 MB)
with open("contract.pdf", "rb") as f:
    upload_resp = requests.post(
        f"{BASE_URL}/documents/upload",
        headers=HEADERS,
        files={"file": f}
    )
document_id = upload_resp.json()["documentId"]

# Step 2: Start structural extraction
extract_resp = requests.post(
    f"{BASE_URL}/documents/pdf-structural-extract",
    headers=HEADERS,
    json={"documentId": document_id}
)
task_id = extract_resp.json()["taskId"]

# Step 3: Poll every 2 seconds until COMPLETED (statuses are uppercase)
result_doc_id = None
for _ in range(60):
    status_resp = requests.get(
        f"{BASE_URL}/tasks/{task_id}",
        headers=HEADERS
    )
    payload = status_resp.json()
    if payload["status"] == "COMPLETED":
        result_doc_id = payload["resultDocumentId"]
        break
    if payload["status"] == "FAILED":
        raise RuntimeError(f"Extraction failed: {payload}")
    time.sleep(2)

if result_doc_id is None:
    raise TimeoutError("Extraction did not complete within 120 seconds")

# Step 4: Download the ZIP archive containing StructureInfo.json
result_resp = requests.get(
    f"{BASE_URL}/documents/{result_doc_id}/download",
    headers=HEADERS
)
with open("extraction_result.zip", "wb") as out:
    out.write(result_resp.content)

Authentication uses lowercase snake_case header names on every call. The upload endpoint accepts multipart/form-data with the PDF file under the field name file, with a 100 MB size limit per document.

Terminal output showing four curl calls to the Foxit extraction API: upload, extract, poll, and download

 The four calls against the live API. Note the 202 on the extract call and the COMPLETED status before any download is attempted.

The response shape from that run. Every element sits under analyzeResult, and text elements carry region.boundingBox as an eight-number polygon rather than a four-number rectangle. A table element is the exception, since its region comes back empty and its geometry sits in a regions array instead.

The table below puts both platforms side by side on the integration and output dimensions:

DimensionGoogle Document AIFoxit PDF Structural Extraction API
Account setupGCP project, service account, IAM role, billingFree developer account, no credit card
AuthenticationService account JSON key via GOOGLE_APPLICATION_CREDENTIALSclient_id and client_secret as HTTP headers
Call patternSynchronous or async depending on processorFour-step async (upload, extract, poll, download)
Output formatDocument proto (blocks, paragraphs, tokens)StructureInfo.json with 12 semantic element types
Cloud dependencyGCP-native; Vertex AI integration availableCloud-agnostic, any stack
Language coverageVaries by processor200+ languages via dedicated OCR layer

Common mistakes and troubleshooting

Four failure modes account for most of the time lost on a first integration. Only the first one reports itself clearly; the other three surface as exceptions in your own code rather than as API errors.

  • Sending an Authorization: Bearer header : PDF Services authenticates with two separate lowercase headers, client_id and client_secret. There is no token exchange step and no OAuth flow to implement. This is the one mistake the API names outright, returning HTTP 400 with {"allow": false, "reason": "Missing credentials: provide both 'client_id' and 'client_secret' headers."}.
  • Treating the extract call as synchronous : pdf-structural-extract returns HTTP 202 with a taskId, never the result. Read the status from GET /tasks/{taskId}, which also returns a progress percentage, and note that the values are uppercase (PENDING, IN_PROGRESS, COMPLETED, FAILED). Comparing against lowercase strings produces a poll loop that never exits, which is why the sample above also breaks out on FAILED and caps its attempts.
  • Reading region.boundingBox on every element : text elements such as title, head, and paragraph carry region as {page, boundingBox}, but a table element returns an empty region and puts its geometry in a regions array instead. A loop that assumes one shape raises KeyError on the first document containing a table.
  • Indexing elements at the JSON root : every result nests under analyzeResult, so data["elements"] raises KeyError while data["analyzeResult"]["elements"] works. The same applies to uploads above the 100 MB per-document limit, which fail at the upload step rather than during extraction.

Extraction architecture, output format, and document coverage

Google Document AI’s processing model is processor-centric. You select a processor type (Invoice Parser, Form Parser, Document OCR), and the service returns a Document proto containing position-anchored blocks, paragraphs, and tokens. Semantic meaning depends on which processor you deployed, so a Form Parser and an Invoice Parser return structurally similar protos but with different field-level annotations.

Foxit’s PDF Structural Extraction API runs three coordinated layers on every document, regardless of document type. The OCR layer handles rasterized content across more than 200 languages. The layout recognition layer maps spatial relationships and table cell grids, resolving multi-column text blocks, overlapping text-image regions, stamped signatures on top of text fields, and engineering drawing annotations. The AI parsing layer then classifies content semantically, assigning each element a type from a fixed taxonomy of twelve labels.

The rendered pipeline. Every document takes the same path, so there is no processor to choose per document type.

Those twelve types appear in StructureInfo.json inside the returned ZIP archive, covering title, head, paragraph, table, image, headerFooter, form, hyperlink, footnote, sidebar, annotation, and formula. Each element carries its reading-order position and spatial coordinates, so the structure your downstream system receives reflects how a human would read the original document rather than raw storage order.

The practical delta shows up in RAG pipeline integration. Google Document AI’s Document proto gives you text and coordinates, but your pipeline needs a post-processing step to decide what each block means semantically. With StructureInfo.json, you filter to table elements, iterate rows, and pass the content directly to your embedding model, because the semantic classification happened upstream.

The output schema comparison below clarifies where each platform puts the interpretive work:

Schema dimensionGoogle Document AI (Document proto)Foxit (StructureInfo.json)
Structure modelHierarchical, covering pages, blocks, paragraphs, and tokensFlat list of semantically typed elements with spatial metadata
Semantic labelsField-level labels tied to specific processor selection12 fixed element types, processor-independent
Table representationCell tokens within a table blockDedicated table element type with cell grid coordinates
Reading orderImplicit (coordinate ordering required client-side)Explicit, preserved in element sequence
Formula supportLimitedDedicated formula element type

Document coverage on both platforms is broad. Foxit processes scanned PDFs, image-based PDFs, multi-page contracts, invoices, and form-heavy documents. The layout layer handles edge cases that trip up simpler OCR tools, including stamped signatures overlapping text fields, mixed raster-vector pages, and footnote regions that appear spatially disconnected from their reference markers.

Data privacy and ecosystem independence

Regulated workloads ask two questions before any technical evaluation, starting with where the document goes and what audit evidence you can produce.

Foxit’s API compliance page documents SOC 2 Type II independent audit, HIPAA-aligned features with Business Associate Agreement (BAA) support, and GDPR-supporting features including redaction, anonymization, and secure metadata handling. Foxit’s AI service documentation states that input documents and results are held temporarily and deleted within 24 hours. If your organization requires a BAA, Foxit can provide one.

Google Document AI routes all processing through GCP infrastructure. Teams subject to data residency requirements need to select the appropriate GCP region, review Google’s data processing addendum, and confirm their cloud agreement covers the specific data types being processed. That review is standard for teams already operating within GCP, but it adds a compliance surface for teams that are not.

Foxit’s API is cloud-agnostic. Your team calls it from any existing stack, passing two credential headers, and extracts documents without spinning up a GCP project, provisioning a storage bucket, or accepting GCP billing terms. For teams evaluating outside GCP, that independence cuts both technical and commercial risk from the decision.

When to use Google Document AI and when to use Foxit

The right choice depends on your existing infrastructure and what your extraction output needs to do.

ScenarioBest fitKey reason
Teams already deep in GCP who want Vertex AI integrationGoogle Document AINative Vertex AI pipeline support and Google-managed processor versions reduce ops overhead
High-volume PDF processing outside the GCP ecosystemFoxitCloud-agnostic with credit-based pricing, with no GCP billing or IAM dependency
Workloads with strict data residency or BAA requirementsFoxitSOC 2 Type II audit, HIPAA-aligned features with BAA support, and GDPR-supporting features
Teams that need semantic element classification ready for downstream consumptionFoxitStructureInfo.json delivers 12 pre-labeled element types without a client-side post-processing step

The fourth scenario is worth walking through in detail. Your team has a contract review pipeline that needs to extract all tables and footnotes from multi-page PDFs and push them into a downstream system. With the Foxit API, the workflow runs like this:

  1. Upload the PDF to /pdf-services/api/documents/upload and receive a documentId.
  2. POST to /pdf-services/api/documents/pdf-structural-extract with the documentId and receive a taskId.
  3. Poll /pdf-services/api/tasks/{taskId} every two seconds until status equals COMPLETED.
  4. Download the result ZIP from /pdf-services/api/documents/{resultDocumentId}/download and parse StructureInfo.json.

Once you have StructureInfo.json, filtering to table and footnote elements is a single-pass list comprehension. The labeled elements arrive with spatial coordinates and reading order intact, so your downstream system receives structured, ordered data ready for indexing, embedding, or display, with no second model call, no coordinate sorting, and no block-level classification required.

Teams running active Vertex AI pipelines in GCP, with processors not among the seven being retired, have no technical reason to switch. For everyone else, the four API calls above give you a working extraction against your own documents in minutes.

Google Document AI FAQ

Google Document AI is a managed cloud service for document parsing and data extraction, built on GCP processors. You select a processor type (such as Invoice Parser, Form Parser, or Document OCR), send a document via the API, and receive a structured Document proto containing text, coordinates, and field-level annotations. All processing runs on Google Cloud Platform infrastructure.

Foxit’s API runs three coordinated processing layers on every document (OCR, layout recognition, and AI parsing) regardless of document type, while Google Document AI uses a processor model where semantic classification depends on the specific processor you select. The output schemas also differ. Foxit returns StructureInfo.json inside a ZIP archive with twelve pre-labeled element types preserving reading order and spatial relationships, while Google Document AI returns a Document proto with hierarchical blocks, paragraphs, and tokens that require client-side semantic interpretation. Foxit is also cloud-agnostic and needs no GCP project, while Google Document AI is GCP-native.

Foxit’s API compliance page documents SOC 2 Type II independent audit, HIPAA-aligned features with Business Associate Agreement (BAA) support, and GDPR-supporting features including redaction, anonymization, and secure metadata handling. Foxit’s AI service documentation states that input documents and results are held temporarily and deleted within 24 hours. Foxit does not claim HIPAA certification or GDPR certification, and the API compliance page does not state that documents are never stored, so teams with specific retention requirements should review the documentation directly and request a BAA where applicable.

The API returns a ZIP archive containing StructureInfo.json. That file classifies every element in the document using one of twelve labeled types, including title, head, paragraph, table, image, headerFooter, form, hyperlink, footnote, sidebar, annotation, and formula. Each element includes spatial coordinates and reading-order position, so the structure your downstream system receives reflects how a human would read the original document. Table elements carry cell grid coordinates, making row-level data extraction straightforward without additional parsing.

Yes. Create a free developer account at app.developer-api.foxit.com/sign-up. No credit card is required. Once you register, your client_id and client_secret are available immediately in the Developer Portal, and you can run extractions against your own documents using the API Playground or the downloadable Postman collection.

Foxit’s OCR layer processes rasterized content across more than 200 languages, so scanned PDFs and image-based documents (including TIFFs) are processable without pre-conversion. The layout recognition layer then maps spatial relationships and resolves edge cases such as multi-column text blocks, overlapping text-image regions, and footnote regions that appear spatially disconnected from their reference markers.

After 30 June 2026, Google is retiring the Enterprise Document OCR, Expense, Custom classifier, Custom splitter, Invoice, Pay slip, and Bank statement processors. Remaining processors (including Form Parser and Document OCR for non-deprecated versions) continue to operate on their own rolling deprecation schedule, where each version is deprecated six months after a newer one ships. Teams relying on any of the seven retired processors need to migrate before that date.

Conclusion

Across all five axes, the practical difference between Google Document AI alternatives comes down to how much infrastructure you take on to reach a first result. Foxit requires two credential headers. Google’s setup adds a GCP project, service account, IAM configuration, and billing enablement before you process a single document. Foxit’s three-layer extraction model (OCR, layout recognition, AI parsing) delivers semantic classification across every document type without processor selection. StructureInfo.json‘s twelve labeled element types reduce client-side post-processing compared to Google’s Document proto. Both platforms handle the major document types, and Foxit’s 200-plus language OCR covers non-Latin scripts across all document categories. Foxit’s SOC 2 Type II audit, HIPAA-aligned BAA support, and GDPR-supporting features give regulated teams a documented compliance baseline, and the cloud-agnostic model means your team runs document extraction without taking on GCP billing, IAM governance, or ecosystem lock-in.

Create a free developer account at app.developer-api.foxit.com/sign-up, no credit card required, and run the four-step extraction against your own document today.

6 Best DocuSign API Alternatives for Developers in 2026

Illustration of a developer at dual monitors comparing document API dashboards and workflow icons

Comparing a DocuSign API alternative can eat hours of research time. This guide breaks down six eSign APIs, including Dropbox Sign, Adobe Acrobat Sign, PandaDoc, SignNow, BoldSign, and Foxit eSign, against the six criteria that matter most for integration speed and long-term maintenance.

DocuSign’s API works, but redirecting signers to an external DocuSign-hosted page puts a seam in your user experience you can’t fully control, pricing tiers require a sales call to decode, and per-envelope costs escalate unpredictably at scale. If you’ve already decided DocuSign isn’t the right fit, this roundup gives you a structured way to narrow the field fast.

Six alternatives are covered here (Dropbox Sign, Adobe Acrobat Sign, PandaDoc, SignNow, BoldSign, and Foxit eSign), evaluated against six criteria that directly affect integration time and long-term maintainability. Each tool is broken down the same way, so you can compare like against like rather than marketing page against marketing page.

What to evaluate in an eSign API

The six criteria below separate APIs worth building on from ones that will cost you significant refactoring time later. Pin them down before you compare options.

Embedded signing depth. An iframe-based session keeps signers inside your application, while a redirect-based session hands them off to a third-party URL. The delta between “supports embedded signing” and “delivers a fully iframe-native experience” is significant.

Auth model. OAuth2 client credentials gives your backend a machine-to-machine token with no user interaction required. API key auth is simpler but typically coarser in permission scope and harder to rotate safely at scale.

Webhook event granularity. A single “document completed” event isn’t enough if your workflow needs to react to individual signer events, field changes, or expiration triggers. Check what event names are actually documented, not listed on a marketing page.

SDK language coverage. Confirm whether the vendor ships official SDKs for Python, Java, Node.js, and Go. If raw REST calls are your only option, factor in the maintenance overhead for your team.

Compliance certifications. eIDAS, ESIGN, UETA, HIPAA, GDPR, and 21 CFR Part 11 have different requirements. Confirm which certifications are documented and current, not featured in a hero banner.

Pricing model transparency. Envelope-based, seat-based, and consumption-based pricing each carry different risk profiles at scale. If you can’t read the pricing page without talking to sales, add that friction to your evaluation score.

Decision flowchart for evaluating eSign API options, branching to Foxit eSign, BoldSign, SignNow, and others

The six alternatives at a glance

The table summarizes where each tool lands on the criteria above. Treat it as a shortlist filter, then read the section for any tool that survives. Compliance and pricing move often, so the linked pages are the source of truth, not this table.

ToolAuthEmbedded signingWebhooksOfficial SDKsPricing model
Dropbox SignOAuth2 + API keyIframe via sign_urlSigner-level eventsPython, Node, Java, Ruby, PHPAPI tier, published
Adobe Acrobat SignOAuth2Transient docs + widgetsExtensiveJava, plus RESTEnterprise, sales-led
PandaDocOAuth2 + API keyIframe (send + sign)Document lifecycleNode, Python, plus RESTDocument/seat-based
SignNowOAuth2 + API keyEmbedded session URLsFunctional, coarserFewer official SDKsPer-envelope, published
BoldSignOAuth2 + API keyIframe embeddedDocumented events.NET, Java, Node, PythonTiered, transparent
Foxit eSignOAuth2 client credentialsIframe / web view, no redirect9 events incl. folder_executed, HMAC-signedREST, examples in PythonTiered, published

 

Dropbox Sign

Dropbox Sign (formerly HelloSign) runs a clean REST API with solid embedded signing and documentation that developers consistently rate as approachable. If you’re already in the Dropbox ecosystem and don’t need heavy customization, it provides a reliable, well-documented API. But it does have thinner webhook payloads and narrower embedded-UX control than purpose-built full-control APIs.

  • Auth model. OAuth2 for multi-account apps, plus straightforward API key auth for single-account integrations.
  • Embedded signing. Embedded requests return a sign_url you load directly into an iframe, keeping signers in your app.
  • Webhook granularity. Events fire at each signer-level state change, though payloads are less granular than the top-tier options.
  • SDKs. Official SDKs for Python, Node.js, Java, Ruby, and PHP.
  • Compliance. Positioned for general business use; confirm the current certification list in their docs before relying on a specific standard.
  • Pricing model. API pricing is published as its own tier, separate from the end-user product.

See the developer docs and API pricing.

Dropbox Sign API documentation homepage showing guides, SDKs, and API reference navigation

Dropbox Sign’s developer documentation. The organized reference and SDK list are the reason it scores well on the docs-quality criterion.

Adobe Acrobat Sign

Adobe Acrobat Sign brings enterprise-scale infrastructure and a deep compliance footprint to its REST API, at the cost of a larger, more complex surface area. It’s a reasonable fit for large enterprises already standardized on Adobe Document Cloud with compliance needs that benefit from Adobe’s footprint. On the other side, it also has the largest integration overhead on this list and pricing that is not self-serve.

  • Auth model. OAuth2, integrated with Adobe’s broader identity and Document Cloud platform.
  • Embedded signing. Supported via Transient Documents and widget-based flows rather than a single drop-in iframe call.
  • Webhook granularity. Extensive event coverage, appropriate for large multi-step enterprise workflows.
  • SDKs. An official Java SDK plus a broad REST surface; other languages typically integrate at the REST level.
  • Compliance. Strong certification footprint aimed at healthcare, finance, and government; verify the specifics for your regulatory context.
  • Pricing model. Enterprise-tier and sales-led; expect a conversation rather than a public per-call rate.

See the developer guide and Adobe’s Acrobat business pricing page.

Adobe Acrobat Sign API overview page in the Adobe Developer documentation site

The Acrobat Sign API overview. The breadth here is the point, and also the integration-overhead warning.

PandaDoc

PandaDoc’s API sits closer to document-creation-plus-signing than pure eSignature, which is a strength if you need both from one integration. It works well for building quote-to-sign or proposal-to-signature workflows where generation and signing happen through one API. The tradeoffs are document-based pricing that adds up at volume, and thinner signing-order controls than purpose-built eSign APIs.

  • Auth model. OAuth2 and API key options.
  • Embedded signing. Embedded sending and signing via iframe, alongside template-driven document generation.
  • Webhook granularity. Document-lifecycle events covering creation, sending, and completion.
  • SDKs. Official Node.js and Python SDKs, plus a documented REST API.
  • Compliance. Business-grade; confirm the current list against your requirements in their docs.
  • Pricing model. Document-centric and can climb at high envelope volumes, so model your cost at target scale.

See the developer documentation and pricing.

PandaDoc’s developer hub. Document generation sitting next to signing is what distinguishes it from pure eSign APIs.

SignNow

SignNow offers a capable REST API that is frequently competitive on per-envelope cost at volume, which makes it a common pick for high-throughput, straightforward signing. It’s a cost-sensitive option that can handle high-volume, straightforward signing if you don’t have any complex embedded-UX requirements. However, its coarser webhooks and narrower SDK coverage will push you toward raw REST.

  • Auth model. API key and OAuth2 authentication.
  • Embedded signing. Embedded session URL generation for in-app signing.
  • Webhook granularity. Functional, but event types are coarser than the top-tier options.
  • SDKs. Narrower official SDK coverage, so expect more raw REST work outside the main supported languages.
  • Compliance. Business and industry compliance is advertised; verify the current certifications in their docs.
  • Pricing model. Per-envelope pricing that is published and tends to reward volume.

See the API documentation and pricing.

SignNow’s REST API documentation. Envelope creation, signer routing, and embedded session URLs are all covered here.

 

BoldSign

BoldSign is a developer-first eSign API with a clean REST interface and pricing that is more transparent than most enterprise alternatives at the lower tiers. This option offers a modern, well-documented API with straightforward pricing, outside the most heavily regulated industries. Worth noting is a smaller compliance footprint than the established players.

  • Auth model. OAuth2 and API key options.
  • Embedded signing. Solid iframe-based embedded signing built for developer integration.
  • Webhook granularity. Documented event types suitable for most integration workflows.
  • SDKs. Official SDKs including .NET, Java, Node.js, and Python.
  • Compliance. A newer entrant with a smaller certification footprint than established players, so verify current standards before committing in a regulated industry.
  • Pricing model. Tiered and transparent, published without a mandatory sales call at the lower tiers.
BoldSign Developer Hub homepage with sandbox access and eSignature API documentation links

The BoldSign Developer Hub. Transparent pricing and a self-serve sandbox are its main developer-experience draws.

 

Foxit eSign

Foxit eSign gives developers full control over the signing experience, with no redirect to an external Foxit-hosted page at any point. This is the tool covered in the most technical depth here, and the getting-started section below runs against its live API.

  • Auth model. OAuth2 client credentials. Your backend gets a machine-to-machine Bearer token with no user login in the loop.
  • Embedded signing. Signing sessions load inside an iframe or web view within your own application. You control the header, sidebars, and the exact page signers land on after finishing.
  • Webhook granularity. Nine event types, including folder_executed, and every callback is signed with an HMAC-SHA-256 digest of the raw body so you can verify authenticity.
  • SDKs. A documented REST API with worked examples; the getting-started code below is Python.
  • Compliance. eIDAS at the AES and QES levels (QES requires pairing with a qualified trust service provider), plus the ESIGN Act, UETA, HIPAA, GDPR, 21 CFR Part 11, CCPA, FINRA, FERPA, and SOC 2 Type II infrastructure.
  • Pricing model. Tiered and published, with a free developer account to build against.
Foxit eSign embedded signing session showing a Service Agreement with interactive signature fields

The Foxit eSign embedded signing session, loaded in-app. Text tags in the source PDF are parsed into the interactive Full name, initial, date, and signature fields shown here, with no redirect to an external page.

Signing order control

Setting signInSequence to false on a folder request puts all recipients into parallel mode, while leaving it true enforces the sequence you define. Hybrid flows mix both, so some signers proceed in parallel while others wait on prior steps.

Compliance coverage

Full details live at the Foxit compliance page and the Foxit Trust Center.

Best for development teams that need full embedded-signing control, flexible signer routing, and broad compliance coverage from a single integration, especially in regulated industries.
Watch out for planning your regional endpoint (the instance_url returned at auth) into your configuration rather than hardcoding one.

Getting started with the Foxit eSign API

Three steps take you from zero to your first signed document. The code below runs end to end against the live API.

The Foxit eSign dashboard you land on after signing in. The API you are about to call drives the same envelopes shown here.

Step 1. Activate the API tab. Log into your Foxit eSign account, navigate to Settings, and open the API tab. Fill out the form to receive your client_id and client_secret.

The API Consumer Credentials screen. Foxit masks the client id, secret, and access token by default and requires a one-time passcode to reveal them, which is the credential-handling behavior worth knowing before you build.

Step 2. Obtain a Bearer token. POST to the regional OAuth2 endpoint with a form-encoded body (sending a JSON body returns HTTP 415 Unsupported Media Type). The response includes access_token, token_type, expires_in, and instance_url.

Step 3. Create an envelope and register a webhook. Use the Bearer token in the Authorization header to create an envelope (Foxit calls it a folder) from a source document, then register a webhook targeting the folder_executed (EXECUTED) event to be notified once every party has signed and the document is executed.

The code reads credentials from environment variables. It exchanges credentials for a token, then creates a draft envelope from a sample PDF without emailing anyone.

import os
import requests

# The token endpoint requires a form-encoded body
# (application/x-www-form-urlencoded). Sending a JSON body returns
# HTTP 415 Unsupported Media Type.
TOKEN_URL = "https://na1.foxitesign.foxit.com/api/oauth2/access_token"

# Step 1: Exchange credentials for a Bearer token.
token_response = requests.post(
    TOKEN_URL,
    data={  # use data= (form-encoded), NOT json=
        "grant_type": "client_credentials",
        "client_id": os.environ["FOXIT_ESIGN_CLIENT_ID"],
        "client_secret": os.environ["FOXIT_ESIGN_CLIENT_SECRET"],
        "scope": "read-write",
    },
)
token_response.raise_for_status()
token_data = token_response.json()

access_token = token_data["access_token"]     # Bearer token
instance_url = token_data["instance_url"]      # already a full URL, e.g. https://na1.foxitesign.foxit.com/
base = instance_url.rstrip("/")                # trim trailing slash; do NOT re-add https://

# Step 2: Create an envelope (folder) from a document.
# sendNow=False creates a DRAFT and emails no one.
headers = {
    "Authorization": f"Bearer {access_token}",
    "Content-Type": "application/json",
}
payload = {
    "folderName": "Service Agreement",
    "fileUrls": ["https://your-app.example.com/agreement.pdf"],
    "fileNames": ["agreement.pdf"],
    "sendNow": False,                          # DRAFT; set True to dispatch to signers
    # signInSequence True  = signers proceed in the sequence you define
    # signInSequence False = all signers receive the document in parallel
    "signInSequence": True,
    "parties": [
        {
            "firstName": "Jane",
            "lastName": "Doe",
            "emailId": "[email protected]",
            "sequence": 1,
        }
    ],
}
resp = requests.post(f"{base}/api/folders/createfolder", headers=headers, json=payload)
resp.raise_for_status()
folder_id = resp.json()["folder"]["folderId"]  # the id nests under "folder"
print("Envelope created:", folder_id)

The code above reads your client id and secret from the environment, POSTs them form-encoded to the regional token endpoint, and reads the access_token and instance_url from the response. Because instance_url is already a complete URL, you strip its trailing slash and use it directly rather than prepending a scheme. You then POST to /api/folders/createfolder with the source document, the signing parties, and sendNow set to false so the envelope is created as a draft without notifying anyone, and read the new folder’s id from folder.folderId in the response.

Registering the executed webhook

You register webhooks once in the eSign portal’s API settings, not through the token flow above. Add your endpoint URL and a webhook secret, then enable the events you care about. The screenshot below shows the real configuration screen with folder_executed selected.

Foxit eSign Configure Webhooks screen showing URL, secret field, and event checkboxes including folder_executed

The Configure Webhooks page. Note the nine event checkboxes, folder_executed selected, and the field note confirming each request is signed with a Base64 HMAC-SHA-256 digest of the raw body.

folder_executed fires when all parties have signed and the document reaches the EXECUTED state, which is the point at which the completed, legally binding PDF is ready to archive. Foxit delivers each event as an HTTP POST with the signature appended as a query parameter (?signature=...), where the signature is the Base64-encoded HMAC-SHA-256 of the raw request body keyed with your webhook secret. Recompute that digest on receipt and compare it before you act on the callback.

import base64, hashlib, hmac

def verify_webhook(raw_body: bytes, signature: str, secret: str) -> bool:
    """Return True only if the callback's signature matches the raw body."""
    expected = base64.b64encode(
        hmac.new(secret.encode(), raw_body, hashlib.sha256).digest()
    ).decode()
    return hmac.compare_digest(expected, signature)

The full API reference is at developersguide.foxitesign.foxit.com.

Docusign API FAQ

An eSignature API lets your application send, track, and complete legally binding signature requests without building a separate signing portal. Your app calls the endpoints to create the workflow, route the document to signers, capture their signatures, and pull the completed, audit-ready record, all without anyone leaving your product.

Six strong alternatives worth evaluating are Dropbox Sign (well-documented, fits the Dropbox ecosystem), Adobe Acrobat Sign (enterprise compliance depth), PandaDoc (proposal-to-signature workflows), SignNow (cost-effective at volume), BoldSign (developer-first, clean REST interface), and Foxit eSign (full embedded control and multi-standard compliance across eIDAS, ESIGN, UETA, HIPAA, GDPR, and 21 CFR Part 11).

Yes. Signing sessions load inside an iframe or web view within your own application, with no redirect to an external Foxit page. You can customize headers and sidebars, and configure where signers land after completing the document. The experience stays inside your product throughout.

Foxit eSign meets eIDAS at the AES (Advanced Electronic Signature) and QES (Qualified Electronic Signature) levels (QES requires pairing with a qualified trust service provider). Additional certifications include the ESIGN Act, UETA, HIPAA, GDPR, 21 CFR Part 11, CCPA, FINRA, FERPA, and SOC 2 Type II infrastructure. Full details are at the Foxit compliance page.

Create an account at developer-api.foxit.com, activate the API tab in your eSign account settings to receive your client_id and client_secret, then POST to https://na1.foxitesign.foxit.com/api/oauth2/access_token with a form-encoded body to obtain a Bearer token. Use that token in the Authorization header to make your first envelope call.

Your backend POSTs a form-encoded request to the regional token endpoint using your client_id and client_secret. The response returns a Bearer token with a defined expiry and a region-specific instance_url. Every subsequent API call passes that token in the Authorization header, with no session-level user login required at any point.

You register webhooks through the eSign API Settings page and configure them to fire on specific event types, nine in total, spanning sent, viewed, signed, cancelled, executed, deleted, completed, assigned, and access-code-failure. The folder_executed event fires when all parties have completed signing. Foxit signs each webhook POST with an HMAC-SHA-256 digest of the raw body, giving you a verifiable authenticity signal on every inbound notification.

Setting signInSequence to false on a folder request sends the document to all recipients simultaneously. Setting it to true enforces the order you define in the sequence field on each party. Hybrid flows combine both modes, so some signers proceed in parallel while others wait on prior steps, all from a single API call.

Foxit eSign covers the broadest compliance footprint across eIDAS (AES and QES), HIPAA, GDPR, 21 CFR Part 11, FINRA, FERPA, CCPA, and SOC 2 Type II. Adobe Acrobat Sign also carries strong enterprise certifications relevant to healthcare and finance. Teams in regulated industries should verify current certification status directly with each vendor before committing.

Envelope-based pricing charges a fixed fee per document sent for signature, which makes costs predictable at low volumes but expensive at scale. Consumption-based pricing ties costs to actual usage metrics (API calls, active users, or data volume), which can reduce spend at high volume but introduces budget variability. Seat-based pricing charges per licensed user regardless of volume, which suits teams with consistent, predictable signing activity.

Picking the right eSign API

The six criteria (embedded signing depth, auth model, webhook event granularity, SDK language coverage, compliance certifications, and pricing transparency) cut the field quickly when you apply them consistently.

Dropbox Sign fits teams in the Dropbox ecosystem who want reliable coverage without high complexity. Adobe Acrobat Sign suits large enterprises with existing Adobe infrastructure and deep compliance footprints. PandaDoc is the right call for proposal-to-signature workflows. SignNow works for cost-sensitive, high-volume signing at scale. BoldSign offers a clean developer experience outside the most heavily regulated industries.

If you need full embedded-signing control, flexible signer routing, and compliance coverage across eIDAS, HIPAA, GDPR, and 21 CFR Part 11 bundled without piecing those certifications together from add-ons, Foxit eSign is worth a close look. Once a folder reaches the EXECUTED state, the completed document carries a full audit trail and signer certificate, which is what you archive as the legal record.

An executed Foxit eSign agreement. The EXECUTED state is the archival point the folder_executed webhook announces.

Visit developer-api.foxit.com to create a free developer account and try the full signature flow with no commitment.

How to Turn PDFs into Structured Data with Foxit’s PDF Structural Extraction API

Diagram showing pdf data extraction turning an invoice into a JSON file with typed elements and table cells

PDF data extraction with Foxit’s Structural Extraction API turns messy invoices and tables into typed JSON, complete with bounding regions and addressable cells. This tutorial walks through the four REST calls, upload, extract, poll, and download, and shows working Python code that builds a clean dictionary from an invoice’s line items. It also covers common mistakes like case-sensitive auth headers and stale document IDs.

Pull text out of a multi-column invoice and you get a flat string with column headers mixed into values, row boundaries gone, and field labels indistinguishable from the data they describe. Foxit’s PDF Structural Extraction API returns typed JSON instead, where every element carries a type, its text, a bounding region, and, for tables, an addressable grid of cells.

This tutorial walks the four REST calls that get you there, uploading a PDF, starting the analysis, polling the task, and downloading the result. By the end you’ll have working Python code that turns an invoice into a dictionary your pipeline can address by key.

Raw text vs. structured extraction

What separates raw text extraction from structured extraction is the shape of the output, not the accuracy of the characters.

Take a vendor invoice with a line-item table covering description, quantity, and unit price. Text extraction returns something like "1 API Integration Consulting 10 $ 150.00 $1,500.00". The content is all there, but the row and column relationships are gone, so your parsing code has to reconstruct structure the PDF already encoded, and it has to do that differently for every layout you encounter.

Structured extraction preserves what raw text discards. The pdf-structural-extract endpoint returns each element with a type, a content object holding the text and its font styling, and a region giving the page number and bounding polygon. Tables come back as a cell grid with explicit rowIndex and columnIndex values, so a cell’s position is data rather than something you infer from coordinates.

Prerequisites

Scaffold the workspace in one shot:

mkdir foxit-extract && cd foxit-extract
python3 -m venv .venv && source .venv/bin/activate
pip install requests
curl -L -o invoice.pdf https://github.com/lucienchemaly/foxit-demo-templates/raw/main/invoice_table_test.pdf
export FOXIT_CLIENT_ID="your_client_id"
export FOXIT_CLIENT_SECRET="your_client_secret"

Here is the invoice the rest of the tutorial extracts from.

Sample invoice PDF with billing details and a five column line item table used for pdf data extraction

The source document. The five-column table and the labeled fields above it are what the extraction turns into addressable JSON.

Authentication

PDF Services authenticates with two named request headers on every call, client_id and client_secret, both lowercase with an underscore. There is no OAuth exchange and no bearer token, and wrapping the credentials in an Authorization: Bearer header returns a 400 instead. The base host for every endpoint in this tutorial is https://na1.fusion.foxit.com/pdf-services.

Keep the values in environment variables rather than in the file, so nothing secret travels with your code.

The four-call extraction flow

Structural extraction is an asynchronous job, so it runs in four steps.

Flow diagram of four API calls for pdf data extraction: upload, extract, poll, and download JSON

The four calls and what each one hands to the next. The id you download with comes from the finished task, not the upload.

  1. Upload the PDF and receive a documentId.
  2. Start the analysis against that id and receive a taskId.
  3. Poll the task until its status reaches COMPLETED, which also returns a resultDocumentId.
  4. Download the result, a ZIP archive holding the structured JSON.

Step 1: Upload the document

Send the PDF as multipart/form-data to the upload endpoint, using the form field name file.

curl -X POST "https://na1.fusion.foxit.com/pdf-services/api/documents/upload" \
  -H "client_id: $FOXIT_CLIENT_ID" \
  -H "client_secret: $FOXIT_CLIENT_SECRET" \
  -F "[email protected]"

The -F flag is what makes curl send a multipart body, and the @ prefix tells it to read the file from disk rather than treat the value as a literal string. Uploads are capped at 100 MB, and an uploaded document is deleted after 24 hours, so treat the documentId as short-lived rather than a permanent handle.

A successful upload returns a single key:

{
  "documentId": "6a6c9834a820c33d30d222e5"
}

Step 2: Start the structural analysis

POST that id to the extraction endpoint with a JSON body.

curl -X POST "https://na1.fusion.foxit.com/pdf-services/api/documents/pdf-structural-extract" \
  -H "client_id: $FOXIT_CLIENT_ID" \
  -H "client_secret: $FOXIT_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"documentId": "6a6c9834a820c33d30d222e5"}'

The call returns HTTP 202 with a taskId rather than the finished document, since analysis runs asynchronously. documentId is the only required field in the body, and a password-protected source PDF takes an optional password alongside it. Full request and response details live in the PDF Structural Extraction reference, which also carries the endpoint’s Trial designation, so pin the schema version you parse against rather than assuming it is stable.

{
  "taskId": "6a6c9835d24a2429666f61b6"
}

Step 3: Poll the task

Ask for the task by id until it finishes.

curl "https://na1.fusion.foxit.com/pdf-services/api/tasks/6a6c9835d24a2429666f61b6" \
  -H "client_id: $FOXIT_CLIENT_ID" \
  -H "client_secret: $FOXIT_CLIENT_SECRET"

The response carries the state, a percentage, and, once the work is done, the id of the result document:

{
  "taskId": "6a6c9835d24a2429666f61b6",
  "status": "COMPLETED",
  "progress": 100,
  "resultDocumentId": "6a6c98375e2cab6bb50e740b"
}

Task statuses are uppercase. The schema enum runs PENDING, IN_PROGRESS, COMPLETED, and FAILED, so a comparison against a lowercase "completed" never matches and your loop spins until it times out. Portal copy sometimes says “processing” in prose, but IN_PROGRESS is the value on the wire.

Step 4: Download the result

Fetch the finished artifact using the resultDocumentId from the poll, not the documentId from the upload. Confusing the two is the most common 4xx at this step.

curl -o extract.zip \
  "https://na1.fusion.foxit.com/pdf-services/api/documents/6a6c98375e2cab6bb50e740b/download" \
  -H "client_id: $FOXIT_CLIENT_ID" \
  -H "client_secret: $FOXIT_CLIENT_SECRET"

The response comes back as application/zip. Unzipping it gives you StructureInfo.json, the structured output, alongside a rendered PNG of each analyzed page (page_p0.pdf_0.png for a one-page file).

The whole flow in Python

Here is the complete script, reading credentials from the environment.

import os
import time
import zipfile
import json
import requests

BASE = "https://na1.fusion.foxit.com/pdf-services/api"
HEADERS = {
    "client_id": os.environ["FOXIT_CLIENT_ID"],
    "client_secret": os.environ["FOXIT_CLIENT_SECRET"],
}


def upload(path):
    with open(path, "rb") as fh:
        r = requests.post(f"{BASE}/documents/upload", headers=HEADERS, files={"file": fh})
    r.raise_for_status()
    return r.json()["documentId"]


def start_extract(document_id):
    r = requests.post(
        f"{BASE}/documents/pdf-structural-extract",
        headers={**HEADERS, "Content-Type": "application/json"},
        json={"documentId": document_id},
    )
    r.raise_for_status()
    return r.json()["taskId"]


def wait_for_task(task_id, interval=3, timeout=180):
    deadline = time.time() + timeout
    while time.time() < deadline:
        r = requests.get(f"{BASE}/tasks/{task_id}", headers=HEADERS)
        r.raise_for_status()
        body = r.json()
        if body["status"] == "COMPLETED":
            return body["resultDocumentId"]
        if body["status"] == "FAILED":
            raise RuntimeError(f"Extraction failed: {body.get('error')}")
        time.sleep(interval)
    raise TimeoutError(f"Task {task_id} unfinished after {timeout}s")


def download_zip(result_id, out="extract.zip"):
    r = requests.get(f"{BASE}/documents/{result_id}/download", headers=HEADERS)
    r.raise_for_status()
    with open(out, "wb") as fh:
        fh.write(r.content)
    return out


document_id = upload("invoice.pdf")
result_id = wait_for_task(start_extract(document_id))
archive = download_zip(result_id)

with zipfile.ZipFile(archive) as z:
    structure = json.loads(z.read("StructureInfo.json"))

analyze = structure["analyzeResult"]
print("schema", analyze["version"]["schema"], "pages", len(analyze["pages"]))

In this code, you upload the invoice and keep the returned documentId, hand that id to the extraction endpoint to get a taskId, then poll the task on a fixed interval until it reports COMPLETED and yields a resultDocumentId. The download call writes the ZIP to disk, and rather than unpacking it to a folder you read StructureInfo.json straight out of the archive. The top-level key is analyzeResult, which is where the schema version, the page list, and the element array all live.

A three-second interval with a 180-second ceiling is comfortable for single-page documents. Back off rather than tightening the loop if you process long files, since polling every second only burns request budget without finishing the job sooner.

Reading the structured JSON

analyzeResult holds four things worth knowing about, an info block of document metadata, a version block, the pages array, and the elements array that carries the content.

{
  "analyzeResult": {
    "version": {
      "schema": "1.0.7",
      "software": "FoxitPDFAnalyzer",
      "model": "idp-analysis"
    },
    "pages": [
      { "pageNumber": 1, "size": {}, "state": {} }
    ],
    "elements": [
      {
        "type": "title",
        "content": {
          "text": "INVOICE",
          "style": { "fontFamilyName": "Arial", "fontSize": 24.0 }
        },
        "region": {
          "page": 1,
          "boundingBox": [90, 71, 189, 71, 189, 99, 90, 99]
        },
        "score": 0.88,
        "id": "title1"
      }
    ]
  }
}

Each element follows the same shape. The type classifies it, and extracting this invoice returns title, head, paragraph, and table. The schema defines a wider set, adding image, headerFooter, form, hyperlink, footnote, sidebar, annotation, and formula, so branch on the types your documents actually produce rather than assuming only four exist. The text and its font styling sit under content, so you read content.text rather than a top-level text key. The region gives the one-based page plus a boundingBox, and that box is an eight-number polygon listing four corner pairs in order, not a four-number rectangle. Every element also carries a confidence score and a stable id such as title1 or paragraph2, and paragraphs additionally carry a paragraphOrder for reading sequence.

Tables are the interesting case. Rather than a headers array and a two-dimensional rows array, a table exposes a cell list under content.body:

{
  "type": "table",
  "content": {
    "body": {
      "rowCount": 4,
      "columnCount": 5,
      "cells": [
        {
          "paragraph": { "type": "paragraph", "content": { "text": "Description" } },
          "rowSpan": 1,
          "columnSpan": 1,
          "rowIndex": 0,
          "columnIndex": 1
        }
      ]
    }
  }
}

Each cell states its own rowIndex and columnIndex along with rowSpan and columnSpan, and its text lives at paragraph.content.text. That is more verbose than a plain grid, but it means merged cells stay describable and you never have to infer column membership from x-coordinates.

Turning the cell list into rows

Since the API hands you cells rather than rows, build the grid yourself once and work with it afterwards.

def table_to_grid(table):
    body = table["content"]["body"]
    grid = [["" for _ in range(body["columnCount"])] for _ in range(body["rowCount"])]
    for cell in body["cells"]:
        text = cell.get("paragraph", {}).get("content", {}).get("text", "")
        grid[cell["rowIndex"]][cell["columnIndex"]] = text.replace("\r\n", " ")
    return grid


tables = [e for e in analyze["elements"] if e["type"] == "table"]
header, *data_rows = table_to_grid(tables[0])

line_items = [dict(zip(header, row)) for row in data_rows]
text_blocks = {
    e["id"]: e["content"].get("text", "")
    for e in analyze["elements"]
    if e["type"] in ("title", "head", "paragraph")
}

print(header)
for item in line_items:
    print(item)

The code above allocates an empty grid from rowCount and columnCount, then drops each cell’s text into its stated position, which sidesteps any assumption about cell ordering in the array. Cell text can contain literal \r\n where a label wraps inside its column, so the replace call flattens that to a space before it reaches your data layer. Splitting the first row off as the header lets you zip each remaining row into a dictionary keyed by column name, and the same comprehension pattern collects the title, heading, and paragraph text by element id.

Running it against the sample invoice prints the real extraction:

['#', 'Description', 'Qty', 'Unit Price', 'Line Total']
{'#': '1', 'Description': 'API Integration Consulting', 'Qty': '10', 'Unit Price': '$ 150.00', 'Line Total': '$1,500.00'}
{'#': '2', 'Description': 'Compliance Review', 'Qty': '5', 'Unit Price': '$ 200.00', 'Line Total': '$1,000.00'}
{'#': '', 'Description': '', 'Qty': '', 'Unit Price': 'Subtotal:', 'Line Total': '$2,500.00'}

Two things in that output are worth designing around. The unit price arrives as the string "$ 150.00", so currency parsing is still your job, and the final row is a subtotal rather than a line item, which is a reminder that the analyzer reports table geometry rather than business meaning. Filter trailing rows on an empty # or Description before you treat them as products. If you want to inspect a full result without running the calls yourself, the StructureInfo_sample.json from this exact run is available to read.

Feeding the output into an agent or downstream workflow

Once the table is a list of dictionaries and the labeled text is keyed by id, the payload is already agent-ready.

agent_context = {
    "document": {"schema": analyze["version"]["schema"], "pages": len(analyze["pages"])},
    "text_blocks": text_blocks,
    "line_items": line_items,
}

Because every element also carries a region, you can layer spatial checks on top, such as confirming a table sits below a particular heading by comparing the y values in their bounding polygons before you trust the association. Foxit also publishes an MCP server for PDF Services, so the same operations are reachable from an agent that speaks the Model Context Protocol rather than raw HTTP.

Common mistakes

  • PascalCase auth headers : the keys are lowercase client_id and client_secret. ClientId and ClientSecret do not authenticate, and neither does an Authorization: Bearer header, which returns a 400.
  • Comparing status to a lowercase string : task statuses are uppercase, so test against COMPLETED and FAILED.
  • Downloading with the upload id : the download path takes the resultDocumentId from the completed task, not the documentId from the upload.
  • Reading elements from the root : the array is nested under analyzeResult, so structure["analyzeResult"]["elements"] is the path.
  • Expecting bbox or a rows array : positions arrive as region.boundingBox with eight numbers, and tables arrive as content.body.cells with index fields rather than a headers plus rows pair.
  • Treating the ZIP as the JSON : the download is an archive, and the structured output is the StructureInfo.json entry inside it.
  • Reusing a stale documentId : uploads are removed after 24 hours, and the cap on an upload is 100 MB.
  • Polling every second : that exhausts request budget without speeding anything up. A few seconds between checks is enough.

PDF data extraction FAQ

Extracting this invoice produces title, head, paragraph, and table elements. Every element carries type, content, region, score, and id, with tables adding a cell grid under content.body.

Raw text collapses a table into one string and loses row and column boundaries. Structural extraction reports rowCount, columnCount, and a cell list where each cell states its own rowIndex and columnIndex, so position is data rather than inference.

No. The extract call returns HTTP 202 with a taskId, and you poll GET /pdf-services/api/tasks/{taskId} until the status reaches COMPLETED, which is when resultDocumentId appears.

A ZIP archive holding StructureInfo.json plus a rendered PNG per analyzed page. The JSON is the structured output and the PNG is useful for visual spot checks.

The sample run reports analyzeResult.version.schema of 1.0.7, produced by FoxitPDFAnalyzer with the idp-analysis model. Read the version from the payload rather than hardcoding it, since it can move.

The task object surfaces the failure state in status as FAILED, so branch on that and log the whole task body when it happens.

Each upload and each task is independent, so run them concurrently and keep one taskId per document. The upload cap is 100 MB per file.

Get started with Foxit’s PDF Structural Extraction API

The pattern is four calls. Upload the PDF for a documentId, start pdf-structural-extract for a taskId, poll until COMPLETED for a resultDocumentId, then download the ZIP and read StructureInfo.json. From there analyzeResult.elements gives you typed titles, headings, paragraphs, and a table cell grid you can turn into dictionaries in a few lines.

Create a free developer account (no credit card) at account.foxit.com/site/sign-up, grab your Client ID and Secret from the APIs Dashboard, and run the script above against invoice_table_test.pdf to see the structured output for yourself.

eSignature API: A Developer’s Guide to Adding Signing to Your App

Illustration of code calling an esignature API alongside a Sign Document screen with a signature field

Adding a signing step to your app involves more than it first appears. Authentication, document preparation, session handling, and completion tracking all need to work together. This guide walks through a full esignature API integration with Foxit eSign, from your first authenticated request to a signed, webhook-confirmed document.

Adding a signing step to an existing app sounds straightforward until you try to implement it. Getting a document signed is a simple idea, but the actual API surface, how authentication works, how you mark up a PDF, and how you learn that signing finished all take longer to figure out than they should. This guide walks a complete Foxit eSign API integration from the first authenticated request through to a webhook-confirmed, digitally signed document. Comfort with REST APIs and bearer tokens is enough to follow along.

What an eSignature API is and how signing workflows work

An eSignature API is a REST interface that owns the document-signing lifecycle, covering preparation, delivery, the signing session, and the audit trail. You supply the document and the signers, and the API handles field rendering, identity capture, signature application, and tamper-evident recordkeeping, so none of that infrastructure is yours to build.

The distinction that matters most for app developers is redirect-based versus embedded signing. Redirect-based signing sends the user to a hosted URL to sign and returns them afterwards, which means they leave your product mid-task. Embedded signing renders the session inside your own application, typically in an iframe, so the user never changes context. If signing sits in the middle of an onboarding or checkout flow, embedded keeps that flow intact.

Foxit eSign supports both. The lifecycle you implement runs through five stages, starting with OAuth 2.0 authentication, then PDF preparation with Text Tags, a POST to /esign/api/v1/folders/createfolder that defines signers and mints a session, the signer completing the embedded session, and a folder_executed webhook confirming the document is final.

Diagram of the esignature API signing lifecycle from OAuth authentication through webhook confirmation

The five stages this guide implements in order. Each one maps to a section below.

Prerequisites

Signing up for eSign API access is self-serve on the Foxit API Platform — no sales ticket and no waiting for an administrator. The platform provisions a trial eSign account for you and manages its credentials, so the whole setup is a short walkthrough:

  1. Create and sign in to your Foxit API Platform account. You need an active Foxit API plan and a complete CAS profile — first name, last name, email address, and company name (a company address is required for eSign provisioning). Your eSign account is provisioned from those profile details, so complete them before you start.
  2. Open the Dashboard and select Get started with eSign in the Dashboard header. That takes you to the eSign activation page.
  3. Choose your document-storage region. The activation card preselects United States, with European Union and Canada as alternatives. Select Activate with [region] storage. Confirm the region before activating: once the remote account exists, the region is locked, and changing it later means submitting a case with Foxit Support.
  4. Wait for provisioning to complete. You may see “Creating your eSign account” and then “Confirming your eSign account” — the second is a reliability check that reconciles ambiguous creation results, so let it finish instead of retrying activation.
  5. Confirm eSign is ready. A successful activation displays “eSign is ready”, your eSign company number, and the selected region. The platform manages the API account and its credentials, and the same unified client_id/client_secret you use for PDF Services also authenticate eSign API, Document Generation, and Embed API — there is no separate eSign key pair to generate. If credential retrieval is still pending or failed, use Resume credentials or Retry credentials on the page; the first-call button stays disabled until credentials are ready.
  6. (Optional but worth it) Run the sample request on the activation page. It uses your profile’s name and email as the first signing party, sends a Base64-encoded one-page contract, adds Signer Name, Today’s Date, Signature, and Date Signed fields, creates a draft with sending disabled, and returns an embedded sending session URL — a quick end-to-end check that everything is wired up. The button switches to Running and then reports “Sample draft created. The embedded sending session is ready.”

Two things to know before you build: your provisioned account is an eSign Business trial that lasts 30 days and starts in TEST mode, so every envelope carries a watermark. Moving the account to Production mode is a Sales/Operations step performed in Foxit Monitor, not something the API can do.

Beyond the account, the rest of the prerequisites are lightweight:

  • Python 3.8+ with pip and a venv, for the webhook handler later in this guide.
  • Flask and requests for the sample code.
  • curl for the token exchange, and ngrok or any tunnel that gives your local webhook endpoint a public HTTPS URL.
  • A code editor, VS Code with the Python extension being a reasonable default alongside PyCharm.
  • A tagged sample PDF, so you do not have to author one. This guide uses agreement-signable.pdf, which already carries Text Tags for a single signer.

Scaffold the workspace in one shot:

mkdir foxit-esign && cd foxit-esign
python3 -m venv .venv && source .venv/bin/activate
pip install flask requests
export ESIGN_HOST="https://na1.foxitesign.foxit.com"export ESIGN_CLIENT_ID="your_api_key"
export ESIGN_CLIENT_SECRET="your_api_secret"
export WEBHOOK_SECRET="your_webhook_secret"

Step 1: Authenticate with the Foxit eSign API

The platform provisions your eSign account and manages its credentials, and that single pair is shared across eSign API, PDF Services, Document Generation, and Embed API. No bearer token, no client_credentials grant, no expires_in to watch.

The quickest way to prove a credential pair works is a minimal folder creation — the same /esign/api/v1/folders/createfolder endpoint Step 3 explains field by field:

curl -X POST "$ESIGN_HOST/esign/api/v1/folders/createfolder" \
  -H "client_id: $ESIGN_CLIENT_ID" \
  -H "client_secret: $ESIGN_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "folderName": "Auth check",
    "inputType": "url",
    "fileUrls": ["https://github.com/lucienchemaly/foxit-demo-templates/raw/main/esign/agreement-signable.pdf"],
    "fileNames": ["agreement.pdf"],
    "parties": [
      {
        "firstName": "Jane",
        "lastName": "Smith",
        "emailId": "[email protected]",
        "permission": "FILL_FIELDS_AND_SIGN",
        "sequence": 1
      }
    ],
    "processTextTags": false,
    "processAcroFields": false,
    "createEmbeddedSigningSession": false,
    "createEmbeddedSendingSession": true,
    "sendNow": false
  }'

A valid credential pair returns JSON with a folder object carrying folderId and folderStatus (DRAFT here, since sendNow is false), so the response doubles as your auth check — a rejected pair comes back as an error before any folder exists. Send the same two headers on every subsequent request. There is nothing to mint or refresh like an OAuth token; if the platform ever flags the stored credentials as stale, refresh them from the activation page with Resume credentials or Retry credentials. And because eSign shares the PDF Services credential pair, the two integrations are interchangeable — one pair of credentials covers both.

Step 2: Prepare a document and define signature fields with Text Tags

Foxit eSign reads field definitions out of the PDF itself at upload time. You embed them as Text Tags, plain-text strings placed where each field belongs, and the API converts them into interactive fields on ingest.

The syntax is ${fieldtype:party_number:required:field_name:width}, where y marks a field required and n marks it optional, the party number maps to a signer’s sequence, and width is expressed as underscores.

${signfield:1:y:____}                 # required signature, party 1
${datefield:1:y::____}                # required date, party 1
${i:1:______}                         # initials field, party 1
${t:1:y:Full_Name:__________}         # required text field, party 1, named "Full_Name"
${signfield:2:y:____}                 # required signature, party 2

Each tag names a type, either in full or by its short alias. The supported set covers signfield (s), initialfield (i), datefield (d), textfield (t), textboxfield (tb), checkboxfield (c), radiobuttonfield (rb), securedfield (sc), attachmentfield (a), imagefield (img), accept (ab), decline (db), payfield (pf), and formulafield (ff). Author them in lowercase to match the documented syntax.

Express width as underscores and never as a literal space, because a space stops the tag from being recognized. A tag written ${s:1: } renders in the signing UI as plain ${s:1: } text with no field attached, while ${signfield:1:y:____} becomes a real signature field. That failure is silent, so the create call still succeeds and you only notice when a signer has nothing to sign.

Two preparation details save support tickets later. Foxit eSign converts tags to fields but does not delete the tag text, so set the tag’s text color to match the page background if you do not want signers reading raw ${...} strings. And paste tags through a plain-text editor first, because smart-quote autocorrect in Word or Google Docs silently swaps straight ASCII characters for typographic ones and tag parsing then fails without an error.

If you would rather not tag a document by hand for a first run, agreement-signable.pdf is already prepared and hosted at a public URL you can pass straight to the next step.

Step 3: Send the document and mint an embedded session

One call to /esign/api/v1/folders/createfolder submits the document, defines the signers, and, when you ask for it, returns a ready-to-render signing URL. Foxit calls the signing container a folder rather than an envelope.

{
  "folderName": "Customer Agreement - Acme Corp",
  "fileUrls": ["https://github.com/lucienchemaly/foxit-demo-templates/raw/main/esign/agreement-signable.pdf"],
  "fileNames": ["agreement.pdf"],
  "parties": [
    {
      "firstName": "Jane",
      "lastName": "Smith",
      "emailId": "[email protected]",
      "permission": "FILL_FIELDS_AND_SIGN",
      "sequence": 1
    }
  ],
  "processTextTags": true,
  "createEmbeddedSigningSession": true,
  "embeddedSignersEmailIds": ["[email protected]"],
  "sendNow": false
}

In this body you point the API at the tagged PDF with fileUrls and give it a display name through fileNames, define the single signer in parties with firstName, lastName, emailId, the FILL_FIELDS_AND_SIGN permission and a signing sequence, then set processTextTags to true so the embedded tags become real fields. Asking for createEmbeddedSigningSession and naming the signer in embeddedSignersEmailIds returns a session URL in the same response, and sendNow set to false keeps Foxit from emailing an invitation, which is what you want while testing.

One nuance to expect here. sendNow: false on its own produces a DRAFT folder, but pairing it with createEmbeddedSigningSession returns folderStatus of SHARED, since the folder has to be live for the session URL to open. No email goes out either way, so the flag still suppresses the invitation, and the status you see just reflects that the document is now signable. To attach the file as bytes instead of a URL, send base64FileString as an array together with inputType set to "base64".

The response nests the folder identifier at folder.folderId and carries an embeddedSigningSessions array. Each entry holds emailIdOfSigner, the raw embeddedToken, and the renderable embeddedSessionURL, which follows this shape:

https://{HOST_NAME}/embedded/embeddedsign?eetid={URL-ENCODED-EMBEDDED-TOKEN}

Three behaviors are worth designing around before you ship. Omitting embeddedSignersEmailIds returns email id of embedded signer(s) not submitted, so always list embedded signers explicitly. Every party number used in a Text Tag needs a matching entry in parties, because a sendNow: true create still reports success while silently dropping the fields of a party that is not listed, which means a mandatory signature is never routed. And for a multi-party document where everyone signs in your app, createEmbeddedSigningSessionForAllParties set to true covers all recipients rather than naming them one by one.

To dispatch a draft later, POST to /api/folders/sendDraftFolder.

Folder state moves through DRAFT, SHARED, COMPLETED, and finally EXECUTED once the digital signature is applied. A run of this guide’s single-signer flow produced exactly that path, with the activity log labelling the creation event CREATED and then recording Envelope viewed, Jane Smith signed this folder at COMPLETED, and Document(s) successfully executed at EXECUTED. PARTIALLY SIGNED appears only when a folder has more than one party and some but not all of them have signed, so you will not see it on a single-signer document.

Step 4: Render the signing session in your app

Load the embeddedSessionURL in an iframe. The sandbox attribute needs a specific minimum set of permissions, and trimming it is a common way to break the signing UI with no visible error.

<iframe
  id="signing-session"
  src="PASTE_EMBEDDED_SESSION_URL_HERE"
  width="100%"
  height="780px"
  style="border: none;"
  sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-top-navigation"
></iframe>

In this markup the src receives the embeddedSessionURL from the createfolder response, and the five sandbox permissions are the minimum the signing UI needs. Removing allow-popups or allow-top-navigation breaks the flow in ways that surface no obvious error, so keep all five unless you have tested a reduced set end to end. Session URLs are short-lived, so generate one when the user is ready to sign rather than caching it, and request a fresh one per signer through /esign/api/v1/embedded/regenerateEmbeddedSigningSession when a session goes stale.

To check the flow before wiring it into your own UI, download the ready-to-run iFrame test page, open it in a browser, paste your embeddedSessionURL into the input, and load it. A correctly tagged document renders with active signing controls.

Embedded esignature API signing session showing four required fields still outlined and unfilled

A real session opened from the embeddedSessionURL this guide’s request returns. All four tags in the sample became required fields, which is what the counter is reporting. The raw tag text still shows through each box, which is exactly why you color it to match the page background before shipping.

Step 5: Confirm completion with webhooks

Polling for completion wastes requests and adds latency. Register a webhook instead and Foxit eSign posts to your endpoint as each signing event happens.

Registration lives on the eSign portal’s API settings page at /consumer/consumerdetails, under Configure Webhooks, where you set the callback URL, a webhook secret, and the events you want. That page is visible only to the account owner, so an admin-level user will not find it. Your endpoint has to be reachable over public HTTPS.

Foxit eSign webhook configuration screen with callback URL, webhook secret, and signing event checkboxes

The owner-only webhook settings. The event checkboxes control which callbacks reach your endpoint.

The events available are folder_sent, folder_viewed, folder_signed, folder_cancelled, folder_executed, folder_deleted, folder_completed, folder_assigned, and folder_access_code_failure. In practice folder_viewed, folder_signed, folder_completed, and folder_executed are the ones that fire on an API-dispatched folder.

The event to build on is folder_executed. folder_completed fires once every party has signed, but folder_executed fires after Foxit applies the digital signature and locks the audit trail, so it is the point at which a download gives you the final document.

Foxit signs every callback. It delivers the POST as <your-url>?signature=<base64>, where the signature is the base64 of an HMAC-SHA-256 over the raw request body keyed with your webhook secret. Verify it against the unparsed bytes, since re-serializing the JSON changes whitespace or key order and breaks the comparison.

import base64
import hashlib
import hmac
import os

from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = os.environ["WEBHOOK_SECRET"]


def verify_signature(raw_body: bytes, signature: str) -> bool:
    expected = base64.b64encode(
        hmac.new(WEBHOOK_SECRET.encode(), raw_body, hashlib.sha256).digest()
    ).decode()
    return hmac.compare_digest(expected, signature)


@app.route("/webhooks/esign", methods=["POST"])
def handle_esign_event():
    raw_body = request.get_data()
    if not verify_signature(raw_body, request.args.get("signature", "")):
        return jsonify({"error": "invalid signature"}), 403

    payload = request.get_json(silent=True) or {}
    event_name = payload.get("event_name")
    folder = payload.get("data", {}).get("folder", {})

    if event_name == "folder_executed":
        # The document is final here. Archive it, update your record, notify the user.
        app.logger.info("Signing finished for folder %s", folder.get("folderId"))

    return jsonify({"status": "received"}), 200

In this handler you read the unparsed body first, recompute the HMAC over those exact bytes with your webhook secret, and compare it against the signature query parameter using hmac.compare_digest so the check is not timing-dependent. A mismatch returns 403 before any business logic runs, which stops a spoofed POST to your public URL from triggging an archive. Only after the signature passes do you parse the JSON, read event_name, and branch on folder_executed to run downstream work, returning 200 so Foxit records the delivery as successful. A ready-to-run version of this receiver lives at webhook_receiver.py in the demo repo.

Test the whole path against a draft. Create the folder with sendNow set to false, dispatch it, sign in the embedded session, and watch the events arrive. Once the signer finishes, the session redirects to a result URL carrying event=signing_success, and the document reaches EXECUTED within a few seconds.

Completed embedded signing session with name, initials, date, and signature filled in and Finish enabled

The session once every field is filled. The counter reads zero and Finish activates, which is the state that produces folder_completed and then folder_executed.

Step 6: Retrieve the executed document and its audit trail

With folder_executed in hand, pull the final PDF. The download takes the folder id as a query parameter.

curl -o executed.pdf \
  "$ESIGN_HOST/api/folders/download?folderId=35117696" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

The response streams the executed PDF, which arrives with a content type of application/octet-stream rather than application/pdf, so write the bytes to a file rather than sniffing the header. The returned document carries the filled field values and the signature certificate, so a text extraction of it contains the signer’s typed name, the date, and the per-signer Signer ID recorded on the certificate page.

For the audit trail itself, GET /api/folders/viewActivityHistory?folderId={id} returns a details object holding the folder metadata plus an activities array, where each entry carries an activity description and the folderStatus at that moment. That endpoint is GET-only and only returns data once the folder has been shared, so a pure DRAFT folder reports nothing useful.

The signature certificate attached to a document once folder_executed has fired. Each signer’s adopted signature sits beside the identity record Foxit captured, which is the tamper-evident audit trail you are archiving alongside the PDF.

Common mistakes

  • Dropping the /api/ prefix : every eSign path sits under /api/, so it is /api/folders/createfolder, not /folders/createfolder.
  • snake_case request fields : the body is camelCase. folderName, fileUrls, emailId, and sendNow work, while folder_name, document_url, email, and send_now do not.
  • Omitting processTextTags : without it the tags stay inert text, the signer sees raw ${...} strings, and they can finish without signing anything.
  • A tag party with no matching parties entry : the create returns success and that party’s fields disappear silently, so their signature is never collected.
  • Skipping signature verification : a public webhook URL that acts on any POST is a spoofing target. Verify the HMAC before you touch the payload.
  • Archiving on folder_completed : that fires before the digital signature is applied. Wait for folder_executed.
  • Caching an embeddedSessionURL : sessions expire. Mint one when the signer is ready, and regenerate when needed.
  • Trimming the iframe sandbox list : removing allow-popups or allow-top-navigation breaks signing with no error message.
  • Smart quotes or a space inside a tag : both stop tag recognition. Use straight ASCII and underscores.
  • Reusing PDF Services credentials : eSign has its own portal, host, and key pair.

eSignature API FAQ

A REST interface that handles the document-signing lifecycle, covering preparation, delivery, the signing session, and the audit trail, so your app makes calls rather than building signing infrastructure.

Embedded signing renders the session inside your own application instead of redirecting to a hosted page, so the signer finishes without leaving your product. That matters most when signing sits inside a flow you do not want to interrupt, like onboarding or checkout.

The party number in a tag maps to a signer’s sequence in the parties array, so ${s:1: } is signed by the party with sequence: 1. Keeping those aligned is the difference between fields routing correctly and vanishing silently.

folder_completed fires when all parties have signed. folder_executed fires after Foxit applies the digital signature and locks the audit trail, which is why downstream archiving should trigger on folder_executed.

Recompute a base64 HMAC-SHA-256 of the raw request body using your webhook secret and compare it with the signature query parameter. Verify against raw bytes, never re-serialized JSON.

Yes. Pass additional entries in fileUrls with matching fileNames. Each document carries its own Text Tags, and party assignments stay consistent across the folder.

No. The free tier gives you credentials and lets you run the full flow, and no credit card is required to create the account.

Yes. Create it with sendNow set to false for a DRAFT folder, inspect it in the eSign dashboard, then dispatch with /api/folders/sendDraftFolder.

Wrapping up

That is the full path for an eSignature API integration. Authenticate with the client_credentials grant, prepare a PDF with Text Tags, create the folder with processTextTags and an embedded session, render the returned embeddedSessionURL in an iframe, and act on a signature-verified folder_executed webhook.

The same shape extends to multi-signer approval chains, CRM-triggered signing when a deal closes, and template-based documents where fields arrive pre-filled from your own data. Those build on the pieces already in place rather than replacing them.

Ready to build? Create a free account (no credit card required) at account.foxit.com/site/sign-up, generate your API Key and Secret from the API tab, and run the token call above against agreement-signable.pdf to see a session URL come back.