LogoSignature Detection API

Detect signatures in PDFs with Python

Detect signatures in PDFs with Python

Signature Detection Team

Signature Detection Team

@Signature Detection Team

A contract PDF is not an image, but the detection result you want is still a list of boxes, one list per page. This post shows the PDF request body, the top-level response, and what a page with count 0 means.

The endpoint is the same one used for images:

POST https://signature-detector.com/api/detect

Send header x-api-key. The JSON body for a PDF is exactly one of pdfUrl or pdfBase64. Do not also send imageUrl or imageBase64 in that request.

PDF by URL

pdfUrl has to be an http or https link the API can download on its own. A link that only works when your browser is logged in will fail, because the server does not forward your cookies.

import requests
 
API_URL = "https://signature-detector.com/api/detect"
HEADERS = {
    "Content-Type": "application/json",
    "x-api-key": "your_api_key_here",
}
 
response = requests.post(
    API_URL,
    headers=HEADERS,
    json={"pdfUrl": "https://example.com/contract.pdf"},
    timeout=120,
)
response.raise_for_status()
data = response.json()
 
for page in data["pages"]:
    print(f"page {page['page']}: {page['count']} mark(s)")
    for detection in page["detections"]:
        box = detection["bbox"]
        print(
            f"  confidence {detection['confidence']:.3f} "
            f"box ({box['x1']:.1f}, {box['y1']:.1f}) "
            f"to ({box['x2']:.1f}, {box['y2']:.1f})"
        )
 
print("Total marks:", data["count"])
print("Credits used:", data["creditsUsed"])

pages, count, and creditsUsed sit on the response itself. They are not under result. An image call still uses result.detections. Branch on which body you sent.

Each page has:

  • page, a 1-based page number
  • width and height, pixels of the raster that was detected (150 DPI, long edge capped at 2,000; US Letter is 1275 by 1650)
  • detections, the same objects an image call returns
  • count, how many detections are on that page

count is the total across pages. pageCount is the number of pages. documentType is "pdf". signed is true when count is greater than zero. That flag means a visual mark was returned. It does not mean a certificate is present. confidenceThreshold is 0.1. PDF detection always uses that cutoff. An image request may send threshold from 0 to 1, and that field is not applied to PDF pages. A value outside 0 to 1 is HTTP 400.

Each detection looks like an image detection:

  • bbox.x1, bbox.y1, bbox.x2, bbox.y2 are pixel positions on that rendered page, origin at the top-left
  • confidence is the detector's score
  • class and class_name follow the image API. The published image example uses 0 and "signature"

The envelope also includes creditsRemaining and, when the row is stored, detectionId.

The Python and Node clients

The published packages post the same fields. Python is signature-detector, imported as signature_detector. Node is the package signature-detector.

from signature_detector import SignatureDetector
 
with SignatureDetector("your_api_key_here", timeout=120) as client:
    pdf = client.detect_pdf(pdf_url="https://example.com/contract.pdf")
    # or client.detect_pdf(pdf_base64=pdf_base64)
 
print(pdf.signed, pdf.page_count, pdf.credits_used)
for page in pdf.pages:
    print(page.page, page.count)
import { SignatureDetector } from "signature-detector";
 
const client = new SignatureDetector({
  apiKey: "your_api_key_here",
  timeoutMs: 120_000,
});
const pdf = await client.detectPdf({
  pdfUrl: "https://example.com/contract.pdf",
});
// or client.detectPdf({ pdfBase64 })
 
console.log(pdf.signed, pdf.pageCount, pdf.creditsUsed);
for (const page of pdf.pages) {
  console.log(page.page, page.count);
}

detect_pdf and detectPdf send exactly one of pdfUrl or pdfBase64 and read pages, count, and creditsUsed from the top level. Python names the credit field credits_used and the page total page_count. HTTP 402 raises InsufficientCreditsError in both packages, and the balance is not charged.

The default client timeout is 60 seconds. PDF processing is allowed 80 seconds on the server, so pass timeout=120 or timeoutMs: 120_000.

PDF from disk

import base64
import requests
 
with open("contract.pdf", "rb") as handle:
    pdf_base64 = base64.b64encode(handle.read()).decode("ascii")
 
response = requests.post(
    "https://signature-detector.com/api/detect",
    headers={
        "Content-Type": "application/json",
        "x-api-key": "your_api_key_here",
    },
    json={"pdfBase64": pdf_base64},
    timeout=120,
)
 
if response.status_code == 402:
    body = response.json()
    raise SystemExit(
        f"Need {body['creditsRequired']} credits for {body['pageCount']} pages, "
        f"have {body['credits']}"
    )
response.raise_for_status()
data = response.json()

Send pdfBase64 or pdfUrl, not both. A data-URL prefix on the base64 string is stripped if you include one. Raw base64 is what this snippet sends.

A 402 body looks like this when a 12-page file is sent with 3 credits left:

{
  "error": "Insufficient credits. This PDF has 12 pages and costs 12 credits (1 per page). You have 3.",
  "code": "insufficient_credits",
  "credits": 3,
  "creditsRequired": 12,
  "pageCount": 12,
  "needsPurchase": true
}

No page is rasterized or charged on that response.

Billing and cache

A new PDF spends 1 credit per page, including pages with count 0. Images stay at 1 credit per request. A 10-page file is 10 credits, which is $0.001. Signup includes 10 credits, the minimum purchase is $1 (10,000 credits), and credits do not expire.

The cache key is pdf: plus the SHA-256 of the whole file. Sending those exact bytes again returns the stored summary with cached: true, creditsUsed: 0, and creditsRemaining unchanged. Saving the PDF again from an editor usually changes the bytes, and that copy is charged.

If rendering or detection fails after the charge, those credits are refunded. A timeout is HTTP 504.

Limits that fail before a charge: more than 20 pages or an encrypted PDF is HTTP 400, and a file over 20 MB is HTTP 413.

Pages with no mark

required_pages = {2, 8}  # your checklist, not an API field
empty = [
    page["page"]
    for page in data["pages"]
    if page["page"] in required_pages and page["count"] == 0
]

An empty page means no visual mark was returned. It does not mean a cryptographic PDF signature is absent. Plenty of electronically signed PDFs have a certificate and no ink. This API does not read the PDF signature dictionary. If you need that check, use a PDF library that inspects /Sig fields, and use this API for scanned or flattened marks.

If you render pages yourself

Rendering to images is still a valid path, and it is the path that uses imageUrl or imageBase64 rather than the PDF fields. One request is one image. A 12-page packet is 12 requests if you send every page, and each uncached request spends 1 credit ($0.0001). That is the same credit total as one 12-page PDF.

response = requests.post(
    "https://signature-detector.com/api/detect",
    headers=HEADERS,
    json={"imageUrl": "https://example.com/contract-page-2.png"},
    timeout=60,
)

The image response has result.detections and result.count. It does not have pages or creditsUsed. Don't share a parser between the two shapes without branching.

Errors

Status Meaning
400 Neither source was sent, both were sent, the PDF is encrypted, it has no pages or more than 20, or the file is not a readable PDF
401 The API key is missing or not active
402 The balance is below 1 credit per page. needsPurchase is true. Nothing is charged
413 The decoded PDF is larger than 20 MB
503 The detector did not accept the call. Credits already charged are refunded
504 Processing timed out. Credits already charged are refunded
500 The detector's response could not be used. Credits already charged are refunded

PDF pages always use 0.1. Filter confidence in Python if you want a stricter queue after the response. A cutoff such as 0.5 is a choice you make. It is not a PDF request field, and it is not a number this service publishes as an ideal operating point. Image requests can send threshold from 0 to 1.

More context: PDF signature detection, the image API, and checking whether a document is signed.

Ready to detect signatures?

Detect signatures in PDFs with Python | Signature Detection API