Verify digital signatures in any PDF. One API call. No API key. Free.
Check who signed a document, when it was signed, and whether it's been tampered with.
POST https://api.econtract.online/verify-pdf
π Try it now β verify.econtract.online
curl -X POST https://api.econtract.online/verify-pdf \
-F "file=@contract.pdf"Response:
{
"document": {
"filename": "contract.pdf",
"hash": "sha256:a11f904a0b23f1c8...",
"size": 245780,
"tampered": false
},
"signatures": [
{
"signer": {
"name": "John Doe",
"email": "john@acme.com",
"organization": "Acme Corp",
"country": "US"
},
"signed_at": "2026-03-21T10:54:51.000Z",
"verdict": "valid",
"integrity": true,
"signature": true,
"chain": true,
"expired": false,
"certificate": {
"issuer": "CN=eContract CA,O=eContract",
"serial": "7A3B...",
"valid_from": "2025-03-21",
"valid_until": "2029-03-21"
},
"timestamp": {
"time": "2026-03-21T10:54:52.000Z",
"tsa": "DigiCert Timestamp Authority"
}
}
],
"summary": {
"total": 1,
"valid": 1,
"invalid": 0,
"verdict": "all_valid"
},
"verified_at": "2026-03-21T15:15:55.123Z",
"verified_by": "eContract - https://verify.econtract.online"
}const form = new FormData();
form.append("file", fileInput.files[0]);
const res = await fetch("https://api.econtract.online/verify-pdf", {
method: "POST",
body: form,
});
const data = await res.json();
if (data.summary.verdict === "all_valid") {
console.log("β
All signatures valid");
} else {
console.log("β Verification failed");
}import requests
with open("contract.pdf", "rb") as f:
res = requests.post(
"https://api.econtract.online/verify-pdf",
files={"file": f}
)
data = res.json()
print(f"Verdict: {data['summary']['verdict']}")
print(f"Tampered: {data['document']['tampered']}")
for sig in data["signatures"]:
print(f" {sig['signer']['name']} β {sig['verdict']}")$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.econtract.online/verify-pdf");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, [
"file" => new CURLFile("contract.pdf")
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = json_decode(curl_exec($ch));
echo "Verdict: " . $response->summary->verdict . "\n";
echo "Tampered: " . ($response->document->tampered ? "YES" : "No") . "\n";file, _ := os.Open("contract.pdf")
defer file.Close()
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, _ := writer.CreateFormFile("file", "contract.pdf")
io.Copy(part, file)
writer.Close()
resp, _ := http.Post(
"https://api.econtract.online/verify-pdf",
writer.FormDataContentType(),
body,
)
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Println(result["summary"])| Check | Description |
|---|---|
| Integrity | SHA-256 hash verification β has the document been modified after signing? |
| Signature | RSA cryptographic signature validation |
| Certificate Chain | Validates signer cert β intermediate CA β root CA |
| Certificate Expiry | Are all certificates in the chain still valid? |
| Timestamp | RFC 3161 TSA timestamp verification (when present) |
| Verdict | Meaning |
|---|---|
all_valid |
All signatures verified, document intact |
has_invalid |
One or more signatures failed verification |
no_signatures |
PDF has no digital signatures |
- β PAdES β PDF Advanced Electronic Signatures (EU standard)
- β CMS / PKCS#7 β Embedded CMS signatures
- β RSA + SHA-256 β Standard cryptographic signatures
- β RFC 3161 β Trusted timestamps from any TSA
- β Multi-signature β Documents with multiple signers
- β Any signing tool β DocuSign, Adobe Sign, GlobalSign, eContract, or any PKI-based signer
verify-pdf is built for AI agents that need to make trust decisions about contracts.
# AI agent workflow: verify before processing
result = requests.post(API_URL, files={"file": pdf}).json()
if result["document"]["tampered"]:
return "REJECT: Document has been tampered with"
if result["summary"]["verdict"] != "all_valid":
return "REJECT: Invalid signatures detected"
# Extract signer info for downstream processing
signers = [s["signer"]["name"] for s in result["signatures"]]
return f"APPROVED: Signed by {', '.join(signers)}"The same check is the verify_pdf tool of the eContract MCP server, so an assistant can verify a PDF the user attaches without any code:
https://api.econtract.online/mcp
Setup for each client: contractlayer/contractlayer β mcp. Connecting to the MCP server needs a free eContract account; verify_pdf itself needs no extra permission scope. The plain HTTP API above needs no account at all.
Use cases for AI agents:
- Contract review automation β verify before analyzing content
- CRM integrations β auto-validate incoming signed documents
- HR systems β verify offer letters and employment contracts
- Legal workflows β chain-of-custody signature validation
Request:
- Content-Type:
multipart/form-data - Field:
file(PDF binary, max 50 MB) - Authentication: None required
- Rate limit: 50 requests/day per IP
Response fields:
| Field | Type | Description |
|---|---|---|
document.filename |
string | Original filename |
document.hash |
string | SHA-256 hash (sha256:...) |
document.size |
number | File size in bytes |
document.tampered |
boolean | true if document was modified after signing |
signatures[] |
array | List of signature objects |
signatures[].signer.name |
string | Signer common name |
signatures[].signer.email |
string | Signer email (if available) |
signatures[].signer.organization |
string | Signer organization |
signatures[].signer.country |
string | Signer country code |
signatures[].signed_at |
string | ISO 8601 signing timestamp |
signatures[].verdict |
string | "valid" or "invalid" |
signatures[].integrity |
boolean | Document integrity check |
signatures[].signature |
boolean | Cryptographic signature check |
signatures[].chain |
boolean | Certificate chain check |
signatures[].expired |
boolean | Certificate expiry check |
signatures[].certificate |
object | Certificate details |
signatures[].timestamp |
object | RFC 3161 timestamp (if present) |
summary.total |
number | Total signatures found |
summary.valid |
number | Valid signature count |
summary.invalid |
number | Invalid signature count |
summary.verdict |
string | "all_valid", "has_invalid", or "no_signatures" |
Error responses:
// 400 β Invalid file
{ "statusCode": 400, "message": "No PDF file provided" }
// 413 β File too large
{ "statusCode": 413, "message": "File size exceeds 50MB limit" }
// 429 β Rate limited
{ "statusCode": 429, "message": "Rate limit exceeded. Try again tomorrow." }Coming soon. We're working on a Docker image for self-hosted deployment.
In the meantime, use the free hosted API at api.econtract.online.
- PAdES / CMS signature verification
- Certificate chain validation
- RFC 3161 timestamp verification
- Free hosted API (live)
- Web UI at verify.econtract.online
- Trust Store v2 (Mozilla CA bundle + Adobe AATL)
- CRL / OCSP revocation checking
- Docker image for self-hosting
- npm / pip packages
- Webhook notifications
- Batch verification endpoint
| Project | Description |
|---|---|
| contractlayer | The eContract MCP server and a config for every AI client |
| contract-manager | Send contracts for e-signature with the REST API (Node.js, Python) |
MIT β free for commercial and personal use.
Built by eContract
Tamper-proof digital signatures for the modern web.