The instinct, the moment a ticket says "add our logo and a confidentiality line to every page," is to reach for a PDF manipulation library. Pull in iText, or PDFBox, or pdf-lib. Calculate x and y coordinates by hand. Draw text at a fixed position, in a fixed font, at a fixed size, and hope the next design change does not mean recalculating every coordinate again. That instinct is a leftover from a world where the only way to put content in a PDF's margin was to draw it there, pixel by pixel, in a language built for placing shapes on a page, not for laying out a header.
The header and footer area of a PDF is margin space, and margin space is exactly what CSS was built to lay out. The alternative to a drawing API is not a bigger drawing API. It is not touching page coordinates at all.
What the endpoint actually takes
PDF4me's Add HTML Header Footer to PDF endpoint is a single POST /api/v2/AddHtmlHeaderFooter call. Four fields are required: docName, the output filename; docContent, the Base64-encoded source PDF; htmlContent, a plain HTML string with inline CSS (not Base64-encoded, unlike the document itself); and location, set to Header, Footer, or Both.
That htmlContent field is the whole point. It is not a template reference or a design-tool export. It is a string like:
<div style="text-align: center; font-family: Arial; font-size: 12px; color: #666;">Confidential, Internal Use Only</div>
Bold text, colored text, a logo pulled in as an embedded image, multi-column layout inside the header block, all of it is standard CSS applied to standard HTML, the same skills a frontend developer already has, applied to a part of a PDF that used to require learning a completely different, PDF-specific drawing model.
Positioning does not require coordinate math either. marginLeft, marginRight, marginTop, and marginBottom are optional pixel values that reserve the space the header or footer content renders into, and skipFirstPage excludes the very first page, useful for cover sheets that should not carry a repeated banner. pages controls which pages get the treatment at all: an empty string for every page, "1" for a single page, "1,3,5" for specific pages, "2-5" for a range, or "1,3,7-10" for a mix of both. Everything runs synchronously by default and returns the finished PDF as Base64 docContent in the response body; setting async to true switches to a 202 response with a polling Location header instead, which matters more once this is numbering hundreds of documents overnight than one file a user is waiting on.
In code
The request is one HTTP call. Reading the source PDF, Base64-encoding it, and sending the styled HTML string is the entire client-side job:
import base64
import time
import requests
API_KEY = "YOUR_API_KEY" # Base64-encoded key from the PDF4me dashboard
ENDPOINT = "https://api.pdf4me.com/api/v2/AddHtmlHeaderFooter"
with open("report.pdf", "rb") as f:
doc_content = base64.b64encode(f.read()).decode("utf-8")
payload = {
"docName": "report.pdf",
"docContent": doc_content,
"htmlContent": "<div style='text-align:center;font-family:Arial;font-size:11px;color:#666;'>Confidential, Internal Use Only</div>",
"location": "Footer",
"pages": "",
"skipFirstPage": True,
"marginBottom": 40.0,
"async": False
}
headers = {
"Content-Type": "application/json",
"Authorization": f"Basic {API_KEY}"
}
response = requests.post(ENDPOINT, json=payload, headers=headers)
if response.status_code == 200:
result = response.json()
pdf_bytes = base64.b64decode(result["docContent"])
with open("report-branded.pdf", "wb") as out:
out.write(pdf_bytes)
elif response.status_code == 202:
poll_url = response.headers["Location"]
while True:
poll = requests.get(poll_url, headers=headers)
if poll.status_code == 200:
pdf_bytes = base64.b64decode(poll.json()["docContent"])
with open("report-branded.pdf", "wb") as out:
out.write(pdf_bytes)
break
time.sleep(2)
skipFirstPage and marginBottom above are the two fields doing the actual layout work: the margin reserves 40 pixels of footer space, and skipping the first page keeps a cover sheet clean. Swap location to "Header" or "Both" and the same shape of request adds a header block, a footer block, or both in one call.
Where this stops being a library problem
A PDF manipulation library treats a header the same way it treats every other piece of content on the page: a shape or a text run placed at an absolute coordinate, styled through that library's own API surface, which is never quite the same surface twice across libraries or languages. Move to a different library, or a different language's port of the same library, and the styling code gets rewritten from scratch, because "bold, 10pt, right-aligned, in a specific shade of grey" means something different in every drawing API.
CSS does not have that problem, because CSS is not a PDF concept. font-weight: bold, text-align: right, color: #666666, these mean the same thing whether the HTML string is built in C#, Python, JavaScript, or Salesforce Apex, because none of those languages are the thing interpreting the styling. The rendering happens on PDF4me's side, once the request lands, so the calling code only ever needs to produce a string. That is a meaningfully smaller job than producing correct calls into a page-drawing API, and it is a job most backend teams already have the skills for without hiring for it.
The one thing this endpoint is not
PDF4me also has a dedicated Add Margin to PDF endpoint, and it is worth being precise about what separates the two, because they sound related and solve different problems. Add Margin takes marginLeft, marginRight, marginTop, and marginBottom in millimeters, extends the page canvas by that amount, and pushes the existing content inward. What lands in that new space is nothing. It is blank. The endpoint's job is to make room, not to fill it.
Add HTML Header Footer starts from the opposite assumption: there is already margin space at the top or bottom of the page, or marginTop/marginBottom on this endpoint reserve it, and the job is putting styled, meaningful content into that space. One endpoint creates emptiness. The other fills it. A workflow that needs both, more room at the top of the page and a styled banner inside that room, calls Add Margin first and Add Header Footer second, not one endpoint standing in for the other.
There is a third related endpoint worth knowing about if the entire ask is a page number and nothing else: Add Page Number to PDF handles that specific case directly, with its own alignment and format parameters, no HTML string required. If the number needs to sit inside a larger designed header alongside a logo or a document title, Add HTML Header Footer is still the right tool. If a number in the corner is the entire requirement, the dedicated endpoint is fewer parameters for the same result.
Testing the HTML before it hits a pipeline
Because htmlContent is a plain string built by application code, it is worth confirming the rendered output looks right before that string generation gets buried inside a workflow step nobody reopens for months. The API Tester runs this exact endpoint in the browser: upload a PDF, paste in the HTML and CSS, see the rendered result immediately, adjust the styling, try again. That loop, write CSS, see it rendered on an actual page, is a much shorter feedback cycle than deploying a change to a scheduled job and waiting for the next run to check whether the logo is centered.
Full code samples covering this endpoint, starting with C#, are maintained in the pdf4me-api-samples repository alongside the documentation itself.
Same endpoint, four ways into a workflow
The REST call is the mechanism. Most teams reach it through whichever automation platform already runs the rest of their document pipeline.
Power Automate exposes this as a connector action inside a Flow, so a styled header lands on a generated report the same step it gets produced, no separate branded-template maintenance. See Add HTML Header Footer to PDF in Power Automate.
Zapier wires the same fields into a Zap step, useful for branding a document the moment it lands from a form submission or a CRM export. See Add HTML Header Footer to PDF in Zapier.
Make does it as a module inside a scenario, typically placed right after a merge or convert step, so the finished, assembled document gets the header rather than each source file separately. See Add HTML Header Footer to PDF in Make.
n8n treats it as a node, which fits naturally into a self-hosted or code-adjacent pipeline where the HTML string itself might be built by an earlier code node before this one applies it. See Add HTML Header Footer to PDF in n8n.
Across all four, the underlying contract matches the REST call exactly: the same htmlContent, location, margins, and page-targeting options, exposed as connector fields instead of JSON keys. HTML and CSS validated once in the API Tester carries over unchanged into whichever platform actually runs the automation.
What this does not replace
This endpoint styles a header or footer block. It is not a full page-layout engine, and it is not a substitute for a PDF library on tasks that actually need one, precise multi-page table layout, form field placement, or complex vector graphics inside the document body itself are still library territory, or a different PDF4me endpoint's job entirely. The claim here is narrower and more honest than "never touch a PDF library again": for the specific, extremely common job of putting a styled banner, a logo, or a repeating line of text into the margin of every page, HTML and CSS already describe that layout better than a drawing API does, and reaching for the heavier tool for that one job is solving a smaller problem with a bigger hammer than it needs.
The next time a ticket asks for a branded header across a batch of generated PDFs, that is markup and a POST request, not a coordinate system to relearn.
Website: pdf4me.com
Documentation: docs.pdf4me.com
Developer portal: dev.pdf4me.com
Top comments (0)