Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use aiohttp to fetch the PDF and a PDF library to add the watermark: aiohttp transfers bytes; it does not edit PDF pages. The complete example below streams a remote PDF to a temporary file, inserts centered text on every page with PyMuPDF, and saves a separate output file. For a small PDF, you can instead read the response into memory. The important choices are how you place the mark, whether it goes above or behind existing content, and how you handle large, rotated, or unsuitable input files.

What aiohttp does—and what it does not do

aiohttp is the asynchronous HTTP client in this workflow. It can request a PDF, read its response as bytes, or stream the response to disk. It does not create or edit PDF content. Use a PDF library such as PyMuPDF to insert text directly, or create a separate one-page PDF stamp and merge it with pypdf.

The aiohttp 3.14.3 stable documentation notes that the convenience methods read(), text(), and json() load the complete response body into memory. That is convenient for small files, but streaming is safer when input size is large or unknown. See the aiohttp Client Quickstart.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Install the libraries

Use Python 3 with aiohttp and PyMuPDF installed in the environment that will run the script:

python -m pip install aiohttp pymupdf

The example uses PyMuPDF’s fitz module. You do not need pypdf as well unless you choose the stamp-PDF approach described below.

Download and watermark a PDF with aiohttp and PyMuPDF

This example streams the response in chunks, checks the HTTP status and basic PDF signature, rejects responses over a configurable example limit, then writes a watermarked copy. It deliberately preserves the downloaded source and uses a new output path. Change PDF_URL, OUTPUT_PDF, and WATERMARK for your case.

import asyncio
from pathlib import Path
import tempfile

import aiohttp
import fitz  # PyMuPDF

PDF_URL = "https://example.com/document.pdf"
OUTPUT_PDF = Path("watermarked.pdf")
WATERMARK = "DRAFT"
MAX_DOWNLOAD_BYTES = 100 * 1024 * 1024  # Example application limit: 100 MiB
CHUNK_SIZE = 64 * 1024

async def download_pdf(url: str, destination: Path) -> None:
    timeout = aiohttp.ClientTimeout(total=90)
    total = 0
    prefix = bytearray()

    async with aiohttp.ClientSession(timeout=timeout) as session:
        async with session.get(url) as response:
            response.raise_for_status()
            content_type = response.headers.get("Content-Type", "").lower()
            if content_type and "pdf" not in content_type and "octet-stream" not in content_type:
                raise ValueError(f"Unexpected Content-Type: {content_type}")

            with destination.open("wb") as output:
                async for chunk in response.content.iter_chunked(CHUNK_SIZE):
                    total += len(chunk)
                    if total > MAX_DOWNLOAD_BYTES:
                        raise ValueError("Download exceeds the configured example limit")
                    if len(prefix) < 5:
                        prefix.extend(chunk[: 5 - len(prefix)])
                    output.write(chunk)

    # A quick format check, not a complete PDF validity test.
    if bytes(prefix) != b"%PDF-":
        destination.unlink(missing_ok=True)
        raise ValueError("Response does not begin with a PDF signature")


def add_text_watermark(source: Path, destination: Path, text: str) -> None:
    document = fitz.open(source)
    try:
        for page in document:
            # Coordinates are based on the page rectangle. This places a
            # centered horizontal line; adjust the box and font for your pages.
            rect = page.rect
            box = fitz.Rect(
                rect.width * 0.15,
                rect.height * 0.43,
                rect.width * 0.85,
                rect.height * 0.57,
            )
            remaining = page.insert_textbox(
                box,
                text,
                fontname="helv",
                fontsize=min(36, rect.width / 12),
                color=(0.55, 0.55, 0.55),
                align=fitz.TEXT_ALIGN_CENTER,
                overlay=True,
            )
            if remaining < 0:
                raise ValueError(f"Watermark did not fit on page {page.number + 1}")

        document.save(destination, garbage=4, deflate=True)
    finally:
        document.close()


async def main() -> None:
    OUTPUT_PDF.parent.mkdir(parents=True, exist_ok=True)
    with tempfile.TemporaryDirectory() as temp_dir:
        downloaded = Path(temp_dir) / "source.pdf"
        await download_pdf(PDF_URL, downloaded)
        add_text_watermark(downloaded, OUTPUT_PDF, WATERMARK)
    print(f"Saved watermarked PDF to {OUTPUT_PDF}")

if __name__ == "__main__":
    asyncio.run(main())

The 100 MiB cap is an example policy in this script, not a universal safe limit. Choose a limit based on your service, memory, disk, and expected documents. A failed download or processing step should not cause you to overwrite the original.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
Document Camera Scanner Capture Portable Book A4 HD for ID Cards Passport Books Watermark Mega Pixel
  • Highlighting Basic Performance -- boasts a 10 mefapixel camera, scanning differents documents within A4 size, recording vedio and LED fill-in light.
  • Practical Functions -- support PDF format export, automatic correction, intelligent cutting, intelligent pagination and merging, code recognition, image quality compression, watermark setting and so on.
  • More Functions -- after being captured, the images can be optimized by adjusting the brightness, saturation, contrast, sharpness, etc.
  • User Friendly Design -- the document scanner is collapsible and portable; as carefully designed, easy for users to install and operate related software.
  • Wide Application: the max. scanning size is A4, can be used to scan various sizes of documents, including file, bill, ID card, passport and other documents of similar size. Widely used in office, classroom, library, bank, hospital, etc; can effectively improve our work efficiency.

Adjust placement and appearance

page.insert_textbox() writes text into a rectangular area. In this example, the box is centered and the text is horizontal. Change the box dimensions and coordinates to move it; reduce the font size or enlarge the box if the fit check reports that the text did not fit. The gray value is an RGB color tuple. This snippet does not set a transparency value, so do not assume it produces a translucent watermark.

PyMuPDF uses page coordinates and offers direct text insertion methods. Page geometry and rotation can make a position that looks correct on a portrait page land unexpectedly on a rotated or differently sized page. Test on representative pages—including landscape, rotated, and mixed-size documents—and inspect the rendered result before relying on it. The PyMuPDF “The Basics” guide describes page operations and coordinate concepts.

Overlay: above or behind the original content

The example uses overlay=True, so the inserted text is placed over existing page content. That is generally visible, but can cover text or other important information. PyMuPDF’s text example here is an overlay; check the visual result rather than assuming it is a background mark. If the watermark must sit behind existing content, use a workflow that supports that draw order and verify it in your target PDF viewer.

Rank #3
Scanner Portable Book Document Camera Capture A4 HD for ID Cards Passport Books Watermark Mega Pixel
  • Highlighting Basic Performance -- boasts a 10 mefapixel camera, scanning differents documents within A4 size, recording vedio and LED fill-in light.
  • Practical Functions -- support PDF format export, automatic correction, intelligent cutting, intelligent pagination and merging, code recognition, image quality compression, watermark setting and so on.
  • More Functions -- after being captured, the images can be optimized by adjusting the brightness, saturation, contrast, sharpness, etc.
  • User Friendly Design -- the document scanner is collapsible and portable; as carefully designed, easy for users to install and operate related software.
  • Wide Application: the max. scanning size is A4, can be used to scan various sizes of documents, including file, bill, ID card, passport and other documents of similar size. Widely used in office, classroom, library, bank, hospital, etc; can effectively improve our work efficiency.

Or skip the browser setup

ScreenshotNeo is a website screenshot API, not a PDF text-watermarking service. If your source is a webpage and the actual goal is a clean screenshot rather than editing a PDF, one GET request can capture it:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation. It accepts cookie or consent banners as a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, with the outcome identified in response headers. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents and MCP clients. The free plan includes 1,000 shots a month with no card; paid plans start at $5 for 3,000 shots. See ScreenshotNeo for the service. Sign up for 1,000 free screenshots a month—no card required.

Use aiohttp read() for a small PDF

When the response is known to be small enough for your application’s memory budget, the shorter pattern is to read the body into bytes and open it from an in-memory stream. PyMuPDF can open a byte stream with an explicit PDF file type:

import asyncio
import aiohttp
import fitz

async def fetch_bytes(url: str) -> bytes:
    timeout = aiohttp.ClientTimeout(total=90)
    async with aiohttp.ClientSession(timeout=timeout) as session:
        async with session.get(url) as response:
            response.raise_for_status()
            data = await response.read()
    if not data.startswith(b"%PDF-"):
        raise ValueError("Response does not appear to be a PDF")
    return data

async def main() -> None:
    data = await fetch_bytes("https://example.com/document.pdf")
    document = fitz.open(stream=data, filetype="pdf")
    try:
        for page in document:
            page.insert_text((72, 72), "DRAFT", fontsize=24, color=(0.55, 0.55, 0.55))
        document.save("watermarked.pdf")
    finally:
        document.close()

asyncio.run(main())

This compact version puts the text at a fixed coordinate near the top-left of each page; it is not centered, and page geometry or rotation can affect how it appears. Its key trade-off is memory: both the downloaded bytes and the PDF library’s working data may occupy memory while processing. Prefer the streamed-to-disk version for larger or less predictable documents.

Rank #4
Portable Scanner Book Document Camera Capture A4 HD for ID Cards Passport Books Watermark Mega Pixel
  • Highlighting Basic Performance -- boasts a 10 mefapixel camera, scanning differents documents within A4 size, recording vedio and LED fill-in light.
  • Practical Functions -- support PDF format export, automatic correction, intelligent cutting, intelligent pagination and merging, code recognition, image quality compression, watermark setting and so on.
  • More Functions -- after being captured, the images can be optimized by adjusting the brightness, saturation, contrast, sharpness, etc.
  • User Friendly Design -- the document scanner is collapsible and portable; as carefully designed, easy for users to install and operate related software.
  • Wide Application: the max. scanning size is A4, can be used to scan various sizes of documents, including file, bill, ID card, passport and other documents of similar size. Widely used in office, classroom, library, bank, hospital, etc; can effectively improve our work efficiency.

Alternative: merge a stamp PDF with pypdf

pypdf’s documented watermark method merges a stamp page onto each target page. It expects a PDF page as the stamp; the cited example does not generate text in that stamp. To use it for text, first render the text into a one-page PDF by another method, then merge that page:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
from pypdf import PdfReader, PdfWriter

source = PdfReader("source.pdf")
stamp = PdfReader("text-stamp.pdf").pages[0]
writer = PdfWriter()

for page in source.pages:
    page.merge_page(stamp, over=False)
    writer.add_page(page)

with open("watermarked.pdf", "wb") as output:
    writer.write(output)

In pypdf’s 6.6.2 “Adding a Stamp or Watermark to a PDF” documentation, over=False places the stamp behind the original page content; over=True puts it in front. The documentation notes that the process of stamping and watermarking is the same, with the over parameter distinguishing the draw order. A stamp page may need to be scaled or transformed to match the target page dimensions. The guide also advises transferring page rotation to page content when rotation causes a watermark to appear incorrectly.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Uploading the watermarked PDF

If the next step is sending the output to another service, aiohttp accepts file objects and streaming request bodies. For multipart form uploads, use aiohttp.FormData and provide the filename and content type expected by that service. Keep the upload separate from the download-and-edit stage so that you can inspect or retry the generated file.

One edge case matters for retries: aiohttp documents that a non-rewindable request body, such as an async generator or stream, may not be replayable after a redirect. If your upload endpoint can redirect, use a body strategy that can be resent, or handle the redirect explicitly. Consult the aiohttp 3.14.3 Client Reference for request-body behavior.

Reliability, security, and troubleshooting

HTTP errors, HTML error pages, or a misleading filename

A URL ending in .pdf does not prove that the response is a PDF. The script calls raise_for_status() before consuming a successful response, checks the optional content type, and checks the leading PDF signature. These are useful early checks, not a complete validation of file integrity. Some servers use generic content types, which is why the example permits application/octet-stream and treats the signature as a separate check. The PDF library can still reject a damaged or unusual file.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Timeouts or unexpectedly large downloads

The example sets a 90-second total request timeout and stops after its configured byte cap. Tune both to the expected source and service conditions; neither value is a universal requirement. For untrusted URLs, also apply an application-level policy about which hosts and sizes your program may fetch. Always remove temporary files when work ends, including on failure; the example uses a temporary directory for that cleanup.

Watermark is missing, clipped, or covers essential text

Check the coordinates, text box, font size, and insertion order. A negative return from insert_textbox() in the example is handled as a fit failure. Test different page dimensions and rotation states, and inspect the output in the PDF viewers your users actually use. A watermark must remain legible without obscuring legally or operationally important content.

Encrypted, malformed, or unusual PDFs

Not every malformed, encrypted, or unusual PDF is guaranteed to open or save successfully with these examples. Catch the relevant library exceptions in a production application, report the failure without publishing a partial output, and decide how password-protected files should be handled. Keep the source unchanged until the output has been saved and checked.

Processing appears slow or uses more memory than expected

Streaming controls how the HTTP response is read; it does not establish a benchmark for PDF processing speed or memory use. PDF libraries still need to parse and modify page structures, and the in-memory read() path retains the response body. No throughput or fidelity winner between pypdf and PyMuPDF follows from the cited documentation. Measure with documents representative of your workload if performance determines your choice.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Practical decision guide

  • Choose PyMuPDF direct text insertion when you want to place text without first preparing a stamp PDF and need to control its page position.
  • Choose pypdf stamp merging when you already have a PDF stamp page or want to generate that stamp separately; explicitly choose foreground or background merge order.
  • Choose aiohttp read() only when whole-response memory use is acceptable for your input sizes.
  • Stream to a temporary file when size is large or not known in advance, and set an application-specific limit and timeout.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.