10 best PDF MCP servers for AI agents in 2026

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.
| Job | Pick | Why it fits |
|---|---|---|
| Converting and editing PDFs through one cloud API | Foxit PDF API MCP Server | 40 tools in v1.1.2 for conversion both ways, merge, split, compress, OCR, redaction, watermark, and forms |
| Certificate-based signing inside agent flows | Nutrient DWS MCP Server | A PAdES digital signing tool, plus OCR and AI redaction, on Nutrient’s cloud |
| Zero-install PDF editing in claude.ai or ChatGPT | CloudConvert MCP Server | Hosted Streamable HTTP endpoint with OAuth; 24 tools |
| Invoice parsing and table extraction in the cloud | PDF.co MCP Server | 38 tools including fillable forms, table finding, and an AI invoice parser |
| Open-source, layout-aware conversion for RAG | Docling MCP | MIT, OCR and table-structure options, v3.3.0 released 6 Oct 2026 |
| Managed parsing and schema extraction | LlamaParse MCP | 26 tools on the unified OAuth endpoint, including indexing and project-key creation; 10K free credits/month |
| Searching long PDFs and whole folders locally | pdf-mcp (jztan) | 13 tools with hybrid keyword and semantic search that flags hidden text as untrusted |
| Lightweight local reading across many formats | anymd | Rust 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 firewall | Stirling PDF MCP Server | Built into Stirling PDF with OAuth2 or API key, off by default |
| Offline editing with no account | PDF Toolkit MCP | 22 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.

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.

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.
| Feature | Details |
|---|---|
| Covers | Convert 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 go | The 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. |
| Setup | stdio 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. |
| Pricing | 1 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. |
| Maintenance | MIT. 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 with | Claude Desktop, VS Code, and Cursor, plus any MCP host via stdio or HTTP stream. |

Foxit charges 1 credit for each successful billable PDF processing request. Uploads, polling, downloads, and failed requests do not use credits.

The free Foxit Developer plan includes 500 credits per year.

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.

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.
| Feature | Details |
|---|---|
| Covers | Merge, 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 go | The server runs locally and sends processing files to Nutrient’s cloud. Sandbox restrictions apply only when configured. |
| Setup | stdio via npx or the Claude extension. OAuth covers both APIs; headless/API-key use needs separate Processor and Data Extraction credentials. |
| Pricing | Separate 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. |
| Maintenance | MIT. v0.1.3 (28 Aug 2026). |
| Works with | Claude Desktop, Cursor, Windsurf, VS Code, and Codex. |

Nutrient’s MIT-licensed MCP server, shown here at v0.1.3.

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.
| Feature | Details |
|---|---|
| Covers | Convert, compress, watermark, capture URLs to PDF, merge, OCR, PDF/A, encrypt and decrypt, split and extract pages, rotate. 24 tools. |
| Where files go | CloudConvert’s cloud. Input files can be supplied as downloadable URLs. Output links expire after 24 hours. ChatGPT automatically bridges attachments into temporary input URLs. |
| Setup | Hosted Streamable HTTP endpoint with OAuth 2.0. Nothing to install. |
| Pricing | 10 free conversion credits per day, with additional credits via packages or subscriptions. See CloudConvert pricing. |
| Maintenance | Hosted service with no package version to track. |
| Works with | ChatGPT and Claude connectors, per CloudConvert’s docs. |

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.
| Feature | Details |
|---|---|
| Covers | PDF 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 go | PDF.co’s cloud. |
| Setup | stdio via uvx. API key required. |
| Pricing | Credit-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. |
| Maintenance | MIT. v0.0.9 (28 Jul 2026). |
| Works with | Cursor and Claude Desktop. |

PDF.co sells monthly plans with a set credit allowance.

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.
| Feature | Details |
|---|---|
| Covers | Convert 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 go | Local 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. |
| Setup | stdio, SSE, or streamable HTTP. The remote Docling Serve API key is separate from MCP endpoint authentication; configure both independently. |
| Pricing | Free and open source; you pay for compute. |
| Maintenance | MIT. v3.3.0 (6 Oct 2026). Version 3.x requires MCP SDK v2; verify your integration’s SDK version before upgrading. |
| Works with | Claude Desktop, LM Studio, and Llama Stack. |

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.
| Feature | Details |
|---|---|
| Covers | Parse 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 go | LlamaIndex’s cloud, with North America and EU endpoints available. |
| Setup | Hosted Streamable HTTP. Interactive clients use OAuth; API-key callers pass files by URL (not direct upload). |
| Pricing | 10K free credits/month; Starter tier includes 40K credits/month. Additional usage billed per 1,000 credits. See LlamaIndex pricing for current rates. |
| Maintenance | Hosted service. The older run-llama/mcp-server-llamacloud repo is archived; use the current hosted endpoint. |
| Works with | Cursor, VS Code, Claude Code, Claude connectors, and Codex. |

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.
| Feature | Details |
|---|---|
| Covers | Hybrid 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 go | Local by default. HTTP deployments use the configured server. Optional remote embeddings send text to the configured endpoint. |
| Setup | stdio or bearer-token-protected HTTP. Claude Desktop extension available. VS Code, Cursor, Codex CLI, and Kiro setups are documented. |
| Pricing | Free. |
| Maintenance | MIT. v3.5.0 (3 Oct 2026). |
| Works with | Claude Desktop and Claude Code; documented setups for VS Code, Cursor, Codex CLI, and Kiro. |

The v3.5.0 release of jztan’s pdf-mcp server.

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.
| Feature | Details |
|---|---|
| Covers | Four tools for mixed-format reading and Markdown output. OCR requires opt-in local models or an installed Tesseract binary. No PDF editing. |
| Where files go | Your machine. No files are uploaded. |
| Setup | stdio Rust binary installed via npm (npx -y @sylphx/anymd). No API key required. |
| Pricing | Free MIT core. An optional $29 one-time Pro add-on enables video timelines and citation checks. |
| Maintenance | MIT core. Release 8.5.3 (11 Oct 2026). |
| Works with | Claude Code, Codex, Cursor, VS Code, Claude Desktop, Windsurf, and Gemini CLI. |

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 @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.
| Feature | Details |
|---|---|
| Covers | Page 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 go | Your server for standard PDF operations. AI features use configured model and embedding providers, which may be hosted externally depending on your configuration. |
| Setup | Streamable 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. |
| Pricing | Stirling 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. |
| Maintenance | Open-core; MCP implementation is proprietary. Stirling PDF v3.1.0. Verify the applicable User License for your deployment context before production use. |
| Works with | MCP Inspector; Claude Desktop via mcp-remote. |

Stirling PDF offers Free, Team, and Enterprise plans. These are platform tiers, not a confirmed per-tool MCP rate card.

Stirling PDF’s MCP endpoint is disabled by default. Enable /mcp and configure API key or OAuth 2.1 authentication before connecting a client.

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.
| Feature | Details |
|---|---|
| Covers | Extract 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 go | Your machine. Fully offline. |
| Setup | stdio via npx. No auth required. |
| Pricing | Free. |
| Maintenance | MIT. v0.3.1 (30 May 2026). |
| Works with | Claude Desktop, Claude Code, Cursor, VS Code, and Windsurf. |

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.
| Server | Read and extract | OCR | Convert to or from PDF | Merge or split | Compress | Protect or redact | Runs on |
|---|---|---|---|---|---|---|---|
| Foxit PDF API MCP Server | Yes | Yes | Yes (both ways) | Yes | Yes | Password and redaction | Foxit cloud |
| Nutrient DWS MCP Server | Yes | Yes | Yes (both ways) | Merge | Yes | Password and redaction | Nutrient cloud |
| CloudConvert MCP Server | Via conversion | Yes | Yes (both ways) | Yes | Yes | Encrypt | CloudConvert cloud |
| PDF.co MCP Server | Yes | Yes | Yes (both ways) | Yes | No | Password | PDF.co cloud |
| Docling MCP | Yes | Yes | To Markdown or JSON | No | No | No | Local or Docling Serve |
| LlamaParse MCP | Yes | Yes | Markdown/text parsing; JSON extraction | Logical section splitting | No | No | LlamaIndex cloud |
| pdf-mcp (jztan) | Yes | Yes (Tesseract) | No | No | No | No | Local |
| anymd | Yes | Opt-in (Tesseract or local model) | To Markdown | No | No | No | Local |
| Stirling PDF MCP Server | Yes (OCR via enabled ocr-pdf using OCRmyPDF/Tesseract); AI Q&A needs AI engine | Yes | Yes (both ways) | Yes | Yes | Encrypt and permissions | Your server |
| PDF Toolkit MCP | Yes | Via rendered page images to client vision | To Markdown; from text, Markdown, or templates | Yes | No | AES-256 encrypt | Local |
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.
| Server | Pricing model | Free allowance |
|---|---|---|
| Foxit PDF API MCP Server | 1 credit per successful (2xx) billable PDF processing request; shared credit pool. See pricing. | 500 credits/year |
| Nutrient DWS MCP Server | Credits 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 Server | Conversion credits per operation; packages or subscriptions. See pricing. | 10 credits/day |
| PDF.co MCP Server | Credits per page and per call. See pricing. | Trial credits on signup |
| LlamaParse MCP | Per-credit usage above free tier; Starter tier includes 40K credits/month. See pricing. | 10K credits/month |
| Stirling PDF MCP Server | Platform-level tiers (Free, Team, Enterprise); 1 cent per credit on paid tiers. See pricing. | 1,000 credits/month on free tier |
| Docling MCP | Free 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 |
| anymd | Free MIT core; optional $29 one-time Pro. | No per-call meter |
| PDF Toolkit MCP | Free. | 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
Can Claude read PDFs without an MCP server?
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.
Do PDF MCP servers work in ChatGPT and claude.ai?
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.
Is there a free PDF MCP server?
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.
Should you call the PDF API directly instead of through MCP?
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.
For more on these tradeoffs, see Building Auditable, AI-Driven Document Workflows with Foxit APIs.
10 best eSignature APIs for developers in 2026

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 case | Pick | Why it fits |
|---|---|---|
| Signing inside a larger PDF workflow | Foxit eSign API | eSign, PDF Services, and Document Generation on one account. See Foxit API pricing for current allocations. |
| Mature SDKs with self-serve embedding | Dropbox Sign API | Six official SDKs, with embedded signing from the Standard plan ($250/month billed yearly). |
| Low per-envelope rate on a monthly plan | BoldSign API | $30/month with 40 envelopes, then $0.75 per envelope sent. |
| Per-signature API pricing with no monthly tier to pick | SignNow API | Full API access is gated behind the custom-quoted Site License plan, billed at $1.50 per signature invite. |
| Pure usage pricing with no monthly minimum | SignWell API | From $0.85 per API document, falling to $0.20 with volume. |
| Paying only for completed signatures | Anvil Etch | $1.50 per packet, billed only when every party signs, with embedding included on the free plan. |
| Government and FedRAMP requirements | DocuSign eSignature REST API | DocuSign Federal offers FedRAMP Moderate and provisional DoD IL4 authorization, separate from commercial Enhanced. |
| Teams already licensing Adobe | Adobe Acrobat Sign API | Free Developer Edition available. Production access and embedding go through Adobe sales. |
| Low per-document fee with an open-source core | DocuSeal | $0.20 per completed document plus a $20 Pro seat. AGPL-3.0 self-hosting available. |
| Open source with unlimited documents | Documenso | Teams 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’s eSign API covers envelope creation, embedded signing, webhook events, and activity history.
Key features at a glance
| Feature | Details |
|---|---|
| Covers | Envelopes, 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 signing | Available on every plan, including the free Developer plan (trial). |
| Webhooks | Webhook channels for envelope status events. Status checks and webhooks consume no credits. |
| Sandbox | eSign trial on the free Developer plan. Envelopes are watermarked in TEST mode. |
| Pricing | eSign 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. |
| SDKs | REST 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 limits | Per 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 claims | Foxit’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.
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.
The eSign API developer portal, source of the Postman collection.
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
| Feature | Details |
|---|---|
| Covers | Signature requests, templates, bulk send, embedded requesting and signing. Hosted only. |
| Embedded signing | Standard ($250/month billed yearly) and Premium. Not available on Essentials. |
| Webhooks | Event callbacks. |
| Sandbox | Free test mode. Documents are watermarked and not legally binding. 10 requests/minute. |
| Pricing | Essentials $75/month billed yearly (50 signature requests/month), Standard $250/month (100 requests/month), Premium by quote. Billed per signature request. |
| SDKs | Python, Ruby, PHP, Java, Node.js, C#. |
| Rate limits | 100 requests/minute general; 25 requests/minute on heavier endpoints. Confirm current limits against the Dropbox Sign rate limits docs. |
| Compliance claims | SOC 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
| Feature | Details |
|---|---|
| Covers | Signing, 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 signing | Enterprise API plan only. |
| Webhooks | Enterprise API plan only. |
| Sandbox | Free, 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 limits | 2,000 requests/hour in production. |
| Compliance claims | SOC 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’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
| Feature | Details |
|---|---|
| Covers | Embedded signing and sending, document generation from .docx templates, and webhooks, documented across the full REST API. Hosted. |
| Embedded signing | Full API access, including embedded signing, requires the Site License plan. Business, Business Premium, and Enterprise plans do not include it. |
| Webhooks | Documented event callbacks for signing status. |
| Sandbox | Free developer account with a Development mode application, up to 500 signature invites without an active trial or subscription. |
| Pricing | Business $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. |
| SDKs | Node.js, C#, Java, Python, PHP. |
| Rate limits | Per application: 500 requests/hour in Development, 1,000 requests/hour by default in Live. |
| Compliance claims | SOC 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
| Feature | Details |
|---|---|
| Covers | Signing, requesting, and template editing through the API, with white-label embedding. Hosted. |
| Embedded signing | Available on the API usage plan, including embedded signing, requesting, and template editing. |
| Webhooks | Yes, with hash verification. |
| Sandbox | Unlimited 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. |
| Pricing | From $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. |
| SDKs | Ruby, Node.js, Python, PHP, plus CLI and MCP server support. |
| Rate limits | Per token: 100 requests/minute general, 30 requests/minute for creation endpoints, 20 requests/minute for test endpoints. |
| Compliance claims | ESIGN, 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’s Etch E-Sign API documentation page.
Key features at a glance
| Feature | Details |
|---|---|
| Covers | E-signatures, PDF filling, document generation, and Workflows on the same platform. Hosted. |
| Embedded signing | Every plan, including Free. White-labeled signing pages from Product Pack ($425/month, or $340/month billed annually). |
| Webhooks | Yes, with optional payload encryption. |
| Sandbox | Unlimited 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. |
| SDKs | Node.js, Python, .NET. Go is community-supported. REST plus GraphQL. |
| Rate limits | Production: 4 requests/second on Free or pay-as-you-go, 40 requests/second on subscriptions. Development: 4 requests/second on all plans. |
| Compliance claims | SOC 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 lists Starter at $50/month, Intermediate at $300/month, and Advanced at $480/month.
Key features at a glance
| Feature | Details |
|---|---|
| Covers | Hosted envelopes and templates. PowerForms and API bulk send from Advanced. Government-cloud deployment is a separate sales discussion. |
| Embedded signing | Enhanced plans only (sales-led, five-user minimum). Embedded sending is available on every plan. |
| Webhooks | Account-, envelope-, and recipient-level Connect. Recipient-level access from Advanced. Retries extend across a 15-day span. |
| Sandbox | Free developer account with no expiry. Envelopes are not legally valid. Production requires a Go-Live review. |
| Pricing | Starter $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. |
| SDKs | C#, Java, Node.js, PHP, Python, Ruby, iOS, Android. |
| Rate limits | Account 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 claims | ISO 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 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 REST API v6 lets you create agreements, retrieve signed documents, and set up webhooks.
Key features at a glance
| Feature | Details |
|---|---|
| Covers | Agreements, library templates, bulk sending (MegaSign), and webhooks through REST API v6. Hosted. |
| Embedded signing | Documented in the developer guide. Production embedding requires an Acrobat Sign Solutions or Embed (OEM) partner program agreement. |
| Webhooks | Yes, through the webhook APIs. |
| Sandbox | Free Developer Edition. Test documents carry a “test” label. |
| Pricing | Sales-led. Sent agreements consume transactions. Confirm allowances and terms in your contract. See Acrobat Sign Solutions for current options. |
| SDKs | Adobe labels its older SDKs obsolete. Use REST v6 and the current Postman samples. |
| Rate limits | Plan-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 claims | Adobe’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.

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

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 uses submission endpoints to send documents and retrieve signed PDFs.
Key features at a glance
| Feature | Details |
|---|---|
| Covers | Signing and form building. AGPL-3.0 core with mandatory UI attribution terms. Embedded signing on Pro. Hosted and self-hosted options. |
| Embedded signing | Pro tier, with React, Vue, Angular, and JavaScript components. |
| Webhooks | Form, submission, and template events (available under the OSS license). |
| Sandbox | Free, unlimited API and embedding sandbox. |
| Pricing | Pro: $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. |
| SDKs | Front-end embed components (React, Vue, Angular, JavaScript). Confirm server SDK availability before planning your integration. |
| Rate limits | Not published as of 7 Oct 2026. |
| Compliance claims | Cloud: 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 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 v2 REST API is moving from separate documents and templates to a unified envelopes model.
Key features at a glance
| Feature | Details |
|---|---|
| Covers | Documents, templates, recipients, fields, and webhooks through the v2 REST API (being consolidated into a unified “envelopes” model). Hosted and self-hosted options. |
| Embedded signing | Teams 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. |
| Webhooks | Document, recipient, and template lifecycle events, with signature verification. |
| Sandbox | Separate staging environment that mirrors production; requires its own account and a test card to try paid-plan features. |
| Pricing | Teams: $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. |
| SDKs | Official TypeScript/Node.js SDK (@documenso/sdk-typescript) plus a React embed package. The REST API itself is language-agnostic. |
| Rate limits | 1,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 claims | SOC 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 official TypeScript/Node.js SDK’s GitHub repository (MIT-licensed, separate from the AGPL-3.0 server).

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.
| Vendor | Published limit |
|---|---|
| Dropbox Sign | 10 requests/minute in test mode |
| SignNow | 500 requests/hour per application in Development, 1,000 requests/hour by default in Live |
| SignWell | 100 requests/minute for general requests, 30/minute for document or template creation, and 20/minute in test mode |
| Adobe Acrobat Sign | 3 identical GET requests/minute on Global, Enterprise, and Developer tiers; 3 per 3 minutes on other tiers |
| DocuSign | Do not poll the same envelope or API object more than once every 15 minutes |
| Documenso | 1,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
How do I choose the best eSignature API for my app?
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.
Which eSignature APIs include embedded signing on a free plan?
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.
Do eSignature APIs charge for envelopes that never get signed?
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.
Does a SOC 2 or HIPAA badge mean every plan is compliant?
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

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.
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.
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.
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."}
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=, notjson=inrequests). 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.boundingBoxis 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 checkingtypefirst :tableelements carry an emptyregionobject and expose position throughregionsandscores(both plural) instead, so a generic per-element loop raises aKeyErrorthe moment it reaches a table. - Treating table cells as a 2D array : table content lives in
content.body.cells, a flat list withrowIndex/columnIndexon each cell, not a nestedrowsarray. - 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
What is contract metadata extraction?
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.
What contract fields can I extract with the Foxit API?
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.
Do I need a separate parser for every contract template?
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.
How do I get the signing audit trail for a contract?
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.
Can I try the Foxit API for free?
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

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’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.
- Steps from credentials to first embedded session. Fewer authentication steps and self-serve signup mean faster iteration.
- True iframe embed vs. redirect-only. A genuine iframe keeps signers inside your product. A redirect breaks the in-app experience.
- SDK language coverage. Real, installable packages on public registries (npm, PyPI, Maven, RubyGems) vs. sample code.
- 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.
| Provider | Embed Approach | SDK Coverage | Compliance | Pricing Model | Free Tier |
|---|---|---|---|---|---|
| Dropbox Sign | iframe (hellosign-embedded) | 6 official SDKs (Python, Node, PHP, .NET, Ruby, Java) | ESIGN, UETA, GDPR | Self-serve | Yes (test mode) |
| SignWell | iframe | 4 official SDKs (Ruby, Node, Python, PHP) | ESIGN, UETA | Self-serve, published pricing | Yes |
| BoldSign | iframe | REST + official SDKs | ESIGN, UETA, GDPR | Self-serve | Yes (free sandbox) |
| Documenso | iframe (embed SDKs for React, Vue, Svelte, Angular, Solid, Preact) | 6 framework-specific embed SDKs | ESIGN, UETA, GDPR | Self-serve / self-host | Free (self-host) |
| Foxit eSign API | iframe (embeddedSessionURL) | Sample code in cURL, PHP, C#, Java; Postman collection | eIDAS (AES, QES), ESIGN, UETA, 21 CFR Part 11, HIPAA, GDPR, CCPA | Self-serve trial, then pricing at developer-api.foxit.com | 30-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 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’s pricing page, showing published self-serve rates rather than a sales-gated quote.
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’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’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.
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: truein 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
What is an embedded signing API?
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.
Why do developers look for Adobe Sign API alternatives?
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.
Does Foxit eSign API support iframe-based embedded signing?
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.
How do I choose an e-signature API for embedded signing?
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.
What compliance standards do embedded signing APIs typically cover?
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.
Can I test an embedded signing API before committing to a paid plan?
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.
What is the difference between a true iframe embed and a redirect-based signing flow?
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

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’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 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.
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’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.
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’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
| Criterion | Foxit eSign | DocuSign | Adobe Acrobat Sign | Dropbox Sign | SignNow | BoldSign |
|---|---|---|---|---|---|---|
| In-app embedding | Signer stays in your app. iframe URL from createEmbeddedSigningSession plus embeddedSignersEmailIds or createEmbeddedSigningSessionForAllParties | Signer stays in your app. Recipient view URL in an iframe, with auth-method and mobile full-screen caveats | Redirect to Adobe’s domain by default. Signing URL can be iframed, with more configuration | Signer stays in your app. URL from the API, with hellosign-embedded managing the iframe | Signer stays in your app. iframe URL via the embedded-invites endpoint | Signer stays in your app. iframe URL from the Get Embedded Signing Link API |
| Auth approach | OAuth2 client-credentials, POST-only form-encoded endpoint | OAuth2 authorization code and JWT grant | OAuth2 authorization code and refresh token | OAuth2 authorization code, with API key for server-to-server | OAuth2 authorization code and password grant | API key or OAuth2 access token |
| Webhook granularity | Envelope lifecycle events with HMAC secret-key verification | Full envelope and recipient lifecycle with HMAC verification | AGREEMENT, WIDGET, and MEGASIGN resource types, scoped at account, group, user, or resource level | Full signature request lifecycle with HMAC-SHA256 signature | Document lifecycle events | Document lifecycle events |
| Client libraries | REST API with cURL, PHP cURL, .NET, and Java samples, plus a Postman collection whose two envelope-creation bodies need JSON repair. No language SDK | C#, Java, Node.js, PHP, Python, Ruby, iOS, Android | C#, Java, JavaScript, OpenAPI, REST, all marked obsolete by Adobe and out of sync with REST v6 | C#, Java, PHP, Python, Ruby, Node.js (OpenAPI-generated) | PHP, Python, Java, .NET, JavaScript | .NET, Java, Node.js, PHP, Python |
| Compliance certifications | eIDAS 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 Cloud | SOC 2 Type 2, ISO 27001, eIDAS, ESIGN, UETA, HIPAA via BAA (plan-dependent), FedRAMP Moderate on Acrobat Sign for Government | SOC 2 Type 2, eIDAS, ESIGN, UETA, HIPAA via BAA (plan-dependent) | SOC 2 Type 2, eIDAS, ESIGN, UETA, HIPAA, GDPR | SOC 2 Type 2, eIDAS, ESIGN, UETA, GDPR, data residency in US, EU, CA, AU. HIPAA from the Business plan upward |
| Pricing model | Consumption and credit-based, shared across eSign, PDF Services, DocGen, and Embed, at 5 credits per envelope | Per-seat and per-envelope (tier-dependent) | Per-seat, negotiated. No standalone Acrobat Sign pricing page is published | Volume-based monthly tiers keyed to signature-request count | Volume-based. Unlimited users on every tier, with full API access on Site License at $1.50 per signature invite | Per-envelope for the API product at $0.75, from $30 per month with 40 envelopes. Web app tiers are per-seat |
| Free developer tier | Yes. 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 envelopes | Yes. Free developer account, no credit card, demo environment rather than production | Yes. Acrobat Sign Developer Edition, free, with API access for testing document exchange and execution | Yes. The API is free in test mode, with paid tiers required for production traffic | Yes. Free trial on the self-serve tiers, with full API access gated to the Site License tier | Yes. 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
Which of these e-signature APIs support HIPAA compliance?
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.
Does Foxit eSign offer a free developer plan?
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.
Can I test an e-signature API integration without a credit card?
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.
What auth grant type should a server-side product integration use?
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

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.
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:
- Python 3.9 or newer and pip
- A virtual environment via venv, so the dependency below stays isolated
- The requests library for HTTP calls
- A code editor such as VS Code with the Python extension
- A free Foxit developer account
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.
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.
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
FAILEDso a rejected document raises instead of spinning forever. - Once the status reads
COMPLETED, downloads the ZIP archive using the task’sresultDocumentIdand readsStructureInfo.jsonout of it in memory.
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.
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}/downloadreturns 404. - Reading
cellsoff the table element. The grid is nested atcontent.body.cells. A top-levelcellslookup raisesKeyError. - Treating
paragraph.contentas a string. It is an object, so the text is atparagraph.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
paragraphobject but carries nocontentkey. - Polling without a bound. A
FAILEDtask never becomesCOMPLETED, so an unboundedwhile Trueloop hangs. - Trusting
info.basicInfo.elementCounts. It can disagree with the length of theelementsarray, so size loops from the array itself. - Using
.strip()to clean cell text. A wrapped label contains a mid-string\r\nthat.strip()leaves in place. Use" ".join(text.split()).
Invoice parsing FAQ
What is invoice data extraction from PDF?
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.
How do I extract line items from a PDF invoice?
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.
Can an extraction API handle scanned PDF invoices?
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.
What does the JSON output from PDF invoice extraction look like?
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.
Why build a grid instead of iterating the cells array directly?
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.
What element types does the API classify?
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.
How should I handle invoices where footer totals sit outside the line-item table?
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

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.
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:
| Dimension | Google Document AI | Foxit PDF Structural Extraction API |
|---|---|---|
| Account setup | GCP project, service account, IAM role, billing | Free developer account, no credit card |
| Authentication | Service account JSON key via GOOGLE_APPLICATION_CREDENTIALS | client_id and client_secret as HTTP headers |
| Call pattern | Synchronous or async depending on processor | Four-step async (upload, extract, poll, download) |
| Output format | Document proto (blocks, paragraphs, tokens) | StructureInfo.json with 12 semantic element types |
| Cloud dependency | GCP-native; Vertex AI integration available | Cloud-agnostic, any stack |
| Language coverage | Varies by processor | 200+ 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: Bearerheader : PDF Services authenticates with two separate lowercase headers,client_idandclient_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-extractreturns HTTP 202 with ataskId, never the result. Read the status fromGET /tasks/{taskId}, which also returns aprogresspercentage, 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 onFAILEDand caps its attempts. - Reading
region.boundingBoxon every element : text elements such astitle,head, andparagraphcarryregionas{page, boundingBox}, but atableelement returns an emptyregionand puts its geometry in aregionsarray instead. A loop that assumes one shape raisesKeyErroron the first document containing a table. - Indexing
elementsat the JSON root : every result nests underanalyzeResult, sodata["elements"]raisesKeyErrorwhiledata["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 dimension | Google Document AI (Document proto) | Foxit (StructureInfo.json) |
|---|---|---|
| Structure model | Hierarchical, covering pages, blocks, paragraphs, and tokens | Flat list of semantically typed elements with spatial metadata |
| Semantic labels | Field-level labels tied to specific processor selection | 12 fixed element types, processor-independent |
| Table representation | Cell tokens within a table block | Dedicated table element type with cell grid coordinates |
| Reading order | Implicit (coordinate ordering required client-side) | Explicit, preserved in element sequence |
| Formula support | Limited | Dedicated 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.
| Scenario | Best fit | Key reason |
|---|---|---|
| Teams already deep in GCP who want Vertex AI integration | Google Document AI | Native Vertex AI pipeline support and Google-managed processor versions reduce ops overhead |
| High-volume PDF processing outside the GCP ecosystem | Foxit | Cloud-agnostic with credit-based pricing, with no GCP billing or IAM dependency |
| Workloads with strict data residency or BAA requirements | Foxit | SOC 2 Type II audit, HIPAA-aligned features with BAA support, and GDPR-supporting features |
| Teams that need semantic element classification ready for downstream consumption | Foxit | StructureInfo.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:
- Upload the PDF to
/pdf-services/api/documents/uploadand receive adocumentId. - POST to
/pdf-services/api/documents/pdf-structural-extractwith thedocumentIdand receive ataskId. - Poll
/pdf-services/api/tasks/{taskId}every two seconds untilstatusequalsCOMPLETED. - Download the result ZIP from
/pdf-services/api/documents/{resultDocumentId}/downloadand parseStructureInfo.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
What is Google Document AI?
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.
How does the Foxit PDF Structural Extraction API differ from Google Document AI?
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.
What are Foxit’s data-handling and compliance commitments for the extraction API?
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.
What output format does the Foxit PDF Structural Extraction API return?
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.
Can I try the Foxit PDF Structural Extraction API without a paid plan?
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.
How does Foxit handle scanned or image-based PDFs?
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.
Which document types does Google Document AI support after the June 2026 deprecations?
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

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.
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.
| Tool | Auth | Embedded signing | Webhooks | Official SDKs | Pricing model |
|---|---|---|---|---|---|
| Dropbox Sign | OAuth2 + API key | Iframe via sign_url | Signer-level events | Python, Node, Java, Ruby, PHP | API tier, published |
| Adobe Acrobat Sign | OAuth2 | Transient docs + widgets | Extensive | Java, plus REST | Enterprise, sales-led |
| PandaDoc | OAuth2 + API key | Iframe (send + sign) | Document lifecycle | Node, Python, plus REST | Document/seat-based |
| SignNow | OAuth2 + API key | Embedded session URLs | Functional, coarser | Fewer official SDKs | Per-envelope, published |
| BoldSign | OAuth2 + API key | Iframe embedded | Documented events | .NET, Java, Node, Python | Tiered, transparent |
| Foxit eSign | OAuth2 client credentials | Iframe / web view, no redirect | 9 events incl. folder_executed, HMAC-signed | REST, examples in Python | Tiered, 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_urlyou 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’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.
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.
See the developer portal and pricing.
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.
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.
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
What is an eSignature API?
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.
What are the best DocuSign API alternatives for developers in 2026?
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).
Does the Foxit eSign API support embedded signing?
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.
What compliance standards does the Foxit eSign API meet?
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.
How do I get started with the Foxit eSign API?
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.
How does OAuth2 client credentials auth work in the Foxit eSign API?
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.
What webhook events does the Foxit eSign API support?
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.
How does Foxit eSign handle parallel and sequential signing flows?
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.
Which eSign API is best for regulated industries?
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.
What is the difference between envelope-based and consumption-based eSign pricing?
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

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
- Python 3.8+ with pip and a virtual environment via venv.
- The requests library for the HTTP calls.
- curl if you want to try the endpoints before writing code.
- A code editor, VS Code with the Python extension is a good default, though PyCharm or Sublime Text work equally well.
- A Foxit Developer account, free with no credit card, created at app.developer-api.foxit.com/sign-up. Activate the Developer plan (500 credits per year) and copy the Client ID and Client Secret from the APIs Dashboard.
- A sample PDF, so you do not have to build one. This tutorial uses invoice_table_test.pdf, a one-page invoice with a five-column line-item table.
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.
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.
The four calls and what each one hands to the next. The id you download with comes from the finished task, not the upload.
- Upload the PDF and receive a
documentId. - Start the analysis against that id and receive a
taskId. - Poll the task until its
statusreachesCOMPLETED, which also returns aresultDocumentId. - 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_idandclient_secret.ClientIdandClientSecretdo not authenticate, and neither does anAuthorization: Bearerheader, which returns a 400. - Comparing status to a lowercase string : task statuses are uppercase, so test against
COMPLETEDandFAILED. - Downloading with the upload id : the download path takes the
resultDocumentIdfrom the completed task, not thedocumentIdfrom the upload. - Reading
elementsfrom the root : the array is nested underanalyzeResult, sostructure["analyzeResult"]["elements"]is the path. - Expecting
bboxor a rows array : positions arrive asregion.boundingBoxwith eight numbers, and tables arrive ascontent.body.cellswith 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.jsonentry 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
What element types does the structural extraction return?
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.
How is this different from raw text extraction for tables?
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.
Is the extraction synchronous?
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.
What does the download actually contain?
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.
What schema version does the output use?
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.
Does a failed task tell me why?
The task object surfaces the failure state in status as FAILED, so branch on that and log the whole task body when it happens.
Can I extract several documents at once?
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

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.
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:
- 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.
- Open the Dashboard and select Get started with eSign in the Dashboard header. That takes you to the eSign activation page.
- 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.
- 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.
- 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_secretyou 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. - (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.
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.
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.
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, andsendNowwork, whilefolder_name,document_url,email, andsend_nowdo 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
partiesentry : 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 forfolder_executed. - Caching an
embeddedSessionURL: sessions expire. Mint one when the signer is ready, and regenerate when needed. - Trimming the iframe
sandboxlist : removingallow-popupsorallow-top-navigationbreaks 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
What is an eSignature API?
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.
What is embedded signing and why does it improve the user experience?
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.
How do Text Tags relate to the parties in the API call?
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.
What is the difference between folder_completed and folder_executed?
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.
How do I verify a webhook actually came from Foxit?
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.
Can one folder hold several documents?
Yes. Pass additional entries in fileUrls with matching fileNames. Each document carries its own Text Tags, and party assignments stay consistent across the folder.
Do I need a paid plan to build this?
No. The free tier gives you credentials and lets you run the full flow, and no credit card is required to create the account.
Can I review a document before it reaches signers?
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.