8 Best HelloSign (Now Dropbox Sign) API Alternatives for Developers in 2026

Dropbox Sign puts embedded signing behind its $250 a month Standard plan and white labeling behind a custom quote. This guide compares 8 HelloSign API alternatives on pricing, billing triggers, webhooks, and SDKs. You also get a migration checklist to scope the work before you switch.
Dropbox Sign API (formerly HelloSign) puts embedded signing behind its Standard plan, at $250 a month billed yearly for 100 signature requests a month. White-label branding requires Premium and a custom quote.
If those plan gates or growing signing volume no longer fit your product, switching APIs means more than changing a URL.
In this article, we compare 8 Dropbox Sign API alternatives based on their features, pricing, usage limits, and migration requirements. None was verified as a drop-in replacement for Dropbox Sign, so plan to rewrite requests, callbacks, and the embedded client.
Top picks by reason for switching
For PDF generation followed by signing, our eSign API keeps both steps on one account. The other picks below address pay-per-use billing, prepaid credits, white labeling, self-hosting, and government requirements.
If you are leaving because… | Pick | Why it fits |
You generate, convert, or merge PDFs before signing them | Foxit eSign API | Embedded signing and sending on every plan, with 5 credits per envelope from the same pool as PDF Services and Document Generation |
Per-request cost at moderate volume | BoldSign API | $30 a month including 40 envelopes, then $0.75 per envelope sent, with embedded signing and templates on the API plan |
You want no monthly minimum | SignWell API | Per-document pricing starting at $0.85 and falling with volume, down to $0.20 or lower for high-volume or custom plans, and the first 25 documents a month are free with a card on file |
Quotas that reset every month | Zoho Sign API | $0.35 per API envelope (5 credits at $0.07), bought in blocks, and credits do not expire while the Zoho Sign account remains active |
White-label signing without an enterprise quote | SignNow API | Professional includes embedded signing and sending and white labeling, from $84 a month billed annually for 500 invites a year |
Paying for signatures that never complete | DocuSeal | $0.20 per completed document plus a $20/month Pro seat, with a self-hostable AGPLv3 core carrying additional attribution terms |
Per-request pricing altogether | Documenso | Flat cloud plans under a fair-use policy: Teams at $40/month billed yearly for internal workflows, and Platform at $250/month billed yearly for embedding in an external product |
Enterprise procurement or government requirements | Docusign eSignature REST API | Federal eSignature and CLM offering with reported FedRAMP Moderate and provisional DoD IL4 authorization; embedded signing requires an Enhanced developer plan. |
Most of these APIs support embedded signing, templates, and webhooks. The entries below explain the plan limits and migration work that distinguish them.
If your product generates PDFs before sending them for signature, you can test that flow on the free Foxit Developer plan while you compare the other options below.
What the Dropbox Sign API gives you today
The table below separates annual and monthly billing for Dropbox Sign’s API plans.
Plan | Signature requests a month | Billed yearly | Billed monthly | What the plan adds |
Essentials | 50 (tiers at 100, 250, 500) | $900 a year ($75/month) | $100 a month | Non-embedded sending; 5 templates |
Standard | 100 (tiers at 250, 500) | $3,000 a year ($250/month) | $300 a month | Embedded signing and requesting, bulk send, OAuth, branding; 15 templates |
Premium | From 100 | Custom quote | Custom quote | Embedded templates, premium branding (white-label), data residency, multiple domains; QES as an add-on |
Test mode | Not applicable | Free | Free | Watermarked, not legally binding, 10 requests a minute |
Standard 250 runs $5,400 a year or $540 billed monthly. Standard 500 runs $7,500 a year or $750 monthly. Essentials 500 is $5,400 a year.
What counts as one signature request. One bundle of documents sent together, regardless of the number of signers. Embedded and template sends count. Drafts count only when the sender continues.
Quota behavior. A request made after you exceed your quota returns HTTP 402 immediately, the same code used for an unpaid or under-credited account. Separately, exceeding your quota also automatically upgrades your subscription to the next plan tier, with a warning at 80 percent usage, so the account is never permanently capped. A request right at the threshold can still be 402 before the upgrade takes effect, but you are not locked out afterward.

Rate limits. 100 requests a minute for standard calls; 25 a minute for sending, embedded creation, and file downloads; 10 a minute in test mode.
The callback contract. Respond with HTTP 200 and the literal body Hello API Event Received. Verify the event_hash HMAC. Expect up to 6 retries. The callback URL is cleared after 10 consecutive failures.
Changes to account for in an existing integration.
- HTTPS-only callbacks since December 1, 2024
- An April 24, 2024 security incident that forced API key and OAuth token rotation for affected accounts
- An October 6, 2026 change that rejects duplicate signer emails in grouped_signers requests
Before you compare anything, list what your integration uses today, including embedded signing, embedded requesting, templates, bulk send, OAuth, and branding. That list decides the plan you need everywhere else.
How we compared these alternatives
We compare each API on 8 criteria: coverage, embedded signing and plan requirements, pricing and billing unit, free and test use, webhooks, templates and fields, SDKs and limits, and migration work. Compliance claims are vendor-reported.
Vendors name the same object differently. Dropbox Sign calls it a signature request. BoldSign and SignWell call it a document. SignNow calls it a document plus an invite. DocuSeal calls it a submission. Documenso and Docusign call it an envelope. Foxit’s endpoint paths use “folder” for what the product surface calls an envelope. Each entry names the vendor’s own object on first use and notes when billing fires.
The 8 Dropbox Sign API alternatives in detail
Use each entry’s “Moving from Dropbox Sign” row to identify the equivalent objects and the main code changes.
Foxit eSign API
We build and maintain the Foxit eSign API. We compare it using the same criteria as the other alternatives.
Our eSign API sends envelopes (called “folders” in endpoint paths) for electronic signature, with embedded signing and sending available on every plan. One Developer Portal account covers eSign, PDF Services for PDF processing, and Document Generation API (DocGen API), with a shared credit pool.
Feature | Details |
Covers | Envelopes from PDF URLs or Base64 files, templates, ordered recipients, reminders, cancellation, activity history, and completed-file download. PDF Services and Document Generation on the same account and credits. Cloud only, with storage region chosen at eSign activation. |
Embedded signing | Embedded signing (one or all recipients) and embedded sending views on every plan, including the free Developer plan. The API also includes an endpoint to regenerate expired signing sessions, plus UI options including theme color and hiding controls. |
Pricing | See the Foxit API pricing page. 5 credits per envelope created through the API or the eSign UI; PDF Services and Document Generation operations cost 1 credit each from the same pool. Status checks, webhooks, uploads, and downloads use no credits. Running out of credits returns HTTP 429 with a QUOTA_EXCEEDED error code. Credits reset yearly and do not roll over. |
Free and test use | eSign trial access on the free Developer plan, which includes 500 shared credits a year. |
Webhooks | Webhook channels for API envelopes or the whole account, with events including folder_sent, folder_viewed, folder_signed, folder_cancelled, folder_completed, folder_executed, and folder_deleted. Optional HMAC-SHA-256 signature arrives as a query parameter. |
Templates and fields | Templates created through the API, with fields from text tags in the PDF or from x/y field objects. AcroForm fields can convert, and signer authentication is available by email, SMS, or voice access codes. |
SDKs and limits | REST with OAuth 2.0 client credentials and request samples for Shell, Ruby, Node.js, PHP, and Python. No separately installable eSign SDK was verified for this API version. Limits are 15 requests a minute in sandbox mode and 100 in production, per application. |
Moving from Dropbox Sign | A signature request becomes an envelope (folders/createfolder with sendNow: true), and the embedded URL comes back from envelope creation rather than a separate sign_url call. Callbacks become webhook channels verified by HMAC rather than answering Hello API Event Received, and text tags must be rewritten in Foxit syntax (e.g., ${signfield:1:y:name}). |
Where Foxit eSign API stands out
Embedded signing and sending are available on every plan. If you already generate PDFs with our PDF Services or Document Generation API, you can add signing under the same account and credit pool.
For workflow examples, see our n8n, Power Automate, and contract lifecycle management tutorials. Those tutorials use an older API setup, so follow the current API reference for authentication and endpoints.
Envelope creation returns embeddedSessionURL directly. If a session expires before a signer opens it, you can regenerate the session without creating another envelope.

What to consider
Our credits reset annually, so budget envelopes alongside PDF processing and generation. Startup’s 5,000 credits cover up to 1,000 envelopes a year, or about 83 a month, if no other operations use the pool.
This is our calculation, not a monthly allowance. Bulk sending is advertised on our eSign product page but has no documented operation in the current API reference; confirm API support with our team before relying on it.
Our eSign API is covered by SOC 2 Type II audits, meets ESIGN/UETA requirements, and supports eIDAS SES, AES, and QES. QES requires identity-validation services. We also support 21 CFR Part 11-compliant signature flows; confirm requirements for your workflow.
Best for
Products that generate or process PDFs and then send them for signature, where running both operations on one account and one credit pool reduces integration overhead.
Getting started
The Foxit eSign API reference covers envelope creation, text tags, and webhooks. You can send a test envelope from your own PDF on the free Developer plan.
BoldSign API
BoldSign is an eSignature platform with a dedicated API plan that includes embedded signing from the entry tier.
Feature | Details |
Covers | Documents (envelopes) with templates, embedded requesting, signing and template creation, custom fields, sender identities, text tags, and scheduled sends. Data centers in the US, EU, Canada, and Australia. |
Embedded signing | Included on the Enterprise API plan, which starts at $30 a month. |
Pricing | $30 a month including 40 envelopes, then $0.75 per envelope sent. Volume quotes on request. Bulk send, SMS delivery, ID verification, KBA, and QES are listed as add-ons. |
Free and test use | Free sandbox with full API access, with test documents watermarked and deleted after 14 days, and 50 requests an hour in the sandbox. |
Webhooks | Sent, Viewed, Signed, Completed, Declined, Expired, Revoked, and other events, with HMAC-SHA256 in the X-BoldSign-Signature header. Respond with 200 within 10 seconds, and expect 6 retries. |
Templates and fields | Templates with roles, text tags, and embedded template creation. |
SDKs and limits | .NET, Python, PHP, Java, and Node.js; 2,000 requests an hour in production per account. |
Moving from Dropbox Sign | A signature request becomes a document, and sign_url becomes an embedded signing link. Callbacks become HMAC-signed webhooks. No Dropbox Sign template importer was verified in the BoldSign sources reviewed. |
Where BoldSign API stands out
Embedded signing and embedded templates are available on the entry API plan, which means the $250 Standard gate from Dropbox Sign doesn’t carry over. BoldSign also reverses charges for revoked documents, which distinguishes it from vendors that bill on send regardless of outcome.
The sandbox provides full API access with watermarked documents, so you can build and verify an integration before any cost is incurred. Plan around the hourly request limit when your application sends requests in bursts.

What to consider
Envelopes are billed when sent. Revoked documents are reversed, but declined documents remain billed. BoldSign’s comparison page uses “no overages” language, though its plan detail lists bulk send, SMS, ID verification, KBA, and QES as add-ons that carry separate costs.
The sandbox limit of 50 requests an hour is tighter than production and may slow integration testing at pace. No Dropbox Sign template importer was documented in the sources reviewed, so templates need to be rebuilt in BoldSign.
Best for
SaaS products at moderate volume that need embedded signing on an entry-level API plan.
Getting started
The BoldSign free sandbox is available at the BoldSign developer portal.
SignWell API
SignWell is a document signing platform that structures its API pricing around per-document usage with no monthly minimum.
Feature | Details |
Covers | Documents with any number of files and signers, templates, embedded signing, requesting and template editing, bulk send, and white-label embedding. |
Embedded signing | Included in API usage pricing. |
Pricing | Per API document, starting at $0.85 and decreasing with monthly volume. A live pricing calculator on SignWell’s own API pricing page shows the rate holding at $0.85 through roughly 200 documents a month, then dropping to $0.60 and holding there through at least 475 documents a month; SignWell’s published FAQ states the rate continues falling to $0.20 or lower for high-volume or custom plans, though the exact breakpoints above 500 documents a month aren’t published. 10% off when paid yearly. |
Free and test use | First 25 API documents a month are free with a card on file. A free developer account without a card gets up to 3 a month. Unlimited test mode. |
Webhooks | document_sent, document_viewed, document_signed, document_completed, document_declined, and other events, verified with HMAC-SHA256 keyed by the webhook ID. A webhook retry schedule was not found in the documentation reviewed. |
Templates and fields | Templates, with documents created from them through the API. |
SDKs and limits | An OpenAPI file, a CLI, and an MCP server, documented on SignWell’s developer resources page; no official Ruby, Node, Python, or PHP client libraries were confirmed in the current documentation. 100 requests a minute per token, 30 for document and template creation, 20 in test mode. |
Moving from Dropbox Sign | A signature request becomes a document, and embedded signing and requesting move to SignWell’s iFrames. Callbacks become webhooks with event-hash checks. No Dropbox-specific migration tooling was verified in the SignWell sources reviewed. |
Where SignWell API stands out
With a card on file, the first 25 API documents each month are free. For uneven volume, pay-per-document billing avoids buying a monthly request allowance you may not use.
The MCP server and CLI are useful for development and automation workflows beyond standard API integration. Embedded signing and white-label embedding are included without a plan upgrade.

What to consider
The free 25 requires a card on file, while the developer account without a card covers only 3 documents a month.
A webhook retry schedule was not documented in the sources reviewed, so plan to verify failure handling behavior before going live.
The published per-document rate below $0.60 applies to high-volume or custom agreements rather than a clearly published self-serve tier, so get a quote before modeling costs much past 500 documents a month.
Best for
Products with low or uneven volume that don’t want a monthly minimum.
Getting started
The SignWell API getting started guide is available at signwell.com.
Zoho Sign API
Zoho Sign offers an API-only plan built around prepaid credits, with no subscription fee or per-user charge.
The plan supports embedded sending and signing rather than app-based signing; branding and webhooks are still configured in the app.
Feature | Details |
Covers | An API-only plan with embedded sending and signing, templates, signer authentication (OTP, ID verification, KBA), and AI field detection. Branding, custom domains, white labeling, and webhooks are configured in the app. Signing through the app interface is disabled on this plan. |
Embedded signing | Included on the API-only plan. |
Pricing | Credits at $0.07 each, bought in blocks of 500 for $35. Each envelope sent through the API uses 5 credits ($0.35). SMS, authentication, and trust services use extra credits. No subscription or per-user fee. |
Free and test use | A 14-day Enterprise trial with up to 10 trial documents. Subscribers can send up to 50 watermarked test documents a month with testing=true. |
Webhooks | Configured in the Zoho Sign app, with optional HMAC-SHA256 verification using the X-ZS-WEBHOOK-SIGNATURE header. |
Templates and fields | Templates with up to 25 recipients and 40 documents per request, and bulk send up to 1,000 recipients per request. |
SDKs and limits | REST API with most calls limited to 50 a minute, template export to 2 a minute, and template import to 1 a minute. |
Moving from Dropbox Sign | Rebuild templates and field placement in Zoho Sign. Plan around the 50-calls-a-minute threshold. Credits don’t reset monthly, while Dropbox Sign quotas reset monthly by default, with yearly refresh available for an additional cost. |
Where Zoho Sign API stands out
Prepaid credits that don’t expire while the account remains active solve the month-end quota problem. A slow month doesn’t waste quota, and an unusually busy month draws down the same pool without triggering a permanent plan upgrade.
For batch sends, consider the 1,000-recipient limit alongside the 50-calls-a-minute API limit. AI field detection can reduce template setup work for complex layouts.

What to consider
The 50-calls-a-minute limit may require a queue for bursty workloads.Webhooks and branding are configured through the Zoho Sign app interface rather than through API calls, which adds a setup step outside the API integration. The trial is 14 days with 10 documents, giving limited time to validate a full flow.
Best for
Irregular volume paid from a prepaid budget, where month-to-month fluctuations make subscription tiers inefficient.
Getting started
The Zoho Sign API documentation and pricing are available at zoho.com/sign.
SignNow API
SignNow is an eSignature platform that includes white labeling in its self-serve Professional plan.
Dropbox Sign restricts premium branding to its custom-quote Premium tier, so SignNow removes that gate for teams that need a fully branded embedded signing experience without an enterprise sales cycle.
Feature | Details |
Covers | Documents and document groups with invites, templates, embedded signing and sending, white labeling, document generation, and bulk invites on the Professional plan. |
Embedded signing | Included on Professional. |
Pricing | Professional, billed annually: $84 a month (500 invites a year), $146 (1,000 invites), $250 (2,000 invites), and $500 (5,000 invites). For role-based document, document-group, and embedded invites, multiple signers in one invite count once and billing fires on send. A standalone document or document-group signing link is different: it bills once the last signer completes, not when the link is generated. Bulk and freeform invites have their own counting rules too. |
Free and test use | Watermarked Dev Mode. The pricing page advertises unlimited test invites, but API docs cap accounts without an active plan or trial at 500 invites. With an active plan or trial, Development invites do not consume the plan allowance. The live trial includes 250 invites. |
Webhooks | Event subscriptions for documents and invites, signed with your own secret in the X-SignNow-Signature header. |
Templates and fields | Templates, text tags, role-based invites, and prefilled fields. |
SDKs and limits | PHP, .NET, Java, Python, Node.js, and Android. Default 1,000 requests an hour per application in production, 500 in development. |
Moving from Dropbox Sign | A signature request becomes a document (or a group) plus an invite, and sign_url becomes an embedded invite link. Monthly request quotas become yearly invite pools. |
Where SignNow API stands out
White labeling is included in a self-serve Professional plan starting at $84 a month billed annually. Yearly invite pools also work well for products with predictable annual volume that doesn’t track neatly to calendar months.
A product sending contracts on renewal cycles often benefits more from an annual pool than a monthly quota that resets mid-campaign.

What to consider
Invites are counted yearly, and the plan grows with usage, so plan carefully around the counting rules for different invite types, especially signing links, which bill on completion rather than on send like every other invite type.
The default limit is 1,000 requests an hour, so budget calls across the full signing flow rather than treating that as a per-minute allowance.
Best for
Branded in-app signing at a predictable annual volume, where white labeling without a custom quote is a requirement.
Getting started
The SignNow developer portal and API documentation are available at signnow.com.
DocuSeal
DocuSeal is an open-source eSignature platform with an AGPLv3 core carrying additional attribution terms, and a paid cloud or on-premises Pro option. Its API covers template-based form submissions with a front-end embedding component.
Feature | Details |
Covers | Templates and submissions with a form builder, embedded signing through a web component plus React, Vue, and Angular wrappers, an embedded form builder, and an AGPLv3 open-source core with additional attribution terms, run in DocuSeal’s cloud (global or EU) or on-premises with Pro. |
Embedded signing | Pro, in the cloud or on-premises. |
Pricing | Pro seat: $20 per user a month or $200 a year, plus $0.20 per completed document for API and embedded signing. On-premises Pro: $240 per user a year. Cloud add-ons: 20 SMS per month included, then $0.20 per SMS; ID verification $2 per verification; QES $2 to $4 per signature. On-premises SMS uses your own provider. |
Free and test use | Free, unlimited test mode in a separate account with its own API keys. |
Webhooks | Form, submission, and template events (form.viewed, form.started, form.completed); HMAC-SHA256 verification through the X-Docuseal-Signature header. |
Templates and fields | Text tags in the form {{Field Name;role=Signer1;type=date}}; documented template import from DocuSign. A Dropbox Sign importer was not verified. |
SDKs and limits | Front-end embedding components plus JavaScript/TypeScript, Python, Ruby, PHP, Java, C#, and Go API libraries. A numeric API rate limit was not published as of this check. |
Moving from Dropbox Sign | A signature request becomes a submission, and a signer becomes a submitter. Rebuild templates and replace the hellosign-embedded client with DocuSeal’s component. |
Where DocuSeal stands out
The $0.20 usage charge applies only to completed documents, so abandoned requests don’t incur that per-document fee. The Pro seat fee still applies. Self-hosting gives you control of the infrastructure, with paid API and embedding requirements to factor in.
The front-end embedding components for React, Vue, and Angular reduce the integration surface compared to building a raw iframe wrapper, and the separate test-mode account with its own API keys keeps test and production environments cleanly separated.

What to consider
A Pro seat is required for API access and embedding, so a $20 monthly seat fee applies even at low volume. The AGPLv3 core carries additional attribution terms that legal should review before self-hosting.
A numeric API rate limit was not published in the sources reviewed, so test throughput limits during evaluation. DocuSeal documents template import from DocuSign, but a Dropbox Sign importer was not verified in the sources reviewed.
Best for
Teams that prefer completion-based usage charges and need a self-hosting option.
Getting started
The DocuSeal documentation and free test mode are available at docuseal.com, and the open-source repository is on GitHub.
Documenso
Documenso is an AGPL-3.0 open-source document signing platform with hosted plans and a self-hosting option.
Its API is transitioning from individual document and template endpoints to a unified envelope model.
Feature | Details |
Covers | Documents and templates unified under an “envelopes” model, with embedding SDKs for React, Vue, Svelte, Angular, Solid, and Preact, plus server SDKs for TypeScript and Python (with a Go SDK also listed), all on an AGPL-3.0 codebase available hosted or self-hosted. |
Embedded signing | Teams and above for internal workflows; Platform for an external commercial product beyond moderate testing. White-label embedding on Platform; the embedded editor on Enterprise or as a Platform add-on. |
Pricing | Flat plans billed yearly with unlimited documents under a fair-use policy: Individual $25 a month, Teams $40 (5 users, then $10 a user), Platform $250. |
Free and test use | A free plan with 5 documents a month; a separate staging environment for API testing. |
Webhooks | The X-Documenso-Secret header carries your plain secret for verification. Comparing it in constant time is required; it’s a plain string comparison, not an HMAC signature. |
Templates and fields | Templates with recipients and fields; the older document and template creation endpoints are deprecated in favor of envelopes, with a documented cutover date for removing the legacy endpoints. |
SDKs and limits | 1,000 requests a minute per IP is the global ceiling across the whole API. An organization’s own plan can carry a separate, lower limit per user or API token, which triggers a 429 before the global ceiling is ever reached. |
Moving from Dropbox Sign | A signature request becomes an envelope, so build against the new envelope endpoints rather than the deprecated document and template endpoints. |
Where Documenso stands out
Flat subscriptions with unlimited documents under the fair-use policy make cost predictable regardless of volume fluctuations within the plan’s intended use case.
Teams sending hundreds of internal agreements a month pay $40 a month on the Teams plan.The embedding SDK library covers 6 front-end frameworks, which reduces integration work for products with diverse front-end stacks.
The open-source AGPL-3.0 Community Edition gives engineering teams the option to inspect the full signing stack, contribute to the project, or self-host for infrastructure control.

What to consider
The fair-use policy on unlimited documents is real; high volume or commercial embedding in an external product requires the Platform plan at $250 a month.
The webhook verification uses a plain secret string rather than HMAC, so constant-time comparison is essential to avoid timing attacks. The API is in transition, with older endpoints deprecated, so building against the new envelope endpoints now avoids a future forced migration.
Design for the lower of the two: the 1,000-requests-a-minute global per-IP ceiling, and whatever organization-level limit your plan carries, since either can trigger a 429 first.
Best for
High volume on a fixed budget, or teams that want to inspect or own the full stack.
Getting started
The Documenso documentation, staging environment, and open-source repository are available at documenso.com and on GitHub.
Docusign eSignature REST API
Docusign offers an eSignature API and a separate federal offering for government requirements.
The federal offering is a reason to consider it when procurement requirements rule out simpler self-serve options.
Feature | Details |
Covers | Envelopes, templates, PowerForms, API bulk send (Advanced plan and above), and Connect webhooks. A segregated government-cloud offering through Docusign Federal. |
Embedded signing | Enhanced plans only, which are sales-led with a 5-user minimum. Embedded sending is available on every plan. |
Pricing | Starter $50 a month billed annually or $75 monthly, from 40 envelopes a month; Intermediate $300 or $450, from 100 envelopes a month; Advanced $480 or $720, from 100 envelopes a month with higher-tier features; Enhanced by quote. An envelope counts once sent, whether or not it is signed. |
Free and test use | A free developer account in the demo environment; production requires a go-live review. |
Webhooks | Docusign Connect at account and envelope level, with an HMAC option. Recipient Connect, for events on envelopes received by your organization’s users, is included from the Advanced plan. |
Templates and fields | Templates with recipient roles; tabs placed by coordinates or anchor text. |
SDKs and limits | C#, Java, Node.js, PHP, Python, and Ruby SDKs. Default 3,000 requests an hour per account. Bursts of 500 calls per 30 seconds in production (200 in the developer environment). At most one GET status check per envelope per application every 15 minutes. |
Moving from Dropbox Sign | A signature request becomes an envelope, and a signer becomes a recipient. Callbacks move to Connect, and you’ll need to plan for the Enhanced tier if your product embeds signing today. |
Where Docusign eSignature REST API stands out
Docusign reports FedRAMP Moderate authorization and provisional DoD IL4 authorization for its federal eSignature and CLM offerings. Check Docusign Federal’s authorization scope against your agency’s requirements before choosing a deployment.
The SDK coverage across C#, Java, Node.js, PHP, Python, and Ruby is broad, and the demo environment with full API access gives developers a complete integration surface before go-live review.

What to consider
Embedded signing requires a sales-led Enhanced plan with a 5-user minimum, so you can’t self-serve into embedded signing the way you can with BoldSign, Foxit, or SignWell. Billing starts when an envelope is sent, whether or not the signer completes.
The 15-minute polling floor on envelope status checks requires webhook-first event handling for real-time flows.
Best for
Enterprises with procurement support or products sold into government environments that require FedRAMP Moderate or DoD IL4 authorization.
Getting started
The Docusign developer account and documentation are available at developers.docusign.com. For government requirements, contact Docusign Federal sales.
What changes when you move off the Dropbox Sign API
Whichever alternative you pick, plan for these 6 areas of migration work.
What to plan for in a migration
Authentication and SDK. Replace Dropbox Sign’s credentials and client code with the new vendor’s supported authentication flow. Foxit uses OAuth client credentials and bearer tokens. BoldSign uses an API-key header; SignNow supports API keys or OAuth 2.0 access tokens, both in an Authorization: Bearer header. Docusign uses OAuth 2.0 with JWT or authorization-code grants. Replace hellosign-node-sdk, hellosign-python-sdk, or equivalent packages with the vendor’s available SDK or a direct REST client.
Payloads and field placement. Signer fields and text tags are vendor-specific. Dropbox Sign tags follow its own syntax; Foxit tags follow the ${signfield:party:required:name} pattern; DocuSeal tags follow {{Field Name;role=Signer1;type=date}}; Docusign uses coordinate-based tabs or anchor text strings. Every migration rewrites the field definitions in source PDFs or in the API payload.
Templates. No Dropbox Sign template importer was verified among the reviewed vendors. DocuSeal documents importing DocuSign templates, but that’s a separate migration path. Plan to rebuild templates from scratch in the new vendor’s system. For integrations with many templates, this is a project in itself.
Webhook handler. The verification contract differs across every vendor. Dropbox Sign requires the literal response body Hello API Event Received plus HMAC validation of event_hash. BoldSign expects a 200 response within 10 seconds and validates the X-BoldSign-Signature HMAC header. Documenso uses a plain secret in X-Documenso-Secret that must be compared in constant time. For Foxit, verify the HMAC-SHA-256 signature received as a query parameter on the webhook URL.Replacing the handler means changing both the response format and the signature verification logic.
Embedded client. The hellosign-embedded library manages the iframe lifecycle for Dropbox Sign, handles domain verification, and emits events like sign and cancel. Each alternative uses its own mechanism. Foxit and SignWell use URLs mounted in standard iframes, BoldSign generates an embedded link you render in an iframe, DocuSeal ships framework components for React, Vue, and Angular, and Documenso providesSDKs for 6 front-end frameworks. Allowed domains must be re-registered wherever the vendor requires it.
Go-live gates. Dropbox Sign requires app approval before production embedded traffic. Docusign requires a go-live review. SignNow gates production on an active subscription or trial. For the other vendors, confirm how to activate production access and provision credentials. Factor any review timeline into your migration schedule.
Run both integrations in parallel until all requests sent through Dropbox Sign have completed or expired. Cancel the Dropbox Sign subscription only after open requests have cleared.
Mapping Dropbox Sign objects to the Foxit eSign API
Here’s how Dropbox Sign’s main objects map to our eSign API. This is a concept map, not a payload converter.
Dropbox Sign API | Foxit eSign API | What changes |
signature_request/send | Create Envelope (folders/createfolder) with sendNow: true | Documents arrive as PDF URLs or Base64; recipients are parties with a sequence |
send_with_template | Create Envelope from Template (templates/createFolder) | Rebuild templates in Foxit eSign first |
create_embedded plus GET /embedded/sign_url | createEmbeddedSigningSession: true returns embeddedSessionURL | The URL comes back at creation; regenerate expired sessions through the API |
unclaimed_draft (embedded requesting) | createEmbeddedSendingSession: true | Users prepare and send inside an embedded sending view |
Dropbox Sign text tags | Foxit text tags such as ${signfield:1:y:name} | Rewrite the tag syntax in your source PDFs |
Callbacks answered with Hello API Event Received | Webhook channels with folder_* events | Return a fast 2xx and verify the HMAC signature |
signature_request_all_signed and signature_request_downloadable | folder_completed, then folder_executed about 5 to 10 seconds later | Download final files after folder_executed |
signature_request_declined | folder_cancelled | Declines and cancellations share one event |
GET /signature_request/files | Download Envelope Files and Get Envelope Activity History | Audit history is a separate call |
Monthly signature-request quota by default; yearly refresh costs extra | 5 credits per envelope from a yearly pool | Budget envelopes per year, shared with PDF calls |
Compare billing triggers and quota rules
Dropbox Sign sells monthly request quotas by default, with yearly quotas available for an additional cost. Confirm the auto-upgrade and HTTP 402 behavior described earlier.
BoldSign and Docusign bill when an envelope or document is sent, whether or not it completes.
SignWell bills documents per month with no minimum, on a graduated per-document rate. Foxit bills 5 credits when an envelope is created from a yearly pool.
DocuSeal bills $0.20 per completed document. Zoho Sign deducts prepaid credits that don’t expire while the account remains active.
SignNow sells yearly invite pools and grows the plan as usage grows.
Documenso charges a flat monthly or annual subscription with unlimited documents under a fair-use policy.
Under send-based or creation-based billing, uncompleted requests still cost money. BoldSign reverses charges for revoked documents but not for declined documents.
DocuSeal’s completion-based billing means abandoned requests incur no per-document fee, though the Pro seat fee still applies.Under subscription billing (Documenso), volume within the fair-use policy has no per-unit cost.
Compare when billing occurs and what happens at quota, not just the headline rate. Abandoned requests and uneven monthly volume can change the total cost.
What stays behind in Dropbox Sign
Signed files and audit trails. Download signed documents with their audit trails before the subscription ends. The files endpoint returns a PDF with the audit trail appended or a ZIP of separate files, depending on the parameter you pass. Audit trail settings can also generate a separate trail document if your account is configured that way.
Export takes time. File downloads sit in the 25-requests-a-minute rate tier. Exporting 10,000 completed requests at one call each takes at least about 7 hours (10,000 requests at 25 per minute equals 400 minutes). The Dropbox Sign Team sync to cloud storage is a Premium feature, so lower-tier accounts need to export manually through the API. Build this into your migration timeline as its own project with a dedicated script.
In-flight requests. Keep the old Dropbox Sign integration running until every open request either completes or expires. Embedded sign_url links expire after 60 minutes or on first access and must be regenerated while the Dropbox Sign integration remains active.
Retention. Canceling the subscription downgrades the account to free after the billing period ends. Signed documents remain accessible on a free account. Confirm API access levels and any contractual retention requirements before canceling. Deleting the account permanently deletes its documents with no recovery path.
Treat the archive export as its own project with its own timeline, separate from the migration to the new API.
Archive before you cancel.
Use the files endpoint to download signed documents and audit trails. At 25 requests a minute, a large archive can take hours to export. Canceling a subscription leaves documents on a free account; deleting the account permanently removes them. Confirm contractual retention requirements, and export signed files and audit trails before the subscription ends.
Where Foxit eSign API fits
Our eSign API fits products that generate or process PDFs before sending them for signature. You can keep the generate-then-sign workflow on one account with a shared credit pool, rather than integrating separate document-processing and signing vendors.
Each envelope created uses 5 credits. Billable PDF Services processing calls and document-generation operations use 1 credit each; status checks, webhooks, uploads, and downloads use no credits.
Embedded signing and sending are available on every plan.
Our free Developer plan includes 500 shared annual credits and trial access to eSign, so you can evaluate the workflow before buying a paid plan. Confirm QES identity-validation and 21 CFR Part 11 workflow requirements with our team.
Create a free Foxit developer account, activate the eSign trial, and send your first envelope through the eSign API.
What else to know before you switch from the Dropbox Sign API
These questions cover the naming, API, and licensing distinctions that can affect your choice.
What happened to HelloSign?
Dropbox announced it would acquire HelloSign for approximately $230 million on January 28, 2019and closed the dealon February 8, 2019. HelloSign became Dropbox Sign, starting October 25, 2022. The API host stayed api.hellosign.com, the embedded library is still hellosign-embedded, and the developer docs continue to reference HelloSign in many places. The brand changed, but the endpoints stayed the same.
Is DocuSign the same as HelloSign?
No. HelloSign is now Dropbox Sign, owned by Dropbox. Docusign is a separate public company with its own API. Our Docusign entry covers the embedded-signing plan gate and federal offering; our Docusign API alternatives guide compares other options.
What is the best open source e-signature software?
For developers, 3 projects are worth evaluating:
- DocuSeal’s core is AGPLv3 with additional attribution terms, and the hosted plans add a paid tier for API and embedding.
- Documenso’s Community Edition is AGPL-3.0, and its hosted plans layer on a paid tier for commercial embedding.
- OpenSign is AGPL-3.0 with directory-level exceptions and offers a hosted option.
Self-hosting the free community editions avoids their hosted plan fees, but hosting and maintenance costs replace them. Embedding and white-label features may remain on paid tiers even when self-hosting, so check each project’s license and feature gating before building a production flow around the free edition.
Programmatic PDF Editing with Foxit PDF Services API: Pages, Merges, Splits, and Flattening at Scale

Manually editing PDFs doesn’t scale when you’re processing hundreds of documents a day. This guide uses working Python and cURL examples to walk through the Foxit PDF Editor API’s core operations, covering page manipulation, merging, splitting, and flattening.
If you’re building document workflows at scale, you already know that manual PDF editing doesn’t cut it. Whether you’re generating contracts, processing invoices, or packaging reports, you need an API that handles page manipulation, merging, splitting, and flattening without breaking under load. Foxit PDF Services API gives you exactly that.
This guide covers the operations you’ll use most: adding and removing pages, merging documents, splitting by page range, and flattening annotations and form fields. Code examples are in Python, but the API is REST-based, so the patterns translate to any stack.
What Is the Foxit PDF Services API?
Foxit PDF Services API is a cloud-based REST API for programmatic PDF manipulation. You send documents and parameters; it returns processed PDFs. No local dependencies, no rendering engine to maintain.
The API handles:
Page insertion, deletion, and reordering
Document merging (combining multiple PDFs into one)
Document splitting (breaking one PDF into multiple outputs)
Flattening (converting annotations, form fields, and overlays into static page content)
Authentication uses API keys. Every request requires client_id and client_secret as separate HTTP request headers.
Why Backend PDF Editing Is an Infrastructure Decision
Teams processing hundreds of PDFs a day have an architecture problem, not a tooling problem.
When your CRM spits out contracts, your ERP generates invoices, and your intake forms produce patient records, the question is how you process these at scale without standing up a server farm or babysitting a library version matrix. At 500+ documents per day, manual desktop tooling is off the table. Per-file scripts that depend on a locally installed library become a liability the moment the library version shifts or a new language target appears.
Two architectural approaches dominate this space: SDK-based libraries and cloud REST APIs. SDK libraries require local installation, version pinning, language-specific bindings, and ongoing maintenance every time a dependency shifts. A cloud REST API requires none of that. Any language that can send an HTTP request can call it, with no package to install, no runtime to configure, and no compatibility matrix to manage.
This guide covers four operations in full: page manipulation (move, rotate, delete, add), merging multiple PDFs into one output, splitting a large document by page count, and flattening annotations into permanently static content. All of it runs via REST calls against the Foxit PDF Services API, authenticated with two request headers and structured around a single four-step loop that applies to every endpoint in the suite.
Prerequisites
Get your environment in order before any code runs. Each tool links to its canonical install page.
venv for project isolation
Node.js 18+ and npm
axiosfor Node examplesA code editor such as VS Code with the Python extension; alternatives include PyCharm, WebStorm, and Sublime Text
Postman (optional, for API exploration alongside the Foxit API Playground)
A Foxit developer account, available at account.foxit.com/site/sign-up with no credit card required and free credits included
Set up your workspace:
mkdir foxit-pdf-tutorial && cd foxit-pdf-tutorial
python3 -m venv .venv
source .venv/bin/activate
pip install requests
Authentication and the Upload → Task → Poll → Download Loop
Authentication
Foxit PDF Services API authenticates via headers. Every request must include client_id and client_secret as separate HTTP request headers, exactly as named: lowercase, underscored. They’re passed individually, as plain strings. Concatenating them, Base64-encoding them, or prefixing them with Bearer are all auth mistakes that look like they should work and don’t.
The base host for the North America environment is https://na1.fusion.foxit.com. Set it once as an environment variable and reuse it:
export BASE_URL="https://na1.fusion.foxit.com"
export CLIENT_ID="your_client_id"
export CLIENT_SECRET="your_client_secret" Every cURL request in this guide uses that pattern:
--header "client_id: $CLIENT_ID"
--header "client_secret: $CLIENT_SECRET"
In Python, read credentials from os.environ and never hardcode them:
import os
import time
import requests
BASE_URL = os.environ["BASE_URL"]
CLIENT_ID = os.environ["CLIENT_ID"]
CLIENT_SECRET = os.environ["CLIENT_SECRET"]
headers = {
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
} In Node.js:
const axios = require("axios");
const BASE_URL = process.env.BASE_URL;
const headers = {
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET,
}; The Four-Step Loop
Every operation in this API follows the same four steps, regardless of endpoint.
POST /pdf-services/api/documents/uploadwith the file asmultipart/form-data. The response returns an uploaddocumentId.POSTto the relevant endpoint (manipulate, combine, split, or flatten) with the uploaddocumentIdin the request body. The response is HTTP 202 with ataskId.GET /pdf-services/api/tasks/{task-id}until thestatusfield readsCOMPLETED. The completed task response also carries aresultDocumentIdand aprogresspercentage. Task statuses are always uppercase:PENDING,PROCESSING,COMPLETED,FAILED.GET /pdf-services/api/documents/{resultDocumentId}/downloadto retrieve your result. The uploaddocumentIdfrom step one and theresultDocumentIdfrom step three are different identifiers. You download using theresultDocumentId. Swapping these is the most common 4xx error.
Use a-midsummer-nights-dream.pdf to follow along with a known-good file:
curl --location "$BASE_URL/pdf-services/api/documents/upload" \
--header "client_id: $CLIENT_ID" \
--header "client_secret: $CLIENT_SECRET" \
--form 'file=@"a-midsummer-nights-dream.pdf"' Response:
{
"documentId": "abc123-upload-id"
}
A reusable Python polling function with exponential backoff:
def poll_task(task_id: str) -> str:
url = f"{BASE_URL}/pdf-services/api/tasks/{task_id}"
delay = 2
while True:
response = requests.get(url, headers=headers)
response.raise_for_status()
data = response.json()
status = data["status"]
if status == "COMPLETED":
return data["resultDocumentId"]
elif status == "FAILED":
raise RuntimeError(f"Task {task_id} failed: {data}")
time.sleep(delay)
delay = min(delay * 2, 30) Branch on "COMPLETED" and "FAILED" in uppercase. Lowercase strings will silently never match.
The async model is what makes this scalable. A 50-page merge or a batch of 200 splits doesn’t block your thread while it runs. You dispatch the operation call, collect the taskId, and poll on a sensible backoff schedule. At 500+ PDFs per day, that non-blocking pattern keeps your worker pool from saturating.
Page Manipulation: Move, Rotate, Delete, and Add Pages
All page operations go through a single endpoint: POST /pdf-services/api/documents/modify/pdf-manipulate.
{
"documentId": "<upload-document-id>",
"password": "optional",
"config": {
"operations": [{ "type": "OPERATION_TYPE" }]
}
} The config.operations array runs in order. Page indexing is 1-based and adjusts after each step. If you delete page 3 and then reference page 4 in the next operation, that reference points to what was originally page 5 before the delete. Keep that in mind when chaining operations in a single request.
MOVE_PAGES
MOVE_PAGES reorders pages within the document:
{
"type": "MOVE_PAGES",
"pages": [5, 6, 7],
"targetPosition": 1
} targetPosition is 1-based and must not exceed the document’s total page count.
ROTATE_PAGES
ROTATE_PAGES changes page orientation:
{
"type": "ROTATE_PAGES",
"pages": [1, 2, 3],
"rotation": "ROTATE_CLOCKWISE_90"
} Valid rotation values are ROTATE_0, ROTATE_CLOCKWISE_90, ROTATE_180, and ROTATE_COUNTERCLOCKWISE_90.
DELETE_PAGES
DELETE_PAGES removes specific pages:
{
"type": "DELETE_PAGES",
"pages": [8, 9]
} ADD_PAGES
ADD_PAGES appends blank pages to the end of the document:
{
"type": "ADD_PAGES",
"pageCount": 2
}
ADD_PAGES has no insert-at-position parameter; blank pages always go to the end. There’s also no REPLACE_PAGES operation in the current API. Page replacement requires a DELETE_PAGES call on the target pages followed by a separate merge step to bring in the replacement content.
A document intake pipeline for scanned medical records often receives pages in landscape orientation when the archive standard is portrait. The Python call below normalizes pages 1 through 3 of the uploaded file:
def rotate_pages(document_id: str) -> str:
url = f"{BASE_URL}/pdf-services/api/documents/modify/pdf-manipulate"
payload = {
"documentId": document_id,
"config": {
"operations": [
{
"type": "ROTATE_PAGES",
"pages": [1, 2, 3],
"rotation": "ROTATE_CLOCKWISE_90"
}
]
}
}
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
return response.json()["taskId"] A legal document workflow that assembles exhibits and needs to move pages 5, 6, and 7 to the front before archiving uses this request body:
{
"documentId": "<upload-document-id>",
"config": {
"operations": [
{
"type": "MOVE_PAGES",
"pages": [5, 6, 7],
"targetPosition": 1
}
]
}
}
All four operation types return HTTP 202 with a taskId. Pass that ID to poll_task, wait for COMPLETED, then download the resultDocumentId.
Merging and Splitting PDFs
Merging Multiple PDFs with pdf-combine
The merge endpoint is POST /pdf-services/api/documents/enhance/pdf-combine.
The request body takes a required documentInfos array. Each entry is a source document object:
{
"documentInfos": [
{ "documentId": "<doc-id-1>" },
{ "documentId": "<doc-id-2>" },
{ "documentId": "<doc-id-3>" }
],
"config": {
"addBookmark": true,
"continueMergeOnError": false,
"retainPageNumbers": false
}
} The config object supports three keys:
addBookmarkgenerates one bookmark per source file in the merged output, useful when the reader needs to navigate by source.retainPageNumberspreserves original page-number labels from each source.continueMergeOnErroris the one that matters most in production. Set it tofalsefor any job where a single bad source must fail the entire batch. Set it totrueonly for best-effort pipelines where partial output is acceptable. Always set it explicitly rather than relying on defaults.
The following code assembles a monthly client report from three sources. Download input.pdf, second.pdf, and input_for_compare.pdf to run this end-to-end:
def upload_file(path: str) -> str:
url = f"{BASE_URL}/pdf-services/api/documents/upload"
with open(path, "rb") as f:
response = requests.post(url, headers=headers, files={"file": f})
response.raise_for_status()
return response.json()["documentId"]
def merge_pdfs(document_ids: list) -> str:
url = f"{BASE_URL}/pdf-services/api/documents/enhance/pdf-combine"
payload = {
"documentInfos": [{"documentId": doc_id} for doc_id in document_ids],
"config": {
"addBookmark": True,
"continueMergeOnError": False,
"retainPageNumbers": False
}
}
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
return response.json()["taskId"]
# Upload all three source files, collect their documentIds
doc_ids = [
upload_file("input.pdf"),
upload_file("second.pdf"),
upload_file("input_for_compare.pdf")
]
# Merge and download
merge_task_id = merge_pdfs(doc_ids) # POST combine → taskId
merge_result_id = poll_task(merge_task_id) # poll → resultDocumentId
Once poll_task returns a resultDocumentId, pass it to your download function.
Splitting a PDF by Page Count with pdf-split
The split endpoint is POST /pdf-services/api/documents/modify/pdf-split. The pdf- prefix is required and matches the naming convention used by pdf-combine, pdf-manipulate, and pdf-flatten.
The request body is minimal:
{
"documentId": "<upload-document-id>",
"pageCount": 10
} pageCount specifies how many pages go into each output file. The last file gets whatever pages remain. The current API exposes only this page-count split mode. File-size-based and bookmark-based splitting are not in the published reference.
curl --location "$BASE_URL/pdf-services/api/documents/modify/pdf-split" \
--header "client_id: $CLIENT_ID" \
--header "client_secret: $CLIENT_SECRET" \
--header 'Content-Type: application/json' \
--data '{
"documentId": "<upload-document-id>",
"pageCount": 10
}' The response returns a taskId. When polling reaches COMPLETED, the result contains multiple output files, one per split chunk.
Flattening Annotations: Making PDF Changes Permanent
Flattening merges annotations, form fields, and layers into the page content itself. The output is a single static PDF where no markup can be toggled, filled, or removed. This is a one-way transformation with no undo.
The endpoint is POST /pdf-services/api/documents/modify/pdf-flatten.
The request body is intentionally minimal:
{
"documentId": "<upload-document-id>"
} No flags for selective annotation or form-field handling are exposed in the current API reference. The operation flattens both annotations and form fields together in a single pass.
When to Flatten a PDF
Flattening is required in three situations:
Archiving a signed form. Without flattening, a technically capable recipient can still manipulate form fields in most PDF viewers, even after signature. Flattening removes that possibility entirely.
Print production. Annotation layers render differently across print drivers and produce visible artifacts in the final output. Flattening eliminates the variable.
Compliance workflows under HIPAA or legal hold. Documents at rest must be immutable. A live form field fails that requirement.
curl --location "$BASE_URL/pdf-services/api/documents/modify/pdf-flatten" \
--header "client_id: $CLIENT_ID" \
--header "client_secret: $CLIENT_SECRET" \
--header 'Content-Type: application/json' \
--data '{ "documentId": "<upload-document-id>" }' Flattening does not encrypt the document or apply password protection. If your workflow requires both, chain a pdf-protect call after flattening using POST /pdf-services/api/documents/security/pdf-protect, passing the resultDocumentId from the completed flatten task as the documentId input for the protect call and the password under config.userPassword. The result of one operation becomes the input of the next.
{
"documentId": "<flatten-result-document-id>",
"config": {
"userPassword": "<password>"
}
} In this body, you point documentId at the flatten task’s resultDocumentId and set the open password under config.userPassword. The password lives inside the config object, not at the top level. A top-level password key is accepted by the request but the task then fails with a parameter error, so nest it under config.
Building a Multi-Step PDF Editing Pipeline for High Volume
Chaining Operations with resultDocumentId
Every completed task returns a resultDocumentId. That ID becomes the documentId for the next operation. The full chain: upload, then operation A, then poll until A reaches COMPLETED, then operation B using A’s resultDocumentId, then poll until B reaches COMPLETED, then download B’s resultDocumentId.
A complete flatten-then-protect pipeline, with inline comments showing which ID type is active at each step:
import os
import time
import requests
BASE_URL = os.environ["BASE_URL"]
CLIENT_ID = os.environ["CLIENT_ID"]
CLIENT_SECRET = os.environ["CLIENT_SECRET"]
headers = {
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
}
def upload_file(path: str) -> str:
url = f"{BASE_URL}/pdf-services/api/documents/upload"
with open(path, "rb") as f:
response = requests.post(url, headers=headers, files={"file": f})
response.raise_for_status()
return response.json()["documentId"]
def poll_task(task_id: str) -> str:
url = f"{BASE_URL}/pdf-services/api/tasks/{task_id}"
delay = 2
while True:
response = requests.get(url, headers=headers)
response.raise_for_status()
data = response.json()
status = data["status"]
if status == "COMPLETED":
return data["resultDocumentId"]
elif status == "FAILED":
raise RuntimeError(f"Task failed: {data}")
time.sleep(delay)
delay = min(delay * 2, 30)
def flatten(document_id: str) -> str:
url = f"{BASE_URL}/pdf-services/api/documents/modify/pdf-flatten"
response = requests.post(url, json={"documentId": document_id}, headers=headers)
response.raise_for_status()
return response.json()["taskId"]
def protect(document_id: str, password: str) -> str:
url = f"{BASE_URL}/pdf-services/api/documents/security/pdf-protect"
payload = {"documentId": document_id, "config": {"userPassword": password}}
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
return response.json()["taskId"]
def download(document_id: str, output_path: str):
url = f"{BASE_URL}/pdf-services/api/documents/{document_id}/download"
response = requests.get(url, headers=headers)
response.raise_for_status()
with open(output_path, "wb") as f:
f.write(response.content)
# Pipeline: upload → flatten → protect → download
upload_doc_id = upload_file("signed-form.pdf") # documentId
flatten_task_id = flatten(upload_doc_id) # taskId
flatten_result_id = poll_task(flatten_task_id) # resultDocumentId
protect_task_id = protect(flatten_result_id, os.environ["PDF_PASSWORD"]) # taskId
protect_result_id = poll_task(protect_task_id) # resultDocumentId
download(protect_result_id, "final-protected.pdf") Three distinct IDs flow through this pipeline: the upload documentId, the taskId returned by each operation call, and the resultDocumentId returned by each completed task. Confusing any two of these produces a 4xx.
Batch Polling for High-Volume Jobs
For a single document, sequential polling works fine. For a batch of thousands, polling each task one at a time becomes the bottleneck. The pattern that scales is to dispatch first, then consolidate.
Upload all source files in parallel and collect their documentIds. Dispatch all operation calls in parallel and collect their taskIds. Then run a single consolidated polling loop that iterates over all taskIds, checks each status, and branches on COMPLETED or FAILED per task. Your thread never blocks waiting for one document while others are already done, and it processes results as they arrive.
The multi.py sample in the foxitsoftware/developerapidemos repository demonstrates this exact dispatch-then-consolidate shape, including the resultDocumentId handoff across operations.
Common Mistakes and Troubleshooting
These are the integration errors that come up most often.
Polling too aggressively. Start at a two-second interval and double it up to a ceiling of around 30 seconds. Tight-looping the /tasks/{task-id} endpoint burns rate-limit budget and produces 429s that slow the whole pipeline down.
Treating FAILED as transient. A FAILED status means the job encountered a specific error. Read the task response body, surface the reason, and branch on it. Retrying indefinitely on FAILED produces the same failure indefinitely.
Downloading before COMPLETED. Some integrations skip the status check and call the download endpoint immediately after the operation response. The result is a partial or empty file. Always confirm status == "COMPLETED" before hitting the download endpoint.
Mixing up the three IDs. All three appear in the same code block so they can’t be confused:
upload_doc_id = upload_file("input.pdf") # documentId → input to operation endpoints
task_id = flatten(upload_doc_id) # taskId → input to the polling endpoint
result_doc_id = poll_task(task_id) # resultDocumentId → input to the download endpoint
download(result_doc_id, "output.pdf") The download endpoint takes the resultDocumentId, not the upload documentId or the taskId.
Task status casing. The polling endpoint returns PENDING, PROCESSING, COMPLETED, and FAILED in uppercase. Branching on lowercase "completed" will silently never match and your loop will run forever.
continueMergeOnError semantics. Setting this to true lets the merge skip a failing source and continue. Setting it to false aborts the entire batch if any source fails. In production, always set this explicitly.
Retrying 4xx responses. Retry on 5xx errors and timeouts. A 400 or 401 will return the same 4xx until you fix the request, so don’t retry them.
Auth header format. client_id and client_secret are separate headers, lowercase with underscores, passed individually. They’re not concatenated, Base64-encoded, or Bearer-prefixed.
pdf-flatten does not encrypt. Flattening makes a document’s content static but doesn’t restrict access to the file. If your compliance workflow requires both, chain a pdf-protect call after the flatten step using the flatten task’s resultDocumentId as the next documentId input.
PDF Editor API FAQ
What is the Foxit PDF Services API used for?
The Foxit PDF Services API is a cloud-based REST API for programmatic PDF manipulation at scale, covering page manipulation, document merging, splitting, and annotation flattening. Any language that can send HTTP requests can use it without installing local dependencies.
How do you merge multiple PDFs using the Foxit API?
Upload each source file to get a documentId, then POST all documentIds in a documentInfos array to POST /pdf-services/api/documents/enhance/pdf-combine. Poll the returned taskId until status is COMPLETED, then download the resultDocumentId.
What is PDF flattening and when should you use it?
PDF flattening converts interactive elements (form fields, annotations, and overlays) into static page content that cannot be edited. Use it when archiving signed forms, preparing documents for print production, or meeting compliance requirements such as HIPAA that mandate immutable records at rest.
What is the difference between documentId and resultDocumentId in the Foxit API?
documentId is returned after uploading a file and is used as input to operation endpoints. resultDocumentId is returned when a task reaches COMPLETED status and is used to download the processed file. They are different identifiers; using one where the other is expected produces a 4xx error.
Does the Foxit PDF Services API support splitting PDFs by custom page ranges?
The current pdf-split endpoint splits by a fixed pageCount value, producing equal-sized chunks with the last file containing remaining pages. File-size-based and bookmark-based splitting are not available in the published API reference.
How should you handle errors in the Foxit PDF Services API?
Retry on 5xx errors and timeouts using exponential backoff. Do not retry 400 or 401 responses, since those indicate a problem with the request itself. Read the response body on FAILED task status and branch on the specific error rather than retrying blindly.
Getting Started with Foxit PDF Services API
Create a free developer account. No credit card required, and free credits are included.
Once you have your credentials, open the Foxit API Playground. Start with pdf-combine. Download input.pdf and second.pdf, upload both to get two documentIds, POST the merge request with the JSON body from the Merging section, and poll for the result. The full cycle, authentication through download, takes under ten minutes on a first attempt.
If you’re evaluating this for production volume, check the current plan options and credit pools. Plans range from the free developer tier up through Startup and Business options with larger shared-credit allocations.
For the authoritative reference on every endpoint and parameter covered in this guide, the Foxit PDF Services API documentation is your starting point. The GitHub samples repository has complete Python and Node.js examples you can clone and run immediately, including the multi-step pipeline pattern this guide covers.
Foxit MCP Server: Give AI Agents Direct Access to 30+ PDF Tools via Model Context Protocol

Learn how the Foxit MCP Server lets AI agents handle PDF conversion, OCR, merge, signing, and document workflows.
Building a document automation agent with raw REST calls means writing the same boilerplate every time: upload a file, poll for task completion, download the result, handle errors, and manage auth tokens across multiple endpoints. For PDF operations, that loop repeats for every conversion, OCR call, or merge operation in your pipeline. The Foxit PDF API MCP Server collapses those loops into 30+ directly callable tools, with the MCP Server handling upstream REST complexity internally.
This guide covers how the server registers, what it exposes, how Foxit’s eSign and DocGen REST APIs extend the same agent session into signing and document generation workflows, and a concrete four-step workflow you can replicate against your own documents.
MCP Architecture in 90 Seconds
The MCP specification defines three roles. The Host is the LLM runtime (Claude Desktop, VS Code with GitHub Copilot, or Cursor) that manages the conversation and decides when to call tools. The Server is the capability provider, a process that advertises tools over the MCP protocol and executes them against some underlying service. Tools are the individual callable operations each server exposes, defined by a JSON schema the host uses to understand inputs and outputs.
Foxit occupies both sides of this architecture. Foxit PDF Editor ships as an MCP Host, the first PDF application to do so, connecting outward to external MCP servers like Gmail or Salesforce so its AI assistant can reach those services. The Foxit PDF API MCP Server works in the other direction, exposing Foxit’s cloud PDF Services API as 30+ tools for any MCP Host to call.
The MCP Server exposes PDF Services operations covering conversion between formats, content extraction, OCR, merge, split, compress, flatten, linearize, compare, watermark, form data import/export, security, and property inspection. Foxit’s eSign API and DocGen API are separate REST services that are not part of the MCP Server, so they are not exposed as MCP tools. A single agent workflow can still reach them, but through the agent’s own code-execution layer rather than through the MCP protocol, a distinction the eSign section explains in detail. The MCP tools handle PDF processing, while code the agent runs handles signing and template generation.

Prerequisites and Configuration
You need three things before registering the server:
- A Foxit developer account (free plan at developer-api.foxit.com, no credit card required) to obtain a
client_idandclient_secret - Python 3.11+ with the
uvpackage manager (or Node.js 18+ withpnpmfor the TypeScript version) - An MCP-compatible host such as Claude Desktop, VS Code, or Cursor
Clone the repo from github.com/foxitsoftware/foxit-pdf-api-mcp-server, then register it in your host’s MCP config. The walkthrough below uses Claude Desktop, but the same command, args, and env values work in any MCP host. In Claude Desktop, open Settings, select the Developer tab, and click Edit Config.

Then open claude_desktop_config.json with any text edito(stored at ~/Library/Application Support/Claude/ on macOS or %APPDATA%\Claude\ on Windows).

Add the Foxit server under the mcpServers key:
{
"mcpServers": {
"foxit-pdf": {
"command": "uv",
"args": [
"--directory",
"/path/to/foxit-pdf-api-mcp-server",
"run",
"foxit-pdf-api-mcp-server"
],
"env": {
"FOXIT_CLOUD_API_HOST": "https://na1.fusion.foxit.com/pdf-services",
"FOXIT_CLOUD_API_CLIENT_ID": "your_client_id",
"FOXIT_CLOUD_API_CLIENT_SECRET": "your_client_secret"
}
}
}
} Set FOXIT_CLOUD_API_CLIENT_ID and FOXIT_CLOUD_API_CLIENT_SECRET as environment variables on your system before the host process launches. Passing credentials through prompt context is a security risk your production setup should address. The client_id and client_secret from your developer portal authenticate all MCP tool calls to the PDF Services API. Adding eSign to the same agent session requires its own OAuth2 token exchange (covered in the next section), keeping the two credential scopes isolated.
After saving, completely quit and reopen Claude Desktop so it loads the config and launches the server as a local subprocess over standard input and output, the transport the Foxit server uses.

On restart, you should see the foxit MCP as Running in the local MCP servers in the developer tab. If you go the Customize tab then open the Connectors and click foxit-pdf to see the tools that the Foxit MCC has access to, you should see the 30+ tools registered.

If the connector never appears, the server failed to launch, and Claude’s logs at
~/Library/Logs/Claude/mcp*.logusually point to the cause, commonly a missinguvbinary or a wrong--directorypath.
To call a tool, type a natural-language request such as “Convert this Word file to PDF and compress it.” The agent selects pdf_from_word and pdf_compress, and Claude Desktop shows an approval prompt with the exact tool name and arguments before each call runs; the tool’s JSON result then streams back into the conversation.

That per-call approval is your audit point, since it surfaces precisely which tool the agent chose and what it passed.

If you would rather run the server in VS Code, the equivalent entry goes in .vscode/mcp.json under a top-level servers key, with an added "type": "stdio" field so VS Code launches the server the same way:
{
"servers": {
"foxit-pdf": {
"type": "stdio",
"command": "uv",
"args": [
"--directory",
"/path/to/foxit-pdf-api-mcp-server",
"run",
"foxit-pdf-api-mcp-server"
],
"env": {
"FOXIT_CLOUD_API_HOST": "https://na1.fusion.foxit.com/pdf-services",
"FOXIT_CLOUD_API_CLIENT_ID": "your_client_id",
"FOXIT_CLOUD_API_CLIENT_SECRET": "your_client_secret"
}
}
}
} You can also run MCP: Add Server from the Command Palette (Cmd+Shift+P or Ctrl+Shift+P), choose Command (stdio), and pick Workspace to write the entry into .vscode/mcp.json or Global to store it in your user profile. Once saved, VS Code shows inline Start, Stop, and Restart actions above the server entry and lists it under the MCP SERVERS – INSTALLED view, where a green indicator and the discovered tool count confirm the connection.
PDF Services MCP Tools: Full Catalog
The 30+ tools organize into seven functional categories. Most tools expect a documentId returned by a prior upload_document call, and return a resultDocumentId you pass to download_document when you want the output locally. The exception is pdf_from_url, which accepts a URL directly.
Document Lifecycle
upload_document: upload a PDF, Office file, image, HTML file, or plain text file; returns adocumentIdfor subsequent operationsdownload_document: retrieve a processed result to a local file pathdelete_document: clean up stored files from cloud storage
PDF Creation (file to PDF)
pdf_from_word,pdf_from_excel,pdf_from_ppt: convert Office documents to PDFpdf_from_text,pdf_from_image,pdf_from_html: convert plaintext, image files, or HTML to PDFpdf_from_url: fetch a live URL and convert the rendered page to PDF
PDF Conversion (PDF to file)
pdf_to_word,pdf_to_excel,pdf_to_ppt: extract editable Office formats from a PDFpdf_to_text,pdf_to_html,pdf_to_image: export text, HTML, or image representations
Manipulation
pdf_merge: combine multiple PDFs into onepdf_split: split by page ranges, page count, or every page individuallypdf_extract: pull a subset of pages from a PDFpdf_compress: reduce file size by 30-70% depending on content typepdf_flatten: convert form fields and annotations to static content (required for compliance archiving workflows)pdf_linearize: optimize for Fast Web View so browsers can stream PDF pages incrementallypdf_watermark: apply text or image watermarks with configurable position, opacity, and rotationpdf_manipulate: rotate, delete, or reorder pages
Analysis
pdf_compare: diff two PDFs and return a color-coded annotation document showing changespdf_ocr: convert scanned or image-based PDFs to searchable text with multi-language supportpdf_structural_analysis: detect document structure (titles, headings, paragraphs, tables with cell grids, images, form fields, hyperlinks, and metadata) with bounding boxes, following the Foxit PDF structural extraction engine schema. The result is JSON packaged inside a downloadable ZIP, not a return of named business entities; it reports layout and structure, and turning that into fields like party names is the job of the agent’s LLM, which performs the semantic extraction over that JSON
Security and Forms
pdf_protect: add password protection with 128-bit or 256-bit AES encryption and granular permission flagspdf_remove_password: strip password protection from a documentexport_pdf_form_data: extract form field values as JSONimport_pdf_form_data: populate form fields from a JSON payload
Properties
get_pdf_properties: return page count, page dimensions, PDF version, encryption status, digital signature info, embedded files, font inventory, and document metadata
The most-used operation in production document pipelines is pdf_from_word. Your agent uploads a DOCX file, gets back a documentId, then calls pdf_from_word with that ID. The underlying PDF Services API runs the conversion asynchronously, but the MCP Server handles polling internally and delivers the final result directly to your agent.
MCP tool call:
{
"name": "pdf_from_word",
"input": {
"documentId": "doc_abc123"
}
} MCP tool response:
{
"success": true,
"taskId": "task_xyz789",
"resultDocumentId": "doc_result456",
"message": "Word document converted to PDF successfully. Download using documentId: doc_result456"
} Pass doc_result456 to download_document to write the output PDF to disk, or feed it directly into another tool call like pdf_structural_analysis or pdf_compress as the next step in a chain.
Extending to eSign: Foxit’s Signing API as a Complementary REST Layer
After PDF processing via MCP tools, the next stage of the workflow dispatches a document for signature through Foxit’s eSign REST API, which lives at https://na1.foxitesign.foxit.com. This guide uses the na1 (US) region throughout.
Foxit also operates regional eSign hosts for the EU (
eu1.foxitesign.foxit.com), Canada (na2.foxitesign.foxit.com), and Australia (au1.foxitesign.foxit.com). The endpoints and payloads are identical; only the host changes, so pick the host that matches your data residency requirements.
The eSign API is not part of the Foxit MCP Server, so it is not an MCP tool, and that distinction matters for how the agent reaches it. Most MCP hosts cannot make arbitrary HTTP calls on their own, so the agent does not reach eSign “through MCP.” Instead, the agent invokes eSign from its own code-execution layer, whether that is a code interpreter the host provides, an agent framework that runs Python, or a custom tool you register that wraps the eSign calls. The cleanest production pattern is to wrap the eSign operations you need as custom MCP tools so the host calls them the same way it calls the PDF tools; the production considerations section returns to this. The code below is what that layer runs.
Authentication uses OAuth2 client_credentials. The eSign token exchange is a distinct flow from the PDF Services header auth that backs your MCP tools:
import requests
resp = requests.post(
"https://na1.foxitesign.foxit.com/api/oauth2/access_token",
data={
"client_id": ESIGN_CLIENT_ID,
"client_secret": ESIGN_CLIENT_SECRET,
"grant_type": "client_credentials",
"scope": "read-write"
}
)
access_token = resp.json()["access_token"] The Foxit eSign API developer guide uses “folder” terminology throughout. The key endpoints in an automated signing flow are:
POST /api/folders/createfolder: create a signing folder from one or more PDF documents, with signers, subject, and messagePOST /api/folders/sendDraftFolder: dispatch a draft folder to its signersPOST /api/templates/createtemplate: save a reusable template from a PDF with pre-placed signature fields (instantiate a folder from it later viaPOST /api/templates/createFolder)GET /api/folders/viewActivityHistory?folderId={id}: retrieve the activity audit trail for a folder once it has been sent (a draft that has never been shared returns an error)- Webhook channels for status callbacks: register a callback URL to receive real-time events when signers view, sign, or decline
A createfolder call takes the PDF output from your MCP pipeline, uploaded to eSign’s document storage after download_document retrieves it, and sets up the signing workflow:
POST /api/folders/createfolder
Authorization: Bearer {access_token}
Content-Type: application/json
{
"folderName": "Acme Corp Contract - Q3 2025",
"sendNow": false,
"fileUrls": ["https://your-storage.example.com/acme_contract_final.pdf"],
"fileNames": ["acme_contract_final.pdf"],
"parties": [
{
"firstName": "John",
"lastName": "Smith",
"emailId": "[email protected]",
"permission": "FILL_FIELDS_AND_SIGN",
"sequence": 1
}
]
} Set sendNow to false to create a draft folder, then dispatch it with a separate call to /api/folders/sendDraftFolder. Alternatively, set sendNow to true to create and send in a single call. For files not accessible via URL, add "inputType": "base64" and pass the documents as a base64FileString array instead of fileUrls; omitting inputType makes the API reject the base64 payload as empty.
Foxit’s eSign API ships with HIPAA, eIDAS, ESIGN Act, UETA, 21 CFR Part 11, FERPA, and FINRA compliance built in. Audit trail records carry signer location, IP address, recipient identity, event timestamp, consent confirmation, security level, and complete folder history. For legal defensibility in regulated industries, capture and store these fields in your own data layer, because relying solely on Foxit’s folder history API for compliance record-keeping introduces a single point of failure in your audit chain.
End-to-End Workflow: AI Agent Automates a Sales Contract
Picture a sales ops agent that starts from a single natural language goal, “Generate a contract for Acme Corp, $48,000 ARR, and send it to [email protected] for signature.” Nothing about the tool sequence is hard-coded. The MCP Server advertises its PDF tools to the host on connection, so the agent can read the goal, recognize that it has a template to render and a document to route for signature, and decide which operations to call and in what order. The PDF steps run as MCP tool calls; the DocGen and eSign steps run from the agent’s code layer. The sequence below is one plausible run the agent might choose, not a fixed script you wire up in advance.

To get a PDF to work with, the agent first reaches for MCP tools. It calls upload_document with the DOCX contract template, receives documentId: "doc_abc", and calls pdf_from_word. The MCP Server handles the async conversion internally and returns resultDocumentId: "doc_pdf" once it completes.
Needing to know what is inside that PDF, the agent calls pdf_structural_analysis with documentId: "doc_pdf". The tool does not hand back named entities like “party” or “ARR.” It returns a resultDocumentId pointing to a ZIP archive, so the agent calls download_document to retrieve it, unzips it, and reads the structural JSON, which describes headings, paragraphs, and table cells with their positions. The agent’s LLM is what performs the semantic extraction: it reads the structural JSON and pulls “Acme Corp” out of a heading or a contract value out of a table cell, confirming the fields it needs are present. The tool hands back structure; the model turns that structure into meaning. If you want the API to return business entities directly rather than leaning on the model to interpret layout, that is the job of Foxit’s iDox.ai Document API, a separate service built for entity and PII extraction.
With the field values in hand, the agent generates the finished contract through the DocGen API, posting to /document-generation/api/GenerateDocumentBase64 with the values merged into the template via {{dynamic_tags}} syntax. DocGen is synchronous, so the call returns the finalized PDF in the response body, with Acme Corp’s name, the $48,000 ARR figure, and the correct dates populated. No polling step is involved.
Finally, the agent routes the document for signature. It authenticates against the eSign OAuth2 endpoint, uploads the DocGen output, creates a signing folder via /api/folders/createfolder with [email protected] as the signer, and dispatches it via /api/folders/sendDraftFolder.
What ties this together is that the model decides the order from the goal, not a script. The PDF steps resolve to MCP tool calls the host already knows about; the DocGen and eSign steps run through the agent’s code layer, since those APIs are not MCP tools. The agent chains the output of one step into the input of the next, and the only orchestration you maintain is whatever exposes that code layer to the model, ideally a set of custom tools rather than ad hoc scripting.
Production Considerations: Error Handling, Rate Limits, and Data Governance
When you call PDF Services through the MCP Server, async polling happens inside the server process. Your agent receives a final resultDocumentId only after the task completes. When you call the raw PDF Services REST API directly, every operation returns a taskId you poll manually. The pattern below applies exponential backoff with a ceiling of 10 seconds per interval and a 30-second total timeout:
import time, requests
API_HOST = "https://na1.fusion.foxit.com/pdf-services"
auth_headers = {
"client_id": "your_client_id",
"client_secret": "your_client_secret"
}
def poll_task(task_id: str, max_wait: int = 30) -> str:
delay = 1
elapsed = 0
while elapsed < max_wait:
resp = requests.get(
f"{API_HOST}/api/tasks/{task_id}",
headers=auth_headers
)
data = resp.json()
if data["status"] == "COMPLETED":
return data["resultDocumentId"]
time.sleep(delay)
elapsed += delay
delay = min(delay * 2, 10)
raise TimeoutError(f"Task {task_id} timed out after {max_wait}s") Because eSign and DocGen are not MCP tools, decide deliberately how the agent reaches them. Letting the model emit raw HTTP from a free-form code interpreter is brittle and hard to audit. The more durable pattern is to wrap the specific eSign and DocGen operations you use, such as create-folder, send-folder, and generate-document, as custom MCP tools with typed inputs. The host then calls them through the same protocol it uses for the PDF tools, the credentials stay in the tool process rather than in the prompt, and the agent’s choices become inspectable tool calls instead of opaque scripts.
The output of pdf_structural_analysis deserves its own caution. The structural JSON for a long contract can run to many thousands of elements, and feeding the entire file into the model can quietly blow past its context window, which tends to surface as a truncated or confused extraction rather than a clean error. Have the code that unzips the archive filter the JSON before the model sees it, keeping only the element types and pages that matter (for a contract, usually the heading blocks and the relevant table), rather than passing the whole document through.
The free developer plan at developer-api.foxit.com covers development and testing volumes. Production workloads above the free-tier threshold require a volume plan requested through the Developer Portal.
For data governance, all API traffic runs over TLS 1.2+, and documents at rest use AES-256 encryption. Foxit’s API security documentation covers SOC 2 Type II audit status, HIPAA BAA support, GDPR, CCPA, eIDAS, ESIGN Act, UETA, 21 CFR Part 11, FERPA, and FINRA requirements. Customer data runs in logically segmented environments. For healthcare, legal, or financial services pipelines, confirm your data residency requirements before connecting production document flows, then choose the matching regional eSign host noted earlier, since the host you call determines where data is processed.
PDF API MCP Server FAQs
What is the Foxit PDF API MCP Server?
The Foxit PDF API MCP Server is an open-source Model Context Protocol server that exposes Foxit’s cloud PDF Services API as 30+ callable tools. Any MCP-compatible AI agent host, including Claude Desktop, VS Code with GitHub Copilot, and Cursor, can invoke these tools directly.
What PDF operations does the Foxit MCP Server support?
The server supports conversion (Word, Excel, PowerPoint, image, HTML, and URL to PDF and back), OCR, merge, split, extract, compress, flatten, linearize, watermark, compare, form data import/export, password protection, and full document property inspection across seven functional tool categories.
How does the Foxit MCP Server handle authentication?
PDF Services tools authenticate via a client_id and client_secret set as environment variables before the MCP host launches. The eSign API uses a separate OAuth2 client_credentials token exchange against https://na1.foxitesign.foxit.com/api/oauth2/access_token. The two credential scopes are isolated by design.
Does the Foxit MCP Server work with Claude Desktop and VS Code?
Yes. The server registers using a standard mcp.json config block for VS Code with GitHub Copilot or a claude_desktop_config.json block for Claude Desktop. The same config structure works for Cursor. All three hosts discover the server’s tools automatically on connection.
Is the Foxit PDF API MCP Server free to use?
The Foxit developer account is free with no credit card required and covers development and testing volumes. Production workloads above the free-tier threshold require a volume plan through the Developer Portal.
Run Your First Tool Call Now
Getting a working MCP tool call takes under 15 minutes:
Create a free developer account at developer-api.foxit.com (no credit card, instant access). Copy your
client_idandclient_secretfrom the dashboard.Set the three environment variables:
export FOXIT_CLOUD_API_HOST="https://na1.fusion.foxit.com/pdf-services"
export FOXIT_CLOUD_API_CLIENT_ID="your_client_id"
export FOXIT_CLOUD_API_CLIENT_SECRET="your_client_secret" - Clone the repo, register it using the config block from the Prerequisites section, restart your MCP host, and invoke
pdf_from_urlwith any public URL. You’ll have a confirmed PDF output in your working directory. The Developer Portal also includes a live API Playground for validating request payloads against the PDF Services API before wiring them into an agent.
For a full signing workflow, the minimum viable addition to the MCP setup is authenticating against the eSign OAuth2 endpoint and posting to /api/folders/createfolder with a static PDF. DocGen field population, pdf_structural_analysis extraction, and webhook callbacks extend the same pattern incrementally from there.
Get your free API access at developer-api.foxit.com.
Generate Dynamic PDFs from JSON using Foxit APIs

See how easy it is to generate PDFs from JSON using Foxit’s Document Generation API. With Word as your template engine, you can dynamically build invoices, offer letters, and agreements—no complex setup required. This tutorial walks through the full process in Python and highlights the flexibility of token-based document creation.
Generate Dynamic PDFs from JSON using Foxit APIs
One of the more fascinating APIs in our library is the Document Generation API. This document generation API lets you create dynamic PDFs or Word documents using your own data as templates. That may sound simple – and the code you’re about to see is indeed simple – but the real power lies in how flexible Word can be as a template engine. This API could be used for:
- Creating invoices
- Creating offer letters
- Creating dynamic agreements (which can integrate with our eSign API)
All of this is made available via a simple API and a “token language” you’ll use within Word to create your templates. Whether you’re feeding in data from a database, a form submission, or a JSON API response, the process looks the same from your Python script. Let’s take a look at how this is done.
Credentials
Before we go any further, head over to our developer portal and grab a set of free credentials. This will include a client ID and secret values – you’ll need both to make use of the API.
Don’t want to read all of this? You can also follow along by video:
Using the API
The Document Generation API flow is a bit different from our PDF Services APIs in that the execution is synchronous. You don’t need to upload your document beforehand or download a result. You simply call the API (passing your data and template) and the result has your new PDF (or Word document). With it being this simple, let’s get into the code.
Loading Credentials
My script begins by loading in the credentials and API root host via the environment:
CLIENT_ID = os.environ.get('CLIENT_ID')
CLIENT_SECRET = os.environ.get('CLIENT_SECRET')
HOST = os.environ.get('HOST') As always, try to avoid hard coding credentials directly into your code.
Calling the API
The endpoint only requires you to pass the output format, your data, and a base64 version of your file. “Your data” can be almost anything you like—though it should start as an object (i.e., a dictionary in Python with key/value pairs). Beneath that, anything goes: strings, numbers, arrays of objects, and so on.
Here’s a Python wrapper showing this in action:
def docGen(doc, data, id, secret):
headers = {
"client_id":id,
"client_secret":secret
}
body = {
"outputFormat":"pdf",
"documentValues": data,
"base64FileString":doc
}
request = requests.post(f"{HOST}/document-generation/api/GenerateDocumentBase64", json=body, headers=headers)
return request.json() And here’s an example calling it:
with open('../../inputfiles/docgen_sample.docx', 'rb') as file:
bd = file.read()
b64 = base64.b64encode(bd).decode('utf-8')
data = {
"name":"Raymond Camden",
"food": "sushi",
"favoriteMovie": "Star Wars",
"cats": [
{"name":"Elise", "gender":"female", "age":14 },
{"name":"Luna", "gender":"female", "age":13 },
{"name":"Crackers", "gender":"male", "age":13 },
{"name":"Gracie", "gender":"female", "age":12 },
{"name":"Pig", "gender":"female", "age":10 },
{"name":"Zelda", "gender":"female", "age":2 },
{"name":"Wednesday", "gender":"female", "age":1 },
],
}
result = docGen(b64, data, CLIENT_ID, CLIENT_SECRET) You’ll note here that my data is hard-coded. In a real application, this would typically be dynamic—read from the file system, queried from a database, or sourced from any other location.
The result object contains a message representing the success or failure of the operation, the file extension for the result, and the base64 representation of the result. To turn that base64 string back into a file, decode it first:
b64_bytes = result["base64FileString"].encode('ascii')
binary_data = base64.b64decode(b64_bytes) Most likely you’ll always be outputting PDFs, so here’s a simple bit of code that stores the result:
with open('../../output/docgen_sample.pdf', 'wb') as file:
file.write(binary_data)
print('Done and stored to ../../output/docgen_sample.pdf') There’s a bit more to the API than I’ve shown here so be sure to check the docs, but now it’s time for the real star of this API, Word.
Using Word as a Template
I’ve probably used Microsoft Word for longer than you’ve been alive and I’ve never really thought much about it. But when you begin to think of a simple Word document as a template, all of a sudden the possibilities begin to excite you. In our Document Generation API, the template system works via simple “tokens” in your document marked by opening and closing double brackets.
Consider this block of text:
See how name is surrounded by double brackets? And food and favoriteMovie? When this template is sent to the API along with the corresponding values, those tokens are replaced dynamically. In the screenshot, notice how favoriteMovie is bolded. That’s fine. You can use any formatting, styling, or layout options you wish.
That’s one example, but you also get some built-in values as well. For example, including today as a token will insert the current date, and can be paired with date formatting to specify how the date looks:
Remember the array of cats from earlier? You can use that to create a table in Word like this:
Notice that I’ve used two new tags here, TableStart and TableEnd, both of which reference the array, cats. Then in my table cells, I refer to the values from that array. Again, the color you see here is completely arbitrary and was me making use of the entirety of my Word design skills.
Here’s the template as a whole to show you everything in context:
The Result
Given the code shown above with those values, and given the Word template just shared, once passed to the API, the following PDF is created:
What About Converting PDF to JSON?
So far we’ve been going one direction: JSON data in, PDF out. But what if you need to go the other way—extract structured content from a PDF and work with it in your application?
Foxit’s PDF Services API includes an Extract endpoint that handles exactly this. You upload a PDF, specify whether you want TEXT, IMAGE, or PAGE-level data, and the API returns the extracted content. The text output is particularly useful if you want to feed the result into a data pipeline, search index, or AI workflow.
Here’s a quick look at how extraction works in Python. First, upload your PDF:
def uploadDoc(path, id, secret):
headers = {
"client_id":id,
"client_secret":secret
}
with open(path, 'rb') as f:
files = {'file': (path, f)}
request = requests.post(f"{HOST}/pdf-services/api/documents/upload", files=files, headers=headers)
return request.json()
doc = uploadDoc("../../inputfiles/input.pdf", CLIENT_ID, CLIENT_SECRET) Then call the Extract endpoint with the document ID and the type of content you want. The result comes back in a structured format you can parse, store, or pass along to other tools—including an LLM if you’re building an AI document pipeline.
You can read a full walkthrough in our PDF text extraction guide.
Ready to Try?
If this looks cool, be sure to check the docs for more information about the template language and API. Sign up for some free developer credentials and reach out on our developer forums with any questions.
If you’re building AI agents or LLM-powered workflows, Foxit also offers an MCP server that lets you connect your agents directly to Foxit PDF Services—so your AI tools can generate, extract, and process documents without any custom glue code.
Want the code? Get it on GitHub (Python).
If you are more of a Node person, check out that version. Get it on GitHub (Node.js).
Building Auditable, AI-Driven Document Workflows with Foxit APIs

We had an incredible time at API World 2025 connecting with developers, sharing ideas, and seeing how Foxit APIs power everything from AI-driven resume builders to interactive doodle apps. In this post, we’ll walk through the same hands-on workflow Jorge Euceda demoed live on stage—showing how to build an auditable, AI-powered document automation system using Foxit PDF Services and Document Generation APIs.
How to Build an AI Resume Analyzer with Python & Foxit APIs (API World 25′)
This year’s API World was packed with energy—and it was amazing meeting so many developers face-to-face at the Foxit booth. We spent three days trading ideas about document automation, AI workflows, and integration challenges.
Our team hosted a hands-on workshop and sponsored the API World Hackathon, where developers submitted 16 high-quality projects built with Foxit APIs. Submissions ranged from:
Automated legal-advice generators
Compatibility-rating apps that analyze your personality match
AI-powered resume optimizers that tailor your CV to dream-job descriptions
Collaborative doodle games that turn drawings into shareable PDFs
Each project offered a new perspective on what’s possible with Foxit APIs—and we loved seeing the creativity.
Among all the sessions, Jorge Euceda’s workshop stood out as a crowd favorite. It showed how to make AI document decisions auditable, explainable, and replayable using event sourcing and two key Foxit APIs. That’s exactly what we’ll walk through below.
Replicate the Full Demo
Click here to grab the project overview file.
Prefer to follow along with the live session instead of reading step-by-step?
Watch Jorge’s complete “AI-Powered Resume to Report” presentation from API World 2025.
It includes every step shown below—plus real-time API responses.
What You’ll Build
A complete, auditable workflow:
Resume Upload → Extract Resume Data → AI Candidate Scoring → Generate HR Report → Event Store
This workshop is designed for technical professionals and managers who want to learn how to use application programming interfaces (APIs) and explore how AI can enhance document workflows. Attendees will get hands-on experience with Foxit’s PDF Services (extraction/OCR) and Document Generation APIs, and see how event sourcing turns AI decisions into an auditable, replayable ledger.
By the end, you’ll have a Python-based demo that extracts data from a PDF resume, analyzes it against a policy, and generates a polished HR Report PDF with a traceable event log.
Getting Set Up
To follow along, you’ll need:
Access to a terminal with a Python 3.9+ Environment and internet connectivity
Visual Studio Code or your preferred IDE
Basic familiarity with REST/JSON (helpful but not required)
- Install Dependencies
python -V
# virtual environment setup, requests installation
python3 -m venv myenv
source myenv/bin/activate
pip3 install requests - Download the project’s zip file below
Now extract the files somewhere in your computer, open in Visual Studio Code or your preferred IDE.
You may use any sample resume PDF for inputs/input_resume.pdf. A sample one is provided, but you may leverage any resume PDF you wish to generate a report on.
- Create a Foxit Account for credentials
Create a Free Developer Account now or navigate to our getting started guide, which will go over how to create a free trial.
Hands-On Walkthrough
Step 1 – Open the Project
Now that you’ve downloaded the workshop source code, navigate to the resume_to_report.py file, which will serve as our main entry point.
Once dependencies are installed and the ZIP file extracted, open your workspace and run:
python3 resume_to_report.py You should see console logs showing:
An AI Report printed as JSON
A generated PDF (
outputs/HR_Report.pdf)An event ledger (
outputs/events.json) with traceable actions
Step 2 — Inspect the outputs
Open the generated HR report to review:
Candidate name and phone
Overall fit score
Matching skills & gaps
Summary and policy reference in the footer
Then open events.json to see your audit trail—each entry captures the AI’s decision context.
{
"eventType": "DecisionProposed",
"traceId": "8d1e4df6-8ac9-4f31-9b3a-841d715c2b1c",
"payload": {
"fitScore": 82,
"policyRef": "EvaluationPolicy#v1.0"
}
} This is your audit trail.
Step 3 — Replay & Explain a Policy Change
Replay demonstrates why event-sourcing matters:
Edit
inputs/evaluation_policy.json: add a hard requirement (e.g.,"kubernetes") or adjust the job_description emphasis.Re-run the script with the same resume.
Compare:
New decision and updated PDF content
Event log now reflects the updated rationale (
PolicyLoadedsnapshot → newDecisionProposedwith the sametraceIdlineage)
Emphasize: The input resume hasn’t changed; only policy did — the event ledger explains the difference.
Policy: Drive Auditable & Replayable Decisions
The AI assistant uses a JSON policy file to control how it scores, caps, and summarizes results. Every policy snapshot is logged as its own event, creating a replayable audit trail for governance and compliance.
{
"policyId": "EvaluationPolicy#v1.0",
"job_description": "Looking for a software engineer with expertise in C++, Python, and AWS cloud services. Experience building scalable applications in agile teams; familiarity with DevOps and CI/CD.",
"overall_summary": "Make the summary as short as possible",
"hard_requirements": ["C++", "python", "aws"]
} Notes:
policyIdappears in both the report and event log.job_descriptiondefines what the AI is looking for.Changing these values creates a new traceable event.
Generate a Polished Report
Next, use the Foxit Document Generation API to fill your Word template and create a formatted PDF report.
Open inputs/hr_report_template.docx, you will find the following HR reporting template with placeholders for the fields we will be entering:
Tips:
Include lightweight branding (logo/header) to make the generated PDF presentation-ready.
Include a footer with traceable Policy ID and Trace ID Events
Results and Audit Trail
Here’s what the final HR Report PDF looks like:
Every decision has a Trace ID and Policy Ref, so you can recreate the report at any time and verify how the AI arrived there.
Why Event-Sourced AI Matters
This pattern does more than score resumes—it proves that AI decisions can be transparent, deterministic, and trustworthy.
By using Foxit APIs to extract, analyze, and generate documents, developers can bring auditability to any workflow that relies on machine logic.
Key Takeaways
Auditability – Every AI step emits a verifiable event.
Replayability – Change a policy and regenerate for deterministic results.
Explainability – Decisions carry policy and trace references for clear “why.”
Automation – PDF Services and Document Generation handle the document lifecycle end-to-end.
Try It Yourself
Ready to build your own auditable AI workflow?
Demo and Source Code: document-workflows-with-foxit.pages.dev
Foxit Developer Portal: developer-api.foxit.com
API Docs: docs.developer-api.foxit.com
Watch the Full Presentation: Euceda’s API World session
Closing Thought
At API World, we set out to show how Foxit APIs can power real, transparent AI workflows—and the community response was incredible. Whether you’re building for HR, legal, finance, or creative industries, the same pattern applies:
Make your AI explain itself.
Start with the Foxit APIs, experiment with policies, and turn every AI decision into a traceable event that builds trust.
How to Chain PDF Actions with Foxit

Performing a single action with the Foxit PDF Services API is straightforward, but what’s the best way to handle a sequence of operations? Instead of downloading and re-uploading a file for each step, you can chain actions together by passing the output of one job as the input for the next. This tutorial walks you through a complete Python example of how to build an efficient document optimization workflow that compresses and then linearizes a PDF.
How to Chain PDF Actions with Foxit
When working with Foxit’s PDF Services, you’ll remember that the basic flow involves:
- Uploading your document to Foxit to get an ID
- Starting a job
- Checking the job
- Downloading the result
This is handy for one off operations, for example, converting a Word document to PDF, but what if you need to do two or more operations? Luckily this is easy enough by simply handing off one result to the next. Let’s take a look at how this can work.
Credentials
Remember, to start developing and testing with the APIs, you’ll need to head over to our developer portal and grab a set of free credentials. This will include a client ID and secret values you’ll need to make use of the API.
If you would rather watch a video (or why not both?) – you can watch the walkthrough below:
Creating a Document Optimization Workflow
To demonstrate how to chain different operations together, we’re going to build a basic document optimization workflow that will:
- Compress the document by reducing image resolution and other compression algorithims.
- Linearize the document to make it better viewable on the web.
Given the basic flow described above, you may be tempted to do this:
- Upload the PDF
- Kick off the Compress job
- Check until done
- Download the compressed PDF
- Upload the PDF
- Kick off the Linearize job
- Check until done
- Download the compressed and linearized PDF
This wouldn’t require much code, but we can simplify the process by using the result of the compress job—once it’s complete—as the source for the linearize job. This gives us the following streamlined flow:
- Upload the PDF
- Kick off the Compress job
- Check until done
- Kick off the Linearize job
- Check until done
- Download the compressed and linearized PDF
Less is better! Alright, let’s look at the code.
First, here’s the typical code used to bring in our credentials from the environment, and define the Upload job:
import os
import requests
import sys
from time import sleep
CLIENT_ID = os.environ.get('CLIENT_ID')
CLIENT_SECRET = os.environ.get('CLIENT_SECRET')
HOST = os.environ.get('HOST')
def uploadDoc(path, id, secret):
headers = {
"client_id":id,
"client_secret":secret
}
with open(path, 'rb') as f:
files = {'file': f}
request = requests.post(f"{HOST}/pdf-services/api/documents/upload", files=files, headers=headers)
return request.json() def compressPDF(doc, level, id, secret):
headers = {
"client_id":id,
"client_secret":secret,
"Content-Type":"application/json"
}
body = {
"documentId":doc,
"compressionLevel":level
}
request = requests.post(f"{HOST}/pdf-services/api/documents/modify/pdf-compress", json=body, headers=headers)
return request.json()
def linearizePDF(doc, id, secret):
headers = {
"client_id":id,
"client_secret":secret,
"Content-Type":"application/json"
}
body = {
"documentId":doc
}
request = requests.post(f"{HOST}/pdf-services/api/documents/optimize/pdf-linearize", json=body, headers=headers)
return request.json() Note that the compressPDF method takes a required level argument that defines the level of compression. From the docs, we can see the supported values are LOW, MEDIUM, and HIGH.
Now, two more utility methods – one that checks the task returned by the API operations above and one that downloads a result to the file system:
def checkTask(task, id, secret):
headers = {
"client_id":id,
"client_secret":secret,
"Content-Type":"application/json"
}
done = False
while done is False:
request = requests.get(f"{HOST}/pdf-services/api/tasks/{task}", headers=headers)
status = request.json()
if status["status"] == "COMPLETED":
done = True
# really only need resultDocumentId, will address later
return status
elif status["status"] == "FAILED":
print("Failure. Here is the last status:")
print(status)
sys.exit()
else:
print(f"Current status, {status['status']}, percentage: {status['progress']}")
sleep(5)
def downloadResult(doc, path, id, secret):
headers = {
"client_id":id,
"client_secret":secret
}
with open(path, "wb") as output:
bits = requests.get(f"{HOST}/pdf-services/api/documents/{doc}/download", stream=True, headers=headers).content
output.write(bits) input = "../../inputfiles/input.pdf"
print(f"File size of input: {os.path.getsize(input)}")
doc = uploadDoc(input, CLIENT_ID, CLIENT_SECRET)
print(f"Uploaded doc to Foxit, id is {doc['documentId']}")
task = compressPDF(doc["documentId"], "HIGH", CLIENT_ID, CLIENT_SECRET)
print(f"Created task, id is {task['taskId']}")
result = checkTask(task["taskId"], CLIENT_ID, CLIENT_SECRET)
print("Done converting to PDF. Now doing linearize.")
task = linearizePDF(result["resultDocumentId"], CLIENT_ID, CLIENT_SECRET)
print(f"Created task, id is {task['taskId']}")
result = checkTask(task["taskId"], CLIENT_ID, CLIENT_SECRET)
print("Done with linearize task.")
output = "../../output/really_optimized.pdf"
downloadResult(result["resultDocumentId"], output , CLIENT_ID, CLIENT_SECRET)
print(f"Done and saved to: {output}.")
print(f"File size of output: {os.path.getsize(output)}") This code matches the flow described above, with the exception of outputting the size as a handy way to see the result of the compression call. When run, the initial size is 355994 bytes and the final size is 16733. That's a great saving! You should, however, ensure the result matches the quality you desire and if not, consider reducing the level of compression. Linearize doesn't impact the file size, but as stated above will make it work nicer on the web.
For a complete listing, find the sample on our GitHub repo.
Next Steps
Obviously, you could do even more chaining based on the code above. For example, as part of your optimization flow, you could even split the PDF to return a 'sample' of a document that may be for sale. You could extract information to use for AI purposes and more. Dig more into our PDF Service APIs to get an idea and let us know what you build on our developer forums!
Introducing PDF APIs from Foxit

Get started with Foxit’s new PDF APIs—convert Word to PDF, generate documents, and embed files using simple, scalable REST APIs. Includes sample Python code and walkthrough.
Introducing PDF APIs from Foxit
At the end of June, Foxit introduced a brand-new suite of tools to help developers work with documents. These APIs cover a wide range of features, including:
- Convert between Office document formats and PDF files seamlessly
- Optimize, manipulate, and secure PDFs with advanced APIs
- Generate dynamic documents using Microsoft Word templates
- Extract text and images from PDFs with powerful tools
- Embed PDFs into web pages in a context-aware, controlled manner
- Integrate with eSign APIs for streamlined signature workflows
These APIs are simple to use, and best of all, follow the “don’t surprise me” principal of development. In this post, I’m going to demonstrate one simple example – converting a Word document to PDF – but you can rest assured that nearly all the APIs will follow incredibly similar patterns. I’ll be using Python for my examples here, but will link to a Node.js version of the same example. And given that we’re talking REST APIs here, any language is welcome to join the document party. Let’s dive in.
Credentials
Before we go any further, head over to our developer portal and grab a set of free credentials. This will include a client ID and secret values you’ll need to make use of the API.
Don’t want to read all of this? You can also follow along by video:
API Flow
As I mentioned above, most of the PDF Services APIs will follow a similar flow. This comes down to:
- Upload your input (like a Word document)
- Kick off a job (like converting to PDF)
- Check the job (hey, how ya doin?)
- Download the result
Or, in pretty graphical format –
The great thing is, once you’ve completed one integration (this post focuses on converting Word to PDF), switching to another is easy—and much of your existing code can be reused. A lazy developer is happy developer! Let’s get started.
Loading Credentials
My script begins by loading the credentials and API root host via the environment:
CLIENT_ID = os.environ.get('CLIENT_ID')
CLIENT_SECRET = os.environ.get('CLIENT_SECRET')
HOST = os.environ.get('HOST') It’s never a good idea to hard-code credentials in your code. But if you do it this one time, I won’t tell. Honest.
Uploading Your Input
As I mentioned, in this example we’ll be making use of the Word to PDF API. Our input will be a Word document, which we’ll upload to Foxit using the upload API. This endpoint is fairly simple – aside from your credentials, all you need to provide is the binary data of the input file. Here’s the method I created to make this process easier:
def uploadDoc(path, id, secret):
headers = {
"client_id":id,
"client_secret":secret
}
with open(path, 'rb') as f:
files = {'file': (path, f)}
request = requests.post(f"{HOST}/pdf-services/api/documents/upload", files=files, headers=headers)
return request.json() And here’s how it’s used:
doc = uploadDoc("../../inputfiles/input.docx", CLIENT_ID, CLIENT_SECRET)
print(f"Uploaded doc to Foxit, id is {doc['documentId']}") The upload API only returns one value, a documentId, which we can use in future calls.
Starting the Job
Each API operation is a job creator. By this I mean you call the endpoint and it begins your action. For Word to PDF, the only required input is the document ID from the previous call. We can build a nice little wrapper function like so:
def convertToPDF(doc, id, secret):
headers = {
"client_id":id,
"client_secret":secret,
"Content-Type":"application/json"
}
body = {
"documentId":doc
}
request = requests.post(f"{HOST}/pdf-services/api/documents/create/pdf-from-word", json=body, headers=headers)
return request.json() And then call it like so:
task = convertToPDF(doc["documentId"], CLIENT_ID, CLIENT_SECRET)
print(f"Created task, id is {task['taskId']}") The result of this call, if no errors were found, isa taskId. We can use this to gauge how the job’s performing. Let’s do that now.
Job Checking
Ok, so the next part can be a bit tricky depending on your language of choice. We need to use the task status endpoint to determine how the job is performing. How often we do this, how quickly and so forth, will depend on your platform and needs. For our little sample script here, everything is running at once. I wrote a function that will check the status. If the job isn’t finished (whether successful or not), it pauses briefly before trying again. While this approach isn’t the most sophisticated, it should work well enough for basic testing:
def checkTask(task, id, secret):
headers = {
"client_id":id,
"client_secret":secret,
"Content-Type":"application/json"
}
done = False
while done is False:
request = requests.get(f"{HOST}/pdf-services/api/tasks/{task}", headers=headers)
status = request.json()
if status["status"] == "COMPLETED":
done = True
# really only need resultDocumentId, will address later
return status
elif status["status"] == "FAILED":
print("Failure. Here is the last status:")
print(status)
sys.exit()
else:
print(f"Current status, {status['status']}, percentage: {status['progress']}")
sleep(5) As you can see, I’m using a while loop that—at least in theory—will continue running until a success or failure response is returned, with a five-second pause between each call. You can adjust that interval as needed—test different values to see what works best for your use case. Typically, most API calls should complete in under ten seconds, so a five-second delay felt like a reasonable default.
Each call to the endpoint returns a task status result. Here’s an example:
{
'taskId': '685abc95a0d113558e4204d7',
'status': 'COMPLETED',
'progress': 100,
'resultDocumentId': '685abc952475582770d6917b'
} The important part here is the status. But you could also use progress to give some feedback to the code waiting for results. Here’s my code calling this:
result = checkTask(task["taskId"], CLIENT_ID, CLIENT_SECRET)
print(f"Final result: {result}") Downloading Your Result
The last piece of the puzzle is simply saving the result. If you noticed above, the task returned a resultDocumentId value. Taking that, and the [Download Document](NEED LINK) endpoint, we can build a utility to store the result like so:
def downloadResult(doc, path, id, secret):
headers = {
"client_id":id,
"client_secret":secret
}
with open(path, "wb") as output:
bits = requests.get(f"{HOST}/pdf-services/api/documents/{doc}/download", stream=True, headers=headers).content
output.write(bits) And finally, call it:
downloadResult(result["resultDocumentId"], "../../output/input.pdf", CLIENT_ID, CLIENT_SECRET)
print("Done and saved to: ../../output/input.pdf") And that’s it! While this script could certainly benefit from more robust error handling, it demonstrates the basic flow. As mentioned, most of our APIs follow this same logic.
Next Steps
Want the complete scripts? Get it on GitHub.
Want it in Node.js? Get it on GitHub.
Rather try this yourself? Sign up for a free developer account now. Need help? Head over to our developer forums and post your questions and comments.