LogoSignature Detection API

Detect signatures in a PDF

Send a PDF to the same detection endpoint. The response lists every page at the top level, with the same bounding-box objects the image API returns, and tells you how many credits the file used.

Open the playground

A new account starts with 10 detection credits. The playground asks you to sign in, then calls the same API.

Request

The PDF body uses the same URL and the same x-api-key header as an image call. Pick one source.

PDF by URL
curl -X POST https://signature-detector.com/api/detect \
  -H "Content-Type: application/json" \
  -H "x-api-key: your_api_key_here" \
  -d '{
    "pdfUrl": "https://example.com/contract.pdf"
  }'

For a file on disk, base64-encode the bytes and send pdfBase64 instead of pdfUrl. A private storage link fails the same way a private image URL fails: the server fetches pdfUrl with a plain HTTP GET and no cookies.

The official clients send those fields from Python detect_pdf (pdf_url or pdf_base64) and from Node detectPdf (pdfUrl or pdfBase64) in the signature-detector package. Sending PDF bytes through imageUrl or imageBase64 also works when the file starts with %PDF or the response content type says PDF. New code should use the PDF fields.

Python client
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.page_count, pdf.credits_used, pdf.pages[0].count)
Node client
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.pageCount, pdf.creditsUsed, pdf.pages[0].count);

detect_pdf and detectPdf read pages, count, and creditsUsed from the top level. Python exposes them as pages, count, page_count, and credits_used. A short balance raises InsufficientCreditsError from either package. The default client timeout is 60 seconds, and PDF processing is allowed 80 seconds on the server, so pass 120 seconds.

Pages in the response

Read pages, count, and creditsUsed on the JSON object. A page with count 0 had no mark at or above the server threshold of 0.1. width and height are the raster that was detected: 150 DPI, with the long edge capped at 2,000 pixels. A US Letter page is 1275 by 1650. Boxes use the same shape as the image API. Crop that raster, not the PDF point size.

200 response
{
  "success": true,
  "documentType": "pdf",
  "signed": true,
  "count": 1,
  "pageCount": 2,
  "confidenceThreshold": 0.1,
  "pages": [
    {
      "page": 1,
      "width": 1275,
      "height": 1650,
      "count": 1,
      "detections": [
        {
          "bbox": {
            "x1": 120.5,
            "y1": 980.2,
            "x2": 460.8,
            "y2": 1104.0
          },
          "confidence": 0.91,
          "class": 0,
          "class_name": "signature"
        }
      ]
    },
    {
      "page": 2,
      "width": 1275,
      "height": 1650,
      "count": 0,
      "detections": []
    }
  ],
  "creditsUsed": 2,
  "creditsRemaining": 98,
  "detectionId": "clxxxx"
}

A repeated identical file looks the same, with "cached": true and "creditsUsed": 0. detectionId is omitted on a fresh call if storing the row fails. The detections are still returned.

Find pages with no mark

Python
import requests

response = requests.post(
    "https://signature-detector.com/api/detect",
    headers={
        "Content-Type": "application/json",
        "x-api-key": "your_api_key_here",
    },
    json={"pdfUrl": "https://example.com/contract.pdf"},
    timeout=120,
)
response.raise_for_status()
data = response.json()

unsigned_pages = [
    page["page"] for page in data["pages"] if page["count"] == 0
]
print("Pages with no signature mark:", unsigned_pages)
print("Total marks:", data["count"])
print("Credits used:", data["creditsUsed"])

The step-by-step version, including pdfBase64, is Detect signatures in PDFs with Python. PDF pages always use 0.1. An optional threshold from 0 to 1 applies to images only. An image response still nests detections under result. Do not share one parser between the two shapes.

Not enough credits

The page count is known before any page is rendered. If the balance is short, the call stops with HTTP 402 and the balance does not change.

402
{
  "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
}

A file over 20 MB is HTTP 413. More than 20 pages, an encrypted PDF, or a file that is not a readable PDF is HTTP 400. If rendering or detection fails after the charge, the credits for that request are refunded. A timeout is HTTP 504.

Pricing

For a PDF, multiply the page count by one credit. 100,000 PDF pages that are not cached cost the same $10 as 100,000 uncached images. Sending each page as its own image is also 1 credit per page. The difference is one request versus one request per page, not the price.

Where this fits

Lending packets and contracts are usually PDFs. The lending and contracts pages describe what to do with a page that comes back empty. Checking whether a document is signed still means a visual mark was found. On a PDF, signed is that check and nothing more.

FAQ

Which fields does a PDF request use?

POST https://signature-detector.com/api/detect with header x-api-key. The JSON body is exactly one of pdfUrl or pdfBase64. pdfUrl is an http or https URL the server downloads with a plain GET. pdfBase64 is the PDF file as base64. Do not send both, and do not send those fields together with imageUrl or imageBase64.

Where are pages, count, and creditsUsed?

They are top-level fields on the 200 JSON, next to success. They are not nested under result. pages is an array. Each item has a 1-based page number, width and height in pixels of that rendered page, a detections array, and a count for that page. count is the total across pages. pageCount is the number of pages. creditsUsed is pageCount on a new file and 0 when the same PDF bytes were already detected. PDFs always use confidenceThreshold 0.1. An image request may send threshold from 0 to 1, and that value is not applied to PDF pages. A threshold outside 0 to 1 is HTTP 400. Each detection has bbox (x1, y1, x2, y2), confidence, class, and class_name. The origin is the top-left of the raster.

How are PDF requests billed?

A PDF spends 1 credit per page, including pages with no detections. A 10-page file is 10 credits ($0.001) the first time those bytes are seen. The balance is checked before any page is rasterized or detected. If the balance is lower than the page count, the response is HTTP 402 with code insufficient_credits, credits, creditsRequired, pageCount, and needsPurchase true, and nothing is charged. Images stay at 1 credit per uncached request. Signup still includes 10 credits, the minimum purchase is still $1, and credits still do not expire.

What happens when the same PDF is sent again?

The cache key is pdf: plus the SHA-256 of the whole file. An identical file returns the stored page summary with cached true, creditsUsed 0, and creditsRemaining unchanged. Re-saving or re-encoding the PDF changes the bytes, so it is a new file and is charged again.

Does this read a digital signature certificate?

No. signed is true only when count is greater than zero, meaning a visual mark was returned. A PDF can contain a cryptographic signature with no visible ink, and it can contain a scanned handwritten mark with no certificate. This endpoint does not parse the PDF signature dictionary. An empty page does not prove the file is unsigned in the cryptographic sense.

Related reading

Try it on your own file

Upload an image in the playground, or create an API key from the dashboard after signup. Read the API docs for the request fields.

Open the playground