IronSoftwareYou refreshed the dashboard and the number was right there: 214,000 API calls this month. Your...
You refreshed the dashboard and the number was right there: 214,000 API calls this month. Your application generates a PDF for every order confirmation, every shipping label, and every monthly statement. Each one is an HTTP POST to v2.api2pdf.com, a wait for the response, a download of the generated file from a temporary URL, and a prayer that the 24-hour auto-delete does not fire before your async job picks it up.
Api2pdf solves a real problem: it is genuinely hard to run wkhtmltopdf or headless Chrome on a managed hosting platform. But at 214,000 calls per month, you are paying for network latency, external service availability, and temporary file management that you would not need if the PDF engine ran inside your own process.
This guide covers the migration from Api2pdf's REST API to IronPDF's in-process .NET library. This is not an API-to-API migration. It is an execution model change from SaaS to library. Eighty percent of this content helps you evaluate any in-process alternative. The remaining twenty percent is IronPDF-specific.
Api2pdf removes the pain of hosting Chrome or wkhtmltopdf yourself. Here is why teams outgrow it:
| Aspect | Api2pdf (REST API) | IronPDF (.NET library) |
|---|---|---|
| Focus | Cloud PDF generation via REST | In-process PDF generation + manipulation |
| Pricing | Usage-based per API call | Per-developer, perpetual license |
| API Style | HTTP REST + client SDK | Native .NET API, no network calls |
| Learning Curve | Low, simple HTTP calls | Low, simple .NET API calls |
| HTML Rendering | Headless Chrome or wkhtmltopdf (remote) | Chrome engine (in-process) |
| Page Indexing | N/A (service handles internally) | 0-based |
| Thread Safety | N/A (stateless API) |
ChromePdfRenderer stateless, safe to share |
| Namespace | Api2Pdf |
IronPdf |
| Feature | Complexity | Notes |
|---|---|---|
| HTML to PDF (Chrome) | Low | Direct replacement; IronPDF runs Chrome in-process |
| URL to PDF | Low |
RenderUrlAsPdf replaces the Chrome URL endpoint |
| HTML to PDF (wkhtmltopdf) | Low | Replace with IronPDF Chrome renderer, better output quality |
| Merge | Low |
PdfDocument.Merge() replaces the PdfSharp merge endpoint |
| Office to PDF (LibreOffice) | High / N/A | No direct IronPDF equivalent. Use LibreOffice headless locally or a separate service |
| Password protection | Low | IronPDF SecuritySettings replaces the password endpoint |
| Bookmarks | Medium | IronPDF exposes bookmark APIs on PdfDocument
|
| Barcode generation | N/A | Not a PDF feature. Use a dedicated barcode library (e.g., IronBarcode, ZXing.NET) |
| File deletion (security) | N/A | Not needed. Files never leave your server |
| Thumbnail generation | Medium | Use pdf.RasterizeToImageFiles(...) or pdf.ToBitmap()
|
| HTML to DOCX/XLSX | N/A | Not an IronPDF feature. Use a dedicated library |
| Scenario | Recommendation |
|---|---|
| High-volume HTML-to-PDF with latency concerns | Migrate. In-process rendering eliminates network overhead |
| Regulated industry with data residency requirements | Migrate. HTML never leaves your infrastructure |
| Low-volume, diverse format conversion (Office, email, images) | Stay or partial migrate. Keep Api2pdf for Office conversion, use IronPDF for HTML-to-PDF |
| Air-gapped or on-premise deployment | Migrate. Api2pdf requires internet |
Checklist: Pre-Migration Readiness
- [ ] Count your monthly Api2pdf API calls (check your dashboard)
- [ ] Categorize calls by endpoint: Chrome HTML, Chrome URL, wkhtmltopdf, LibreOffice, merge, password
- [ ] Identify any LibreOffice (Office to PDF) calls. These need a separate solution
- [ ] Check if your HTML contains sensitive data sent to Api2pdf's servers
- [ ] Verify your deployment supports running IronPDF (needs .NET 6+ or Framework 4.6.2+)
- [ ] Obtain an IronPDF trial key from ironpdf.com/get-started/license-keys/
# Find all Api2pdf client usage
rg -l "Api2Pdf|api2pdf|ChromeHtmlToPdfRequest|WkhtmlHtmlToPdfRequest" --glob "*.cs"
# Count API call sites
rg -c "\.Chrome\.|\.Wkhtml\.|\.PdfSharp\.|\.LibreOffice\." --glob "*.cs"
# Find configuration (API keys)
rg "Api2Pdf|api2pdf" --glob "*.json" --glob "*.config" --glob "*.cs"
In PowerShell: Get-ChildItem -Recurse -Filter *.cs | Select-String "Api2Pdf|api2pdf".
# Remove Api2pdf
dotnet remove package Api2Pdf
# Install IronPDF
dotnet add package IronPdf
Before (Api2pdf):
// Api2pdf — API key passed to constructor
using Api2Pdf;
using System;
class Program
{
static void Main()
{
// API key from portal.api2pdf.com
var a2pClient = new Api2Pdf("your-api2pdf-api-key");
// Every call after this hits the internet
Console.WriteLine("Api2pdf client ready.");
}
}
After (IronPDF):
using IronPdf;
class Program
{
static void Main()
{
// License key — no internet required for rendering
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
Console.WriteLine($"IronPDF licensed: {License.IsLicensed}");
// Everything runs locally from here
}
}
Before:
using Api2Pdf;
After:
using IronPdf;
using IronPdf.Editing; // watermarks, headers, footers
using IronPdf.Rendering; // render options
Before (Api2pdf, ~15 lines):
using Api2Pdf;
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var a2pClient = new Api2Pdf("your-api-key");
var request = new ChromeHtmlToPdfRequest
{
Html = "<h1>Invoice #1042</h1><p>Amount: $5,200.00</p>"
};
var result = await a2pClient.Chrome.HtmlToPdfAsync(request);
if (result.Success)
{
// Result is a URL — you must download the file
Console.WriteLine("PDF available at: " + result.FileUrl);
// Download before the 24-hour expiry
using var http = new HttpClient();
var pdfBytes = await http.GetByteArrayAsync(result.FileUrl);
File.WriteAllBytes("invoice.pdf", pdfBytes);
Console.WriteLine("Downloaded and saved.");
}
else
{
Console.WriteLine("Error: " + result.Error);
}
}
}
After (IronPDF, 8 lines):
using IronPdf;
class Program
{
static void Main()
{
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(
"<h1>Invoice #1042</h1><p>Amount: $5,200.00</p>");
pdf.SaveAs("invoice.pdf");
Console.WriteLine("Saved locally. No download needed.");
}
}
Checklist: What You Just Eliminated
- [x] HTTP round-trip to external API
- [x] Temporary file URL with 24-hour expiry
- [x]
HttpClient.GetByteArrayAsyncdownload step- [x] Internet connectivity requirement for rendering
- [x] API key management and rotation
- [x] External service availability dependency
| Api2pdf Namespace | IronPDF Namespace | Purpose |
|---|---|---|
Api2Pdf |
IronPdf |
Core client/renderer |
Api2Pdf.Models |
IronPdf.Rendering |
Request options / render settings |
| N/A | IronPdf.Editing |
Watermarks, headers, footers |
| Api2pdf Class | IronPDF Class | Description |
|---|---|---|
Api2Pdf (client) |
ChromePdfRenderer |
The entry point for PDF generation |
ChromeHtmlToPdfRequest |
ChromePdfRenderOptions |
Render configuration |
Api2PdfResult |
PdfDocument |
The generated PDF (URL vs. in-memory object) |
a2pClient.PdfSharp |
PdfDocument static methods |
Merge and manipulation |
| Operation | Api2pdf | IronPDF |
|---|---|---|
| HTML to PDF |
a2pClient.Chrome.HtmlToPdf(request) → URL |
renderer.RenderHtmlAsPdf(html) → PdfDocument
|
| URL to PDF |
a2pClient.Chrome.UrlToPdf(request) → URL |
renderer.RenderUrlAsPdf(url) → PdfDocument
|
| File to download | result.SaveFile("path") |
pdf.SaveAs("path") |
| Merge |
a2pClient.PdfSharp.MergePdfs(...) → URL |
PdfDocument.Merge(pdf1, pdf2) → PdfDocument
|
| Operation | Api2pdf | IronPDF |
|---|---|---|
| Get page count | N/A (not exposed via API) | pdf.PageCount |
| Access specific page | N/A |
pdf.Pages[index] (0-based) |
| Add watermark | Not directly supported | pdf.ApplyWatermark(html) |
| Set password | a2pClient.PdfSharp.SetPassword(new PdfPasswordRequest { ... }) |
pdf.SecuritySettings.UserPassword = "..." |
| Operation | Api2pdf | IronPDF |
|---|---|---|
| Merge PDFs |
a2pClient.PdfSharp.MergePdfs(new PdfMergeRequest { Urls = ... }) → URL |
PdfDocument.Merge(pdf1, pdf2) |
| Split / extract | Not directly supported | pdf.CopyPages(start, end) |
Before (Api2pdf):
using Api2Pdf;
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
class HtmlToPdfApi2pdf
{
static async Task Main()
{
var a2p = new Api2Pdf("your-api-key");
var request = new ChromeHtmlToPdfRequest
{
Html = @"<html><head><style>
body { font-family: Arial; }
.header { background: #0d47a1; color: white; padding: 20px; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
td, th { padding: 8px; border: 1px solid #ddd; }
</style></head><body>
<div class='header'><h1>Statement</h1></div>
<table>
<tr><th>Date</th><th>Description</th><th>Amount</th></tr>
<tr><td>2025-01-15</td><td>Service fee</td><td>$250.00</td></tr>
<tr><td>2025-01-20</td><td>Subscription</td><td>$99.00</td></tr>
</table>
</body></html>"
};
var result = await a2p.Chrome.HtmlToPdfAsync(request);
if (!result.Success)
{
Console.WriteLine("API error: " + result.Error);
return;
}
// Must download from temporary URL
using var http = new HttpClient();
var bytes = await http.GetByteArrayAsync(result.FileUrl);
File.WriteAllBytes("statement.pdf", bytes);
Console.WriteLine("Downloaded statement.pdf");
}
}
After (IronPDF):
using IronPdf;
class HtmlToPdfIronPdf
{
static void Main()
{
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PrintHtmlBackgrounds = true;
var pdf = renderer.RenderHtmlAsPdf(@"<html><head><style>
body { font-family: Arial; }
.header { background: #0d47a1; color: white; padding: 20px; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
td, th { padding: 8px; border: 1px solid #ddd; }
</style></head><body>
<div class='header'><h1>Statement</h1></div>
<table>
<tr><th>Date</th><th>Description</th><th>Amount</th></tr>
<tr><td>2025-01-15</td><td>Service fee</td><td>$250.00</td></tr>
<tr><td>2025-01-20</td><td>Subscription</td><td>$99.00</td></tr>
</table>
</body></html>");
pdf.SaveAs("statement.pdf");
Console.WriteLine("Saved locally. No download step.");
}
}
No HTTP client. No temporary URL. No download race condition. See IronPDF HTML-to-PDF tutorial.
Before (Api2pdf):
using Api2Pdf;
using System;
using System.IO;
using System.Net.Http;
using System.Collections.Generic;
using System.Threading.Tasks;
class MergeApi2pdf
{
static async Task Main()
{
var a2p = new Api2Pdf("your-api-key");
// Api2pdf merge requires URLs to existing PDFs
var pdfUrls = new List<string>
{
"https://your-server.com/files/part1.pdf",
"https://your-server.com/files/part2.pdf"
};
var result = await a2p.PdfSharp.MergePdfsAsync(new PdfMergeRequest { Urls = pdfUrls });
if (!result.Success)
{
Console.WriteLine("Merge error: " + result.Error);
return;
}
// Download the merged result
using var http = new HttpClient();
var bytes = await http.GetByteArrayAsync(result.FileUrl);
File.WriteAllBytes("merged.pdf", bytes);
Console.WriteLine("Merged and downloaded.");
}
}
After (IronPDF):
using IronPdf;
class MergeIronPdf
{
static void Main()
{
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
var pdf1 = PdfDocument.FromFile("part1.pdf");
var pdf2 = PdfDocument.FromFile("part2.pdf");
var merged = PdfDocument.Merge(pdf1, pdf2);
merged.SaveAs("merged.pdf");
Console.WriteLine("Merged locally.");
}
}
Checklist: Merge Migration
- [x] No need to host PDFs at public URLs for merge input
- [x] No download step after merge
- [x] Merge works with local files, byte arrays, or in-memory PdfDocuments
- [x] No 24-hour expiry on the merged result
See IronPDF merge documentation.
Before (Api2pdf, not directly supported):
// Api2pdf does not have a native watermark endpoint.
// Common workaround: generate the watermark as HTML overlay,
// or use a separate library after downloading the PDF.
// This is a gap in the Api2pdf feature set.
// You would need to:
// 1. Generate PDF via Api2pdf
// 2. Download the PDF
// 3. Use a local library (PdfSharp, iText, etc.) to add watermark
// 4. Save the result
Console.WriteLine("Watermarking requires a second tool with Api2pdf.");
After (IronPDF):
using IronPdf;
class WatermarkIronPdf
{
static void Main()
{
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
var pdf = PdfDocument.FromFile("input.pdf");
pdf.ApplyWatermark(
"<h1 style='color:rgba(0,0,0,0.15);font-size:60px;'>DRAFT</h1>",
rotation: 45
);
pdf.SaveAs("watermarked.pdf");
Console.WriteLine("Watermarked locally.");
}
}
IronPDF handles watermarking natively. No second library needed.
Before (Api2pdf):
using Api2Pdf;
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
class PasswordApi2pdf
{
static async Task Main()
{
var a2p = new Api2Pdf("your-api-key");
// Api2pdf password endpoint requires a URL to an existing PDF
var result = await a2p.PdfSharp.SetPasswordAsync(new PdfPasswordRequest
{
Url = "https://your-server.com/files/input.pdf",
UserPassword = "user456",
OwnerPassword = "owner123"
});
if (!result.Success)
{
Console.WriteLine("Password error: " + result.Error);
return;
}
using var http = new HttpClient();
var bytes = await http.GetByteArrayAsync(result.FileUrl);
File.WriteAllBytes("protected.pdf", bytes);
Console.WriteLine("Password-protected and downloaded.");
}
}
After (IronPDF):
using IronPdf;
class PasswordIronPdf
{
static void Main()
{
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
var pdf = PdfDocument.FromFile("input.pdf");
pdf.SecuritySettings.OwnerPassword = "owner123";
pdf.SecuritySettings.UserPassword = "user456";
pdf.SecuritySettings.AllowUserPrinting = IronPdf.Security.PdfPrintSecurity.NoPrint;
pdf.SecuritySettings.AllowUserCopyPasteContent = false;
pdf.SaveAs("protected.pdf");
Console.WriteLine("Protected locally.");
}
}
IronPDF gives you specific permission settings, not just a single password. Your PDF file never leaves your server. See IronPDF security documentation.
This is not a typical API-to-API migration. You are changing the execution model:
| Concern | Api2pdf (SaaS) | IronPDF (Library) |
|---|---|---|
| Where rendering happens | Api2pdf's AWS/GCP servers | Your server / container |
| Data transit | HTML sent over internet | HTML stays in-process |
| Availability | Depends on Api2pdf uptime | Depends on your uptime |
| Latency | Network round-trip + render time | Render time only |
| File lifecycle | 24-hour temp URL | Your filesystem / memory |
Api2pdf calls are inherently async (HTTP). IronPDF rendering is synchronous by default but offers async variants:
// IronPDF async rendering
var pdf = await renderer.RenderHtmlAsPdfAsync(html);
Api2pdf returns result.Success / result.Error on every call. IronPDF throws exceptions. Update your error handling:
// Api2pdf pattern
if (!result.Success) { log(result.Error); return; }
// IronPDF pattern
try { var pdf = renderer.RenderHtmlAsPdf(html); }
catch (Exception ex) { log(ex.Message); return; }
With Api2pdf, you downloaded PDFs from temporary URLs. With IronPDF, you have PdfDocument objects in memory. You choose when and where to persist them. Remove any download-and-cleanup logic from your codebase.
The most significant performance gain is eliminating the network round-trip. A rough benchmark:
These are illustrative ranges, not benchmarks. Your actual numbers depend on network conditions, HTML complexity, and server resources.
private static readonly ChromePdfRenderer _renderer = new ChromePdfRenderer();
using var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");
libreoffice --headless --convert-to pdf input.docx) or use a dedicated service. Alternatively, the IronWord library from Iron Software handles DOCX-to-PDF within .NET.Api2Pdf NuGet packageIronPdf NuGet packagenew Api2Pdf("key") with IronPdf.License.LicenseKey = "..."
a2pClient.Chrome.HtmlToPdf() with renderer.RenderHtmlAsPdf()
a2pClient.Chrome.UrlToPdf() with renderer.RenderUrlAsPdf()
a2pClient.PdfSharp.MergePdfs() with PdfDocument.Merge()
a2pClient.PdfSharp.SetPassword() with SecuritySettings propertiesHttpClient.GetByteArrayAsync(result.FileUrl) download coderesult.Success checks with try/catch exception handlingWorth knowing even without IronPDF: if you are evaluating in-process alternatives and want to stay open-source, Puppeteer Sharp is a .NET port of Puppeteer that drives headless Chrome locally. It requires managing a Chrome installation but produces the same quality output as Api2pdf's Chrome endpoint. Playwright for .NET is another option with broader browser support.
Here is my question: if you are currently running Api2pdf at high volume, what is your download-and-cleanup strategy? Do you download synchronously in the request, run a background job, or use webhooks? I am curious how teams handle the temporary URL lifecycle in production. It is the part of the Api2pdf model that introduces the most operational complexity.
The free trial is on NuGet if you want to test before committing.